/* * 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; import static com.android.camera.util.CameraUtil.Assert; import java.io.IOException; 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.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.util.Log; import android.view.SurfaceHolder; import com.android.camera.util.ApiHelper; /** * A class to implement {@link CameraManager} of the Android camera framework. */ class AndroidCameraManagerImpl implements CameraManager { private static final String TAG = "CAM_" + AndroidCameraManagerImpl.class.getSimpleName(); private Parameters mParameters; private boolean mParametersIsDirty; private IOException mReconnectIOException; /* 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; // 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; private CameraHandler mCameraHandler; private android.hardware.Camera mCamera; // Used to retain a copy of Parameters for setting parameters. private Parameters mParamsToSet; AndroidCameraManagerImpl() { HandlerThread ht = new HandlerThread("Camera Handler Thread"); ht.start(); mCameraHandler = new CameraHandler(ht.getLooper()); } private class CameraHandler extends Handler { CameraHandler(Looper looper) { super(looper); } @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) private void startFaceDetection() { mCamera.startFaceDetection(); } @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) private void stopFaceDetection() { mCamera.stopFaceDetection(); } @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) private void setFaceDetectionListener(FaceDetectionListener listener) { mCamera.setFaceDetectionListener(listener); } @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) private void setPreviewTexture(Object surfaceTexture) { try { mCamera.setPreviewTexture((SurfaceTexture) surfaceTexture); } catch (IOException e) { throw new RuntimeException(e); } } @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN_MR1) private void enableShutterSound(boolean enable) { mCamera.enableShutterSound(enable); } @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN) private void setAutoFocusMoveCallback( android.hardware.Camera camera, Object cb) { camera.setAutoFocusMoveCallback((AutoFocusMoveCallback) cb); } public void requestTakePicture( final ShutterCallback shutter, final PictureCallback raw, final PictureCallback postView, final PictureCallback jpeg) { post(new Runnable() { @Override public void run() { try { mCamera.takePicture(shutter, raw, postView, jpeg); } catch (RuntimeException e) { // TODO: output camera state and focus state for debugging. Log.e(TAG, "take picture failed."); throw e; } } }); } /** * Waits for all the {@code Message} and {@code Runnable} currently in the queue * are processed. * * @return {@code false} if the wait was interrupted, {@code true} otherwise. */ public boolean waitDone() { final Object waitDoneLock = new Object(); final Runnable unlockRunnable = new Runnable() { @Override public void run() { synchronized (waitDoneLock) { waitDoneLock.notifyAll(); } } }; synchronized (waitDoneLock) { mCameraHandler.post(unlockRunnable); try { waitDoneLock.wait(); } catch (InterruptedException ex) { Log.v(TAG, "waitDone interrupted"); return false; } } return true; } /** * 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: mCamera = android.hardware.Camera.open(msg.arg1); if (mCamera != null) { mParametersIsDirty = true; // Get a instance of Camera.Parameters for later use. if (mParamsToSet == null) { mParamsToSet = mCamera.getParameters(); } } return; case RELEASE: mCamera.release(); mCamera = null; return; case RECONNECT: mReconnectIOException = null; try { mCamera.reconnect(); } catch (IOException ex) { mReconnectIOException = ex; } return; case UNLOCK: mCamera.unlock(); return; case LOCK: mCamera.lock(); return; case SET_PREVIEW_TEXTURE_ASYNC: setPreviewTexture(msg.obj); return; case SET_PREVIEW_DISPLAY_ASYNC: try { mCamera.setPreviewDisplay((SurfaceHolder) msg.obj); } catch (IOException e) { throw new RuntimeException(e); } return; case START_PREVIEW_ASYNC: mCamera.startPreview(); return; case STOP_PREVIEW: mCamera.stopPreview(); return; case SET_PREVIEW_CALLBACK_WITH_BUFFER: mCamera.setPreviewCallbackWithBuffer( (PreviewCallback) msg.obj); return; case ADD_CALLBACK_BUFFER: mCamera.addCallbackBuffer((byte[]) msg.obj); return; case AUTO_FOCUS: mCamera.autoFocus((AutoFocusCallback) msg.obj); return; case CANCEL_AUTO_FOCUS: mCamera.cancelAutoFocus(); return; case SET_AUTO_FOCUS_MOVE_CALLBACK: setAutoFocusMoveCallback(mCamera, msg.obj); return; case SET_DISPLAY_ORIENTATION: mCamera.setDisplayOrientation(msg.arg1); return; case SET_ZOOM_CHANGE_LISTENER: mCamera.setZoomChangeListener( (OnZoomChangeListener) msg.obj); return; case SET_FACE_DETECTION_LISTENER: setFaceDetectionListener((FaceDetectionListener) msg.obj); return; case START_FACE_DETECTION: startFaceDetection(); return; case STOP_FACE_DETECTION: stopFaceDetection(); return; case SET_ERROR_CALLBACK: mCamera.setErrorCallback((ErrorCallback) msg.obj); return; case SET_PARAMETERS: mParametersIsDirty = true; mParamsToSet.unflatten((String) msg.obj); mCamera.setParameters(mParamsToSet); return; case GET_PARAMETERS: if (mParametersIsDirty) { mParameters = mCamera.getParameters(); mParametersIsDirty = false; } return; case SET_PREVIEW_CALLBACK: mCamera.setPreviewCallback((PreviewCallback) msg.obj); return; case ENABLE_SHUTTER_SOUND: enableShutterSound((msg.arg1 == 1) ? true : false); return; case REFRESH_PARAMETERS: mParametersIsDirty = true; return; default: throw new RuntimeException("Invalid CameraProxy message=" + msg.what); } } catch (RuntimeException e) { if (msg.what != RELEASE && mCamera != null) { try { mCamera.release(); } catch (Exception ex) { Log.e(TAG, "Fail to release the camera."); } mCamera = null; } throw e; } } } @Override public CameraManager.CameraProxy cameraOpen(int cameraId) { mCameraHandler.obtainMessage(OPEN_CAMERA, cameraId, 0).sendToTarget(); mCameraHandler.waitDone(); if (mCamera != null) { return new AndroidCameraProxyImpl(); } else { return null; } } /** * A class which implements {@link CameraManager.CameraProxy} and * camera handler thread. */ public class AndroidCameraProxyImpl implements CameraManager.CameraProxy { private AndroidCameraProxyImpl() { Assert(mCamera != null); } @Override public android.hardware.Camera getCamera() { return mCamera; } @Override public void release() { // release() must be synchronous so we know exactly when the camera // is released and can continue on. mCameraHandler.sendEmptyMessage(RELEASE); mCameraHandler.waitDone(); } @Override public void reconnect() throws IOException { mCameraHandler.sendEmptyMessage(RECONNECT); mCameraHandler.waitDone(); if (mReconnectIOException != null) { throw mReconnectIOException; } } @Override public void unlock() { mCameraHandler.sendEmptyMessage(UNLOCK); mCameraHandler.waitDone(); } @Override public void lock() { mCameraHandler.sendEmptyMessage(LOCK); } @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) @Override public void setPreviewTexture(SurfaceTexture surfaceTexture) { mCameraHandler.obtainMessage(SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture).sendToTarget(); } @Override public void setPreviewDisplay(SurfaceHolder surfaceHolder) { mCameraHandler.obtainMessage(SET_PREVIEW_DISPLAY_ASYNC, surfaceHolder).sendToTarget(); } @Override public void startPreview() { mCameraHandler.sendEmptyMessage(START_PREVIEW_ASYNC); } @Override public void stopPreview() { mCameraHandler.sendEmptyMessage(STOP_PREVIEW); mCameraHandler.waitDone(); } @Override public void setPreviewDataCallback( Handler handler, CameraPreviewDataCallback cb) { mCameraHandler.obtainMessage( SET_PREVIEW_CALLBACK, PreviewCallbackForward.getNewInstance(handler, this, cb)).sendToTarget(); } @Override public void setPreviewDataCallbackWithBuffer( Handler handler, CameraPreviewDataCallback cb) { mCameraHandler.obtainMessage( SET_PREVIEW_CALLBACK_WITH_BUFFER, PreviewCallbackForward.getNewInstance(handler, this, cb)).sendToTarget(); } @Override public void addCallbackBuffer(byte[] callbackBuffer) { mCameraHandler.obtainMessage(ADD_CALLBACK_BUFFER, callbackBuffer).sendToTarget(); } @Override public void autoFocus(Handler handler, CameraAFCallback cb) { mCameraHandler.obtainMessage( AUTO_FOCUS, AFCallbackForward.getNewInstance(handler, this, cb)).sendToTarget(); } @Override public void cancelAutoFocus() { mCameraHandler.removeMessages(AUTO_FOCUS); mCameraHandler.sendEmptyMessage(CANCEL_AUTO_FOCUS); } @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN) @Override public void setAutoFocusMoveCallback( Handler handler, CameraAFMoveCallback cb) { mCameraHandler.obtainMessage( SET_AUTO_FOCUS_MOVE_CALLBACK, AFMoveCallbackForward.getNewInstance(handler, this, cb)).sendToTarget(); } @Override public void takePicture( Handler handler, CameraShutterCallback shutter, CameraPictureCallback raw, CameraPictureCallback post, CameraPictureCallback jpeg) { mCameraHandler.requestTakePicture( ShutterCallbackForward.getNewInstance(handler, this, shutter), PictureCallbackForward.getNewInstance(handler, this, raw), PictureCallbackForward.getNewInstance(handler, this, post), PictureCallbackForward.getNewInstance(handler, this, jpeg)); } @Override public void setDisplayOrientation(int degrees) { mCameraHandler.obtainMessage(SET_DISPLAY_ORIENTATION, degrees, 0) .sendToTarget(); } @Override public void setZoomChangeListener(OnZoomChangeListener listener) { mCameraHandler.obtainMessage(SET_ZOOM_CHANGE_LISTENER, listener).sendToTarget(); } @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) public void setFaceDetectionCallback( Handler handler, CameraFaceDetectionCallback cb) { mCameraHandler.obtainMessage( SET_FACE_DETECTION_LISTENER, FaceDetectionCallbackForward.getNewInstance(handler, this, cb)).sendToTarget(); } @Override public void startFaceDetection() { mCameraHandler.sendEmptyMessage(START_FACE_DETECTION); } @Override public void stopFaceDetection() { mCameraHandler.sendEmptyMessage(STOP_FACE_DETECTION); } @Override public void setErrorCallback(ErrorCallback cb) { mCameraHandler.obtainMessage(SET_ERROR_CALLBACK, cb).sendToTarget(); } @Override public void setParameters(Parameters params) { if (params == null) { Log.v(TAG, "null parameters in setParameters()"); return; } mCameraHandler.obtainMessage(SET_PARAMETERS, params.flatten()) .sendToTarget(); } @Override public Parameters getParameters() { mCameraHandler.sendEmptyMessage(GET_PARAMETERS); mCameraHandler.waitDone(); return mParameters; } @Override public void refreshParameters() { mCameraHandler.sendEmptyMessage(REFRESH_PARAMETERS); } @Override public void enableShutterSound(boolean enable) { mCameraHandler.obtainMessage( ENABLE_SHUTTER_SOUND, (enable ? 1 : 0), 0).sendToTarget(); } } /** * 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 AutoFocusMoveCallback to another thread. */ @TargetApi(ApiHelper.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); } }); } } }