diff options
Diffstat (limited to 'src/com/android/camera/cameradevice/AndroidCameraManagerImpl.java')
-rw-r--r-- | src/com/android/camera/cameradevice/AndroidCameraManagerImpl.java | 1531 |
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(); + } + }); + } + } +} |