blob: 5aacf419bb49979680e49441fdc6aa280630a54a [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.camera.app;
import android.annotation.TargetApi;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.AutoFocusMoveCallback;
import android.hardware.Camera.ErrorCallback;
import android.hardware.Camera.FaceDetectionListener;
import android.hardware.Camera.OnZoomChangeListener;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.ShutterCallback;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.view.SurfaceHolder;
import com.android.camera.debug.Log;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;
/**
* A class to implement {@link CameraManager} of the Android camera framework.
*/
class AndroidCameraManagerImpl implements CameraManager {
private static final Log.Tag TAG = new Log.Tag("AndroidCamMgrImpl");
private static final long CAMERA_OPERATION_TIMEOUT_MS = 2500;
private static final long MAX_MESSAGE_QUEUE_LENGTH = 256;
private Parameters mParameters;
private boolean mParametersIsDirty;
/* Messages used in CameraHandler. */
// Camera initialization/finalization
private static final int OPEN_CAMERA = 1;
private static final int RELEASE = 2;
private static final int RECONNECT = 3;
private static final int UNLOCK = 4;
private static final int LOCK = 5;
// Preview
private static final int SET_PREVIEW_TEXTURE_ASYNC = 101;
private static final int START_PREVIEW_ASYNC = 102;
private static final int STOP_PREVIEW = 103;
private static final int SET_PREVIEW_CALLBACK_WITH_BUFFER = 104;
private static final int ADD_CALLBACK_BUFFER = 105;
private static final int SET_PREVIEW_DISPLAY_ASYNC = 106;
private static final int SET_PREVIEW_CALLBACK = 107;
private static final int SET_ONE_SHOT_PREVIEW_CALLBACK = 108;
// Parameters
private static final int SET_PARAMETERS = 201;
private static final int GET_PARAMETERS = 202;
private static final int REFRESH_PARAMETERS = 203;
// Focus, Zoom
private static final int AUTO_FOCUS = 301;
private static final int CANCEL_AUTO_FOCUS = 302;
private static final int SET_AUTO_FOCUS_MOVE_CALLBACK = 303;
private static final int SET_ZOOM_CHANGE_LISTENER = 304;
// Face detection
private static final int SET_FACE_DETECTION_LISTENER = 461;
private static final int START_FACE_DETECTION = 462;
private static final int STOP_FACE_DETECTION = 463;
private static final int SET_ERROR_CALLBACK = 464;
// Presentation
private static final int ENABLE_SHUTTER_SOUND = 501;
private static final int SET_DISPLAY_ORIENTATION = 502;
// Capture
private static final int CAPTURE_PHOTO = 601;
/** Camera states **/
// These states are defined bitwise so we can easily to specify a set of
// states together.
private static final int CAMERA_UNOPENED = 1;
private static final int CAMERA_IDLE = 1 << 1;
private static final int CAMERA_UNLOCKED = 1 << 2;
private static final int CAMERA_CAPTURING = 1 << 3;
private static final int CAMERA_FOCUSING = 1 << 4;
private final CameraHandler mCameraHandler;
private final HandlerThread mCameraHandlerThread;
private final CameraStateHolder mCameraState;
private final DispatchThread mDispatchThread;
// Used to retain a copy of Parameters for setting parameters.
private Parameters mParamsToSet;
private Handler mCameraExceptionCallbackHandler;
private CameraExceptionCallback mCameraExceptionCallback =
new CameraExceptionCallback() {
@Override
public void onCameraException(RuntimeException e) {
throw e;
}
};
AndroidCameraManagerImpl() {
mCameraHandlerThread = new HandlerThread("Camera Handler Thread");
mCameraHandlerThread.start();
mCameraHandler = new CameraHandler(mCameraHandlerThread.getLooper());
mCameraExceptionCallbackHandler = mCameraHandler;
mCameraState = new CameraStateHolder();
mDispatchThread = new DispatchThread();
mDispatchThread.start();
}
@Override
public void setCameraDefaultExceptionCallback(CameraExceptionCallback callback,
Handler handler) {
synchronized (mCameraExceptionCallback) {
mCameraExceptionCallback = callback;
mCameraExceptionCallbackHandler = handler;
}
}
/**
* Recycles the resources used by this instance. CameraManager will be in
* an unusable state after calling this.
*/
public void recycle() {
mDispatchThread.end();
}
private static class CameraStateHolder {
private int mState;
public CameraStateHolder() {
setState(CAMERA_UNOPENED);
}
public CameraStateHolder(int state) {
setState(state);
}
public synchronized void setState(int state) {
mState = state;
this.notifyAll();
}
public synchronized int getState() {
return mState;
}
private interface ConditionChecker {
/**
* @return Whether the condition holds.
*/
boolean success();
}
/**
* A helper method used by {@link #waitToAvoidStates(int)} and
* {@link #waitForStates(int)}. This method will wait until the
* condition is successful.
*
* @param stateChecker The state checker to be used.
* @param timeoutMs The timeout limit in milliseconds.
* @return {@code false} if the wait is interrupted or timeout limit is
* reached.
*/
private boolean waitForCondition(ConditionChecker stateChecker,
long timeoutMs) {
long timeBound = SystemClock.uptimeMillis() + timeoutMs;
synchronized (this) {
while (!stateChecker.success()) {
try {
this.wait(timeoutMs);
} catch (InterruptedException ex) {
if (SystemClock.uptimeMillis() > timeBound) {
// Timeout.
Log.w(TAG, "Timeout waiting.");
}
return false;
}
}
}
return true;
}
/**
* Block the current thread until the state becomes one of the
* specified.
*
* @param states Expected states.
* @return {@code false} if the wait is interrupted or timeout limit is
* reached.
*/
public boolean waitForStates(final int states) {
return waitForCondition(new ConditionChecker() {
@Override
public boolean success() {
return (states | mState) == states;
}
}, CAMERA_OPERATION_TIMEOUT_MS);
}
/**
* Block the current thread until the state becomes NOT one of the
* specified.
*
* @param states States to avoid.
* @return {@code false} if the wait is interrupted or timeout limit is
* reached.
*/
public boolean waitToAvoidStates(final int states) {
return waitForCondition(new ConditionChecker() {
@Override
public boolean success() {
return (states & mState) == 0;
}
}, CAMERA_OPERATION_TIMEOUT_MS);
}
}
/**
* The handler on which the actual camera operations happen.
*/
private class CameraHandler extends Handler {
private Camera mCamera;
private class CaptureCallbacks {
public final ShutterCallback mShutter;
public final PictureCallback mRaw;
public final PictureCallback mPostView;
public final PictureCallback mJpeg;
CaptureCallbacks(ShutterCallback shutter, PictureCallback raw, PictureCallback postView,
PictureCallback jpeg) {
mShutter = shutter;
mRaw = raw;
mPostView = postView;
mJpeg = jpeg;
}
}
CameraHandler(Looper looper) {
super(looper);
}
private void startFaceDetection() {
mCamera.startFaceDetection();
}
private void stopFaceDetection() {
mCamera.stopFaceDetection();
}
private void setFaceDetectionListener(FaceDetectionListener listener) {
mCamera.setFaceDetectionListener(listener);
}
private void setPreviewTexture(Object surfaceTexture) {
try {
mCamera.setPreviewTexture((SurfaceTexture) surfaceTexture);
} catch (IOException e) {
Log.e(TAG, "Could not set preview texture", e);
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private void enableShutterSound(boolean enable) {
mCamera.enableShutterSound(enable);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void setAutoFocusMoveCallback(
android.hardware.Camera camera, Object cb) {
camera.setAutoFocusMoveCallback((AutoFocusMoveCallback) cb);
}
private void capture(final CaptureCallbacks cb) {
try {
mCamera.takePicture(cb.mShutter, cb.mRaw, cb.mPostView, cb.mJpeg);
} catch (RuntimeException e) {
// TODO: output camera state and focus state for debugging.
Log.e(TAG, "take picture failed.");
throw e;
}
}
public void requestTakePicture(
final ShutterCallback shutter,
final PictureCallback raw,
final PictureCallback postView,
final PictureCallback jpeg) {
final CaptureCallbacks callbacks = new CaptureCallbacks(shutter, raw, postView, jpeg);
obtainMessage(CAPTURE_PHOTO, callbacks).sendToTarget();
}
/**
* This method does not deal with the API level check. Everyone should
* check first for supported operations before sending message to this handler.
*/
@Override
public void handleMessage(final Message msg) {
try {
switch (msg.what) {
case OPEN_CAMERA: {
final CameraOpenCallback openCallback = (CameraOpenCallback) msg.obj;
final int cameraId = msg.arg1;
if (mCameraState.getState() != CAMERA_UNOPENED) {
openCallback.onDeviceOpenedAlready(cameraId);
break;
}
mCamera = android.hardware.Camera.open(cameraId);
if (mCamera != null) {
mParametersIsDirty = true;
// Get a instance of Camera.Parameters for later use.
if (mParamsToSet == null) {
mParamsToSet = mCamera.getParameters();
}
mCameraState.setState(CAMERA_IDLE);
if (openCallback != null) {
openCallback.onCameraOpened(
new AndroidCameraProxyImpl(cameraId, mCamera));
}
} else {
if (openCallback != null) {
openCallback.onDeviceOpenFailure(cameraId);
}
}
break;
}
case RELEASE: {
mCamera.release();
mCameraState.setState(CAMERA_UNOPENED);
mCamera = null;
break;
}
case RECONNECT: {
final CameraOpenCallbackForward cbForward =
(CameraOpenCallbackForward) msg.obj;
final int cameraId = msg.arg1;
try {
mCamera.reconnect();
} catch (IOException ex) {
if (cbForward != null) {
cbForward.onReconnectionFailure(AndroidCameraManagerImpl.this);
}
break;
}
mCameraState.setState(CAMERA_IDLE);
if (cbForward != null) {
cbForward.onCameraOpened(new AndroidCameraProxyImpl(cameraId, mCamera));
}
break;
}
case UNLOCK: {
mCamera.unlock();
mCameraState.setState(CAMERA_UNLOCKED);
break;
}
case LOCK: {
mCamera.lock();
mCameraState.setState(CAMERA_IDLE);
break;
}
case SET_PREVIEW_TEXTURE_ASYNC: {
setPreviewTexture(msg.obj);
break;
}
case SET_PREVIEW_DISPLAY_ASYNC: {
try {
mCamera.setPreviewDisplay((SurfaceHolder) msg.obj);
} catch (IOException e) {
throw new RuntimeException(e);
}
break;
}
case START_PREVIEW_ASYNC: {
final CameraStartPreviewCallbackForward cbForward =
(CameraStartPreviewCallbackForward) msg.obj;
mCamera.startPreview();
if (cbForward != null) {
cbForward.onPreviewStarted();
}
break;
}
case STOP_PREVIEW: {
mCamera.stopPreview();
break;
}
case SET_PREVIEW_CALLBACK_WITH_BUFFER: {
mCamera.setPreviewCallbackWithBuffer((PreviewCallback) msg.obj);
break;
}
case SET_ONE_SHOT_PREVIEW_CALLBACK: {
mCamera.setOneShotPreviewCallback((PreviewCallback) msg.obj);
break;
}
case ADD_CALLBACK_BUFFER: {
mCamera.addCallbackBuffer((byte[]) msg.obj);
break;
}
case AUTO_FOCUS: {
mCameraState.setState(CAMERA_FOCUSING);
mCamera.autoFocus((AutoFocusCallback) msg.obj);
break;
}
case CANCEL_AUTO_FOCUS: {
mCamera.cancelAutoFocus();
mCameraState.setState(CAMERA_IDLE);
break;
}
case SET_AUTO_FOCUS_MOVE_CALLBACK: {
setAutoFocusMoveCallback(mCamera, msg.obj);
break;
}
case SET_DISPLAY_ORIENTATION: {
mCamera.setDisplayOrientation(msg.arg1);
break;
}
case SET_ZOOM_CHANGE_LISTENER: {
mCamera.setZoomChangeListener((OnZoomChangeListener) msg.obj);
break;
}
case SET_FACE_DETECTION_LISTENER: {
setFaceDetectionListener((FaceDetectionListener) msg.obj);
break;
}
case START_FACE_DETECTION: {
startFaceDetection();
break;
}
case STOP_FACE_DETECTION: {
stopFaceDetection();
break;
}
case SET_ERROR_CALLBACK: {
mCamera.setErrorCallback((ErrorCallback) msg.obj);
break;
}
case SET_PARAMETERS: {
mParametersIsDirty = true;
mParamsToSet.unflatten((String) msg.obj);
mCamera.setParameters(mParamsToSet);
break;
}
case GET_PARAMETERS: {
if (mParametersIsDirty) {
mParameters = mCamera.getParameters();
mParametersIsDirty = false;
}
break;
}
case SET_PREVIEW_CALLBACK: {
mCamera.setPreviewCallback((PreviewCallback) msg.obj);
break;
}
case ENABLE_SHUTTER_SOUND: {
enableShutterSound((msg.arg1 == 1) ? true : false);
break;
}
case REFRESH_PARAMETERS: {
mParametersIsDirty = true;
break;
}
case CAPTURE_PHOTO: {
mCameraState.setState(CAMERA_CAPTURING);
capture((CaptureCallbacks) msg.obj);
break;
}
default: {
throw new RuntimeException("Invalid CameraProxy message=" + msg.what);
}
}
} catch (final RuntimeException e) {
if (msg.what != RELEASE && mCamera != null) {
try {
mCamera.release();
mCameraState.setState(CAMERA_UNOPENED);
} catch (Exception ex) {
Log.e(TAG, "Fail to release the camera.");
}
mCamera = null;
} else {
if (mCamera == null) {
if (msg.what == OPEN_CAMERA) {
if (msg.obj != null) {
((CameraOpenCallback) msg.obj).onDeviceOpenFailure(msg.arg1);
}
} else {
Log.w(TAG, "Cannot handle message " + msg.what + ", mCamera is null.");
}
return;
}
}
synchronized (mCameraExceptionCallback) {
mCameraExceptionCallbackHandler.post(new Runnable() {
@Override
public void run() {
mCameraExceptionCallback.onCameraException(e);
}
});
}
}
}
}
private class DispatchThread extends Thread {
private Queue<Runnable> mJobQueue;
private Boolean mIsEnded;
public DispatchThread() {
super("Camera Job Dispatch Thread");
mJobQueue = new LinkedList<Runnable>();
mIsEnded = new Boolean(false);
}
/**
* Queues up the job.
*
* @param job The job to run.
*/
public void runJob(Runnable job) {
if (isEnded()) {
throw new IllegalStateException(
"Trying to run job on interrupted dispatcher thread");
}
synchronized (mJobQueue) {
if (mJobQueue.size() == MAX_MESSAGE_QUEUE_LENGTH) {
throw new RuntimeException("Camera master thread job queue full");
}
mJobQueue.add(job);
mJobQueue.notifyAll();
}
}
/**
* Queues up the job and wait for it to be done.
*
* @param job The job to run.
* @param timeoutMs Timeout limit in milliseconds.
* @return Whether the waiting is interrupted.
*/
public boolean runJobSync(final Runnable job, Object waitLock, long timeoutMs) {
synchronized (waitLock) {
long timeoutBound = SystemClock.uptimeMillis() + timeoutMs;
try {
runJob(job);
waitLock.wait();
} catch (InterruptedException ex) {
Log.v(TAG, "Job interrupted");
if (SystemClock.uptimeMillis() > timeoutBound) {
// Timeout.
Log.v(TAG, "Timeout waiting camera operation");
}
return false;
}
}
return true;
}
/**
* Gracefully ends this thread. Will stop after all jobs are processed.
*/
public void end() {
synchronized (mIsEnded) {
mIsEnded = true;
}
synchronized(mJobQueue) {
mJobQueue.notifyAll();
}
}
private boolean isEnded() {
synchronized (mIsEnded) {
return mIsEnded;
}
}
@Override
public void run() {
while(true) {
Runnable job = null;
synchronized (mJobQueue) {
while (mJobQueue.size() == 0 && !isEnded()) {
try {
mJobQueue.wait();
} catch (InterruptedException ex) {
Log.v(TAG, "Dispatcher thread wait() interrupted, exiting");
break;
}
}
job = mJobQueue.poll();
}
if (job == null) {
// mJobQueue.poll() returning null means wait() is
// interrupted and the queue is empty.
if (isEnded()) {
break;
}
continue;
}
job.run();
synchronized (DispatchThread.this) {
mCameraHandler.post(new Runnable() {
@Override
public void run() {
synchronized (DispatchThread.this) {
DispatchThread.this.notifyAll();
}
}
});
try {
DispatchThread.this.wait();
} catch (InterruptedException ex) {
// TODO: do something here.
}
}
}
mCameraHandlerThread.quitSafely();
}
}
@Override
public void cameraOpen(final Handler handler, final int cameraId,
final CameraOpenCallback callback) {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(OPEN_CAMERA, cameraId, 0,
CameraOpenCallbackForward.getNewInstance(handler, callback)).sendToTarget();
}
});
}
/**
* A class which implements {@link CameraManager.CameraProxy} and
* camera handler thread.
*/
private class AndroidCameraProxyImpl implements CameraManager.CameraProxy {
private final int mCameraId;
/* TODO: remove this Camera instance. */
private final Camera mCamera;
private AndroidCameraProxyImpl(int cameraId, Camera camera) {
mCamera = camera;
mCameraId = cameraId;
}
@Override
public android.hardware.Camera getCamera() {
return mCamera;
}
@Override
public int getCameraId() {
return mCameraId;
}
// TODO: Make this package private.
@Override
public void release(boolean sync) {
Log.v(TAG, "camera manager release");
if (sync) {
final WaitDoneBundle bundle = new WaitDoneBundle();
mDispatchThread.runJobSync(new Runnable() {
@Override
public void run() {
mCameraHandler.sendEmptyMessage(RELEASE);
mCameraHandler.post(bundle.mUnlockRunnable);
}
}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS);
} else {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.removeCallbacksAndMessages(null);
mCameraHandler.sendEmptyMessage(RELEASE);
}
});
}
}
@Override
public void reconnect(final Handler handler, final CameraOpenCallback cb) {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(RECONNECT, mCameraId, 0,
CameraOpenCallbackForward.getNewInstance(handler, cb)).sendToTarget();
}
});
}
@Override
public void unlock() {
final WaitDoneBundle bundle = new WaitDoneBundle();
mDispatchThread.runJobSync(new Runnable() {
@Override
public void run() {
mCameraHandler.sendEmptyMessage(UNLOCK);
mCameraHandler.post(bundle.mUnlockRunnable);
}
}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS);
}
@Override
public void lock() {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.sendEmptyMessage(LOCK);
}
});
}
@Override
public void setPreviewTexture(final SurfaceTexture surfaceTexture) {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture)
.sendToTarget();
}
});
}
@Override
public void setPreviewTextureSync(final SurfaceTexture surfaceTexture) {
final WaitDoneBundle bundle = new WaitDoneBundle();
mDispatchThread.runJobSync(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture)
.sendToTarget();
mCameraHandler.post(bundle.mUnlockRunnable);
}
}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS);
}
@Override
public void setPreviewDisplay(final SurfaceHolder surfaceHolder) {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(SET_PREVIEW_DISPLAY_ASYNC, surfaceHolder)
.sendToTarget();
}
});
}
@Override
public void startPreview() {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(START_PREVIEW_ASYNC, null).sendToTarget();
}
});
}
@Override
public void startPreviewWithCallback(final Handler handler,
final CameraStartPreviewCallback cb) {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(START_PREVIEW_ASYNC,
CameraStartPreviewCallbackForward.getNewInstance(handler, cb)).sendToTarget();
}
});
}
@Override
public void stopPreview() {
final WaitDoneBundle bundle = new WaitDoneBundle();
mDispatchThread.runJobSync(new Runnable() {
@Override
public void run() {
mCameraHandler.sendEmptyMessage(STOP_PREVIEW);
mCameraHandler.post(bundle.mUnlockRunnable);
}
}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS);
}
@Override
public void setPreviewDataCallback(
final Handler handler, final CameraPreviewDataCallback cb) {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(SET_PREVIEW_CALLBACK, PreviewCallbackForward
.getNewInstance(handler, AndroidCameraProxyImpl.this, cb))
.sendToTarget();
}
});
}
@Override
public void setOneShotPreviewCallback(final Handler handler,
final CameraPreviewDataCallback cb) {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(SET_ONE_SHOT_PREVIEW_CALLBACK,
PreviewCallbackForward
.getNewInstance(handler, AndroidCameraProxyImpl.this, cb))
.sendToTarget();
}
});
}
@Override
public void setPreviewDataCallbackWithBuffer(
final Handler handler, final CameraPreviewDataCallback cb) {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(SET_PREVIEW_CALLBACK_WITH_BUFFER,
PreviewCallbackForward
.getNewInstance(handler, AndroidCameraProxyImpl.this, cb))
.sendToTarget();
}
});
}
@Override
public void addCallbackBuffer(final byte[] callbackBuffer) {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(ADD_CALLBACK_BUFFER, callbackBuffer)
.sendToTarget();
}
});
}
@Override
public void autoFocus(final Handler handler, final CameraAFCallback cb) {
final AutoFocusCallback afCallback = new AutoFocusCallback() {
@Override
public void onAutoFocus(final boolean b, Camera camera) {
if (mCameraState.getState() != CAMERA_FOCUSING) {
Log.w(TAG, "onAutoFocus callback returning when not focusing");
} else {
mCameraState.setState(CAMERA_IDLE);
}
handler.post(new Runnable() {
@Override
public void run() {
cb.onAutoFocus(b, AndroidCameraProxyImpl.this);
}
});
}
};
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraState.waitForStates(CAMERA_IDLE);
mCameraHandler.obtainMessage(AUTO_FOCUS, afCallback).sendToTarget();
}
});
}
@Override
public void cancelAutoFocus() {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.removeMessages(AUTO_FOCUS);
mCameraHandler.sendEmptyMessage(CANCEL_AUTO_FOCUS);
}
});
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public void setAutoFocusMoveCallback(
final Handler handler, final CameraAFMoveCallback cb) {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(SET_AUTO_FOCUS_MOVE_CALLBACK, AFMoveCallbackForward
.getNewInstance(handler, AndroidCameraProxyImpl.this, cb))
.sendToTarget();
}
});
}
@Override
public void takePicture(
final Handler handler, final CameraShutterCallback shutter,
final CameraPictureCallback raw, final CameraPictureCallback post,
final CameraPictureCallback jpeg) {
final PictureCallback jpegCallback = new PictureCallback() {
@Override
public void onPictureTaken(final byte[] data, Camera camera) {
if (mCameraState.getState() != CAMERA_CAPTURING) {
Log.v(TAG, "picture callback returning when not capturing");
} else {
mCameraState.setState(CAMERA_IDLE);
}
handler.post(new Runnable() {
@Override
public void run() {
jpeg.onPictureTaken(data, AndroidCameraProxyImpl.this);
}
});
}
};
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraState.waitForStates(CAMERA_IDLE);
mCameraHandler.requestTakePicture(ShutterCallbackForward
.getNewInstance(handler, AndroidCameraProxyImpl.this, shutter),
PictureCallbackForward
.getNewInstance(handler, AndroidCameraProxyImpl.this, raw),
PictureCallbackForward
.getNewInstance(handler, AndroidCameraProxyImpl.this, post),
jpegCallback);
}
});
}
@Override
public void setDisplayOrientation(final int degrees) {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(SET_DISPLAY_ORIENTATION, degrees, 0)
.sendToTarget();
}
});
}
@Override
public void setZoomChangeListener(final OnZoomChangeListener listener) {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(SET_ZOOM_CHANGE_LISTENER, listener).sendToTarget();
}
});
}
@Override
public void setFaceDetectionCallback(final Handler handler,
final CameraFaceDetectionCallback cb) {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(SET_FACE_DETECTION_LISTENER,
FaceDetectionCallbackForward
.getNewInstance(handler, AndroidCameraProxyImpl.this, cb))
.sendToTarget();
}
});
}
@Override
public void startFaceDetection() {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.sendEmptyMessage(START_FACE_DETECTION);
}
});
}
@Override
public void stopFaceDetection() {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.sendEmptyMessage(STOP_FACE_DETECTION);
}
});
}
@Override
public void setErrorCallback(final Handler handler, final CameraErrorCallback cb) {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(SET_ERROR_CALLBACK, ErrorCallbackForward
.getNewInstance(handler, AndroidCameraProxyImpl.this, cb)
).sendToTarget();
}
});
}
@Override
public void setParameters(final Parameters params) {
if (params == null) {
Log.v(TAG, "null parameters in setParameters()");
return;
}
final String flattenedParameters = params.flatten();
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraState.waitForStates(CAMERA_IDLE | CAMERA_UNLOCKED);
mCameraHandler.obtainMessage(SET_PARAMETERS, flattenedParameters).sendToTarget();
}
});
}
@Override
public Parameters getParameters() {
final WaitDoneBundle bundle = new WaitDoneBundle();
mDispatchThread.runJobSync(new Runnable() {
@Override
public void run() {
mCameraHandler.sendEmptyMessage(GET_PARAMETERS);
mCameraHandler.post(bundle.mUnlockRunnable);
}
}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS);
return mParameters;
}
@Override
public void refreshParameters() {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.sendEmptyMessage(REFRESH_PARAMETERS);
}
});
}
@Override
public void enableShutterSound(final boolean enable) {
mDispatchThread.runJob(new Runnable() {
@Override
public void run() {
mCameraHandler.obtainMessage(ENABLE_SHUTTER_SOUND, (enable ? 1 : 0), 0)
.sendToTarget();
}
});
}
}
private static class WaitDoneBundle {
public Runnable mUnlockRunnable;
private Object mWaitLock;
WaitDoneBundle() {
mWaitLock = new Object();
mUnlockRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWaitLock) {
mWaitLock.notifyAll();
}
}
};
}
}
/**
* A helper class to forward AutoFocusCallback to another thread.
*/
private static class AFCallbackForward implements AutoFocusCallback {
private final Handler mHandler;
private final CameraProxy mCamera;
private final CameraAFCallback mCallback;
/**
* Returns a new instance of {@link AFCallbackForward}.
*
* @param handler The handler in which the callback will be invoked in.
* @param camera The {@link CameraProxy} which the callback is from.
* @param cb The callback to be invoked.
* @return The instance of the {@link AFCallbackForward},
* or null if any parameter is null.
*/
public static AFCallbackForward getNewInstance(
Handler handler, CameraProxy camera, CameraAFCallback cb) {
if (handler == null || camera == null || cb == null) return null;
return new AFCallbackForward(handler, camera, cb);
}
private AFCallbackForward(
Handler h, CameraProxy camera, CameraAFCallback cb) {
mHandler = h;
mCamera = camera;
mCallback = cb;
}
@Override
public void onAutoFocus(final boolean b, Camera camera) {
mHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onAutoFocus(b, mCamera);
}
});
}
}
/**
* A helper class to forward ErrorCallback to another thread.
*/
private static class ErrorCallbackForward implements Camera.ErrorCallback {
private final Handler mHandler;
private final CameraProxy mCamera;
private final CameraErrorCallback mCallback;
/**
* Returns a new instance of {@link AFCallbackForward}.
*
* @param handler The handler in which the callback will be invoked in.
* @param camera The {@link CameraProxy} which the callback is from.
* @param cb The callback to be invoked.
* @return The instance of the {@link AFCallbackForward},
* or null if any parameter is null.
*/
public static ErrorCallbackForward getNewInstance(
Handler handler, CameraProxy camera, CameraErrorCallback cb) {
if (handler == null || camera == null || cb == null) return null;
return new ErrorCallbackForward(handler, camera, cb);
}
private ErrorCallbackForward(
Handler h, CameraProxy camera, CameraErrorCallback cb) {
mHandler = h;
mCamera = camera;
mCallback = cb;
}
@Override
public void onError(final int error, Camera camera) {
mHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onError(error, mCamera);
}
});
}
}
/** A helper class to forward AutoFocusMoveCallback to another thread. */
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private static class AFMoveCallbackForward implements AutoFocusMoveCallback {
private final Handler mHandler;
private final CameraAFMoveCallback mCallback;
private final CameraProxy mCamera;
/**
* Returns a new instance of {@link AFMoveCallbackForward}.
*
* @param handler The handler in which the callback will be invoked in.
* @param camera The {@link CameraProxy} which the callback is from.
* @param cb The callback to be invoked.
* @return The instance of the {@link AFMoveCallbackForward},
* or null if any parameter is null.
*/
public static AFMoveCallbackForward getNewInstance(
Handler handler, CameraProxy camera, CameraAFMoveCallback cb) {
if (handler == null || camera == null || cb == null) return null;
return new AFMoveCallbackForward(handler, camera, cb);
}
private AFMoveCallbackForward(
Handler h, CameraProxy camera, CameraAFMoveCallback cb) {
mHandler = h;
mCamera = camera;
mCallback = cb;
}
@Override
public void onAutoFocusMoving(
final boolean moving, android.hardware.Camera camera) {
mHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onAutoFocusMoving(moving, mCamera);
}
});
}
}
/**
* A helper class to forward ShutterCallback to to another thread.
*/
private static class ShutterCallbackForward implements ShutterCallback {
private final Handler mHandler;
private final CameraShutterCallback mCallback;
private final CameraProxy mCamera;
/**
* Returns a new instance of {@link ShutterCallbackForward}.
*
* @param handler The handler in which the callback will be invoked in.
* @param camera The {@link CameraProxy} which the callback is from.
* @param cb The callback to be invoked.
* @return The instance of the {@link ShutterCallbackForward},
* or null if any parameter is null.
*/
public static ShutterCallbackForward getNewInstance(
Handler handler, CameraProxy camera, CameraShutterCallback cb) {
if (handler == null || camera == null || cb == null) return null;
return new ShutterCallbackForward(handler, camera, cb);
}
private ShutterCallbackForward(
Handler h, CameraProxy camera, CameraShutterCallback cb) {
mHandler = h;
mCamera = camera;
mCallback = cb;
}
@Override
public void onShutter() {
mHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onShutter(mCamera);
}
});
}
}
/**
* A helper class to forward PictureCallback to another thread.
*/
private static class PictureCallbackForward implements PictureCallback {
private final Handler mHandler;
private final CameraPictureCallback mCallback;
private final CameraProxy mCamera;
/**
* Returns a new instance of {@link PictureCallbackForward}.
*
* @param handler The handler in which the callback will be invoked in.
* @param camera The {@link CameraProxy} which the callback is from.
* @param cb The callback to be invoked.
* @return The instance of the {@link PictureCallbackForward},
* or null if any parameters is null.
*/
public static PictureCallbackForward getNewInstance(
Handler handler, CameraProxy camera, CameraPictureCallback cb) {
if (handler == null || camera == null || cb == null) return null;
return new PictureCallbackForward(handler, camera, cb);
}
private PictureCallbackForward(
Handler h, CameraProxy camera, CameraPictureCallback cb) {
mHandler = h;
mCamera = camera;
mCallback = cb;
}
@Override
public void onPictureTaken(
final byte[] data, android.hardware.Camera camera) {
mHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onPictureTaken(data, mCamera);
}
});
}
}
/**
* A helper class to forward PreviewCallback to another thread.
*/
private static class PreviewCallbackForward implements PreviewCallback {
private final Handler mHandler;
private final CameraPreviewDataCallback mCallback;
private final CameraProxy mCamera;
/**
* Returns a new instance of {@link PreviewCallbackForward}.
*
* @param handler The handler in which the callback will be invoked in.
* @param camera The {@link CameraProxy} which the callback is from.
* @param cb The callback to be invoked.
* @return The instance of the {@link PreviewCallbackForward},
* or null if any parameters is null.
*/
public static PreviewCallbackForward getNewInstance(
Handler handler, CameraProxy camera, CameraPreviewDataCallback cb) {
if (handler == null || camera == null || cb == null) return null;
return new PreviewCallbackForward(handler, camera, cb);
}
private PreviewCallbackForward(
Handler h, CameraProxy camera, CameraPreviewDataCallback cb) {
mHandler = h;
mCamera = camera;
mCallback = cb;
}
@Override
public void onPreviewFrame(
final byte[] data, android.hardware.Camera camera) {
mHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onPreviewFrame(data, mCamera);
}
});
}
}
private static class FaceDetectionCallbackForward implements FaceDetectionListener {
private final Handler mHandler;
private final CameraFaceDetectionCallback mCallback;
private final CameraProxy mCamera;
/**
* Returns a new instance of {@link FaceDetectionCallbackForward}.
*
* @param handler The handler in which the callback will be invoked in.
* @param camera The {@link CameraProxy} which the callback is from.
* @param cb The callback to be invoked.
* @return The instance of the {@link FaceDetectionCallbackForward},
* or null if any parameter is null.
*/
public static FaceDetectionCallbackForward getNewInstance(
Handler handler, CameraProxy camera, CameraFaceDetectionCallback cb) {
if (handler == null || camera == null || cb == null) return null;
return new FaceDetectionCallbackForward(handler, camera, cb);
}
private FaceDetectionCallbackForward(
Handler h, CameraProxy camera, CameraFaceDetectionCallback cb) {
mHandler = h;
mCamera = camera;
mCallback = cb;
}
@Override
public void onFaceDetection(
final Camera.Face[] faces, Camera camera) {
mHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onFaceDetection(faces, mCamera);
}
});
}
}
/**
* A callback helps to invoke the original callback on another
* {@link android.os.Handler}.
*/
private static class CameraOpenCallbackForward implements CameraOpenCallback {
private final Handler mHandler;
private final CameraOpenCallback mCallback;
/**
* Returns a new instance of {@link FaceDetectionCallbackForward}.
*
* @param handler The handler in which the callback will be invoked in.
* @param cb The callback to be invoked.
* @return The instance of the {@link FaceDetectionCallbackForward}, or
* null if any parameter is null.
*/
public static CameraOpenCallbackForward getNewInstance(
Handler handler, CameraOpenCallback cb) {
if (handler == null || cb == null) {
return null;
}
return new CameraOpenCallbackForward(handler, cb);
}
private CameraOpenCallbackForward(Handler h, CameraOpenCallback cb) {
// Given that we are using the main thread handler, we can create it
// here instead of holding onto the PhotoModule objects. In this
// way, we can avoid memory leak.
mHandler = new Handler(Looper.getMainLooper());
mCallback = cb;
}
@Override
public void onCameraOpened(final CameraProxy camera) {
mHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onCameraOpened(camera);
}
});
}
@Override
public void onCameraDisabled(final int cameraId) {
mHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onCameraDisabled(cameraId);
}
});
}
@Override
public void onDeviceOpenFailure(final int cameraId) {
mHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onDeviceOpenFailure(cameraId);
}
});
}
@Override
public void onDeviceOpenedAlready(final int cameraId) {
mHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onDeviceOpenedAlready(cameraId);
}
});
}
@Override
public void onReconnectionFailure(final CameraManager mgr) {
mHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onReconnectionFailure(mgr);
}
});
}
}
private static class CameraStartPreviewCallbackForward
implements CameraStartPreviewCallback {
private final Handler mHandler;
private final CameraStartPreviewCallback mCallback;
public static CameraStartPreviewCallbackForward getNewInstance(
Handler handler, CameraStartPreviewCallback cb) {
if (handler == null || cb == null) {
return null;
}
return new CameraStartPreviewCallbackForward(handler, cb);
}
private CameraStartPreviewCallbackForward(Handler h,
CameraStartPreviewCallback cb) {
mHandler = h;
mCallback = cb;
}
@Override
public void onPreviewStarted() {
mHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onPreviewStarted();
}
});
}
}
}