summaryrefslogtreecommitdiffstats
path: root/src/com/android/camera/cameradevice/AndroidCameraManagerImpl.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/camera/cameradevice/AndroidCameraManagerImpl.java')
-rw-r--r--src/com/android/camera/cameradevice/AndroidCameraManagerImpl.java1531
1 files changed, 1531 insertions, 0 deletions
diff --git a/src/com/android/camera/cameradevice/AndroidCameraManagerImpl.java b/src/com/android/camera/cameradevice/AndroidCameraManagerImpl.java
new file mode 100644
index 000000000..cb6822b82
--- /dev/null
+++ b/src/com/android/camera/cameradevice/AndroidCameraManagerImpl.java
@@ -0,0 +1,1531 @@
+/*
+ * 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.cameradevice;
+
+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) {
+ try {
+ camera.setAutoFocusMoveCallback((AutoFocusMoveCallback) cb);
+ } catch (RuntimeException ex) {
+ Log.w(TAG, ex.getMessage());
+ }
+ }
+
+ 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 final 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.
+ * @param jobMsg The message to log when the job runs timeout.
+ * @return Whether the job finishes before timeout.
+ */
+ public boolean runJobSync(final Runnable job, Object waitLock, long timeoutMs, String jobMsg) {
+ 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.w(TAG, "Timeout waiting camera operation:" + jobMsg);
+ }
+ 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, "camera release");
+ } 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, "camera unlock");
+ }
+
+ @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, "set preview texture");
+ }
+
+ @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, "stop preview");
+ }
+
+ @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, "get parameters");
+ 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 final 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();
+ }
+ });
+ }
+ }
+}