| /* |
| * 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.ex.camera2.portability; |
| |
| 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.view.SurfaceHolder; |
| |
| import com.android.ex.camera2.portability.debug.Log; |
| |
| import java.io.IOException; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.StringTokenizer; |
| |
| /** |
| * A class to implement {@link CameraAgent} of the Android camera framework. |
| */ |
| class AndroidCameraAgentImpl extends CameraAgent { |
| private static final Log.Tag TAG = new Log.Tag("AndCamAgntImp"); |
| |
| private CameraDeviceInfo.Characteristics mCharacteristics; |
| private AndroidCameraCapabilities mCapabilities; |
| |
| private final CameraHandler mCameraHandler; |
| private final HandlerThread mCameraHandlerThread; |
| private final CameraStateHolder mCameraState; |
| private final DispatchThread mDispatchThread; |
| |
| private static final CameraExceptionHandler sDefaultExceptionHandler = |
| new CameraExceptionHandler(null) { |
| @Override |
| public void onCameraError(int errorCode) { |
| Log.w(TAG, "onCameraError called with no handler set: " + errorCode); |
| } |
| |
| @Override |
| public void onCameraException(RuntimeException ex, String commandHistory, int action, |
| int state) { |
| Log.w(TAG, "onCameraException called with no handler set", ex); |
| } |
| |
| @Override |
| public void onDispatchThreadException(RuntimeException ex) { |
| Log.w(TAG, "onDispatchThreadException called with no handler set", ex); |
| } |
| }; |
| |
| private CameraExceptionHandler mExceptionHandler = sDefaultExceptionHandler; |
| |
| AndroidCameraAgentImpl() { |
| mCameraHandlerThread = new HandlerThread("Camera Handler Thread"); |
| mCameraHandlerThread.start(); |
| mCameraHandler = new CameraHandler(this, mCameraHandlerThread.getLooper()); |
| mExceptionHandler = new CameraExceptionHandler(mCameraHandler); |
| mCameraState = new AndroidCameraStateHolder(); |
| mDispatchThread = new DispatchThread(mCameraHandler, mCameraHandlerThread); |
| mDispatchThread.start(); |
| } |
| |
| @Override |
| public void recycle() { |
| closeCamera(null, true); |
| mDispatchThread.end(); |
| mCameraState.invalidate(); |
| } |
| |
| @Override |
| public CameraDeviceInfo getCameraDeviceInfo() { |
| return AndroidCameraDeviceInfo.create(); |
| } |
| |
| @Override |
| protected Handler getCameraHandler() { |
| return mCameraHandler; |
| } |
| |
| @Override |
| protected DispatchThread getDispatchThread() { |
| return mDispatchThread; |
| } |
| |
| @Override |
| protected CameraStateHolder getCameraState() { |
| return mCameraState; |
| } |
| |
| @Override |
| protected CameraExceptionHandler getCameraExceptionHandler() { |
| return mExceptionHandler; |
| } |
| |
| @Override |
| public void setCameraExceptionHandler(CameraExceptionHandler exceptionHandler) { |
| // In case of null set the default handler to route exceptions to logs |
| mExceptionHandler = exceptionHandler != null ? exceptionHandler : sDefaultExceptionHandler; |
| } |
| |
| private static class AndroidCameraDeviceInfo implements CameraDeviceInfo { |
| private final Camera.CameraInfo[] mCameraInfos; |
| private final int mNumberOfCameras; |
| private final int mFirstBackCameraId; |
| private final int mFirstFrontCameraId; |
| |
| private AndroidCameraDeviceInfo(Camera.CameraInfo[] info, int numberOfCameras, |
| int firstBackCameraId, int firstFrontCameraId) { |
| |
| mCameraInfos = info; |
| mNumberOfCameras = numberOfCameras; |
| mFirstBackCameraId = firstBackCameraId; |
| mFirstFrontCameraId = firstFrontCameraId; |
| } |
| |
| public static AndroidCameraDeviceInfo create() { |
| int numberOfCameras; |
| Camera.CameraInfo[] cameraInfos; |
| try { |
| numberOfCameras = Camera.getNumberOfCameras(); |
| cameraInfos = new Camera.CameraInfo[numberOfCameras]; |
| for (int i = 0; i < numberOfCameras; i++) { |
| cameraInfos[i] = new Camera.CameraInfo(); |
| Camera.getCameraInfo(i, cameraInfos[i]); |
| } |
| } catch (RuntimeException ex) { |
| Log.e(TAG, "Exception while creating CameraDeviceInfo", ex); |
| return null; |
| } |
| |
| int firstFront = NO_DEVICE; |
| int firstBack = NO_DEVICE; |
| // Get the first (smallest) back and first front camera id. |
| for (int i = numberOfCameras - 1; i >= 0; i--) { |
| if (cameraInfos[i].facing == Camera.CameraInfo.CAMERA_FACING_BACK) { |
| firstBack = i; |
| } else { |
| if (cameraInfos[i].facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { |
| firstFront = i; |
| } |
| } |
| } |
| |
| return new AndroidCameraDeviceInfo(cameraInfos, numberOfCameras, firstBack, firstFront); |
| } |
| |
| @Override |
| public Characteristics getCharacteristics(int cameraId) { |
| Camera.CameraInfo info = mCameraInfos[cameraId]; |
| if (info != null) { |
| return new AndroidCharacteristics(info); |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| public int getNumberOfCameras() { |
| return mNumberOfCameras; |
| } |
| |
| @Override |
| public int getFirstBackCameraId() { |
| return mFirstBackCameraId; |
| } |
| |
| @Override |
| public int getFirstFrontCameraId() { |
| return mFirstFrontCameraId; |
| } |
| |
| private static class AndroidCharacteristics extends Characteristics { |
| private Camera.CameraInfo mCameraInfo; |
| |
| AndroidCharacteristics(Camera.CameraInfo cameraInfo) { |
| mCameraInfo = cameraInfo; |
| } |
| |
| @Override |
| public boolean isFacingBack() { |
| return mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK; |
| } |
| |
| @Override |
| public boolean isFacingFront() { |
| return mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT; |
| } |
| |
| @Override |
| public int getSensorOrientation() { |
| return mCameraInfo.orientation; |
| } |
| |
| @Override |
| public boolean canDisableShutterSound() { |
| return mCameraInfo.canDisableShutterSound; |
| } |
| } |
| } |
| |
| private static class ParametersCache { |
| private Parameters mParameters; |
| private Camera mCamera; |
| |
| public ParametersCache(Camera camera) { |
| mCamera = camera; |
| } |
| |
| public synchronized void invalidate() { |
| mParameters = null; |
| } |
| |
| /** |
| * Access parameters from the cache. If cache is empty, block by |
| * retrieving parameters directly from Camera, but if cache is present, |
| * returns immediately. |
| */ |
| public synchronized Parameters getBlocking() { |
| if (mParameters == null) { |
| mParameters = mCamera.getParameters(); |
| if (mParameters == null) { |
| Log.e(TAG, "Camera object returned null parameters!"); |
| throw new IllegalStateException("camera.getParameters returned null"); |
| } |
| } |
| return mParameters; |
| } |
| } |
| |
| /** |
| * The handler on which the actual camera operations happen. |
| */ |
| private class CameraHandler extends HistoryHandler implements Camera.ErrorCallback { |
| private CameraAgent mAgent; |
| private Camera mCamera; |
| private int mCameraId = -1; |
| private ParametersCache mParameterCache; |
| private int mCancelAfPending = 0; |
| |
| 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(CameraAgent agent, Looper looper) { |
| super(looper); |
| mAgent = agent; |
| } |
| |
| 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()); |
| } |
| } |
| |
| 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(CameraActions.CAPTURE_PHOTO, callbacks).sendToTarget(); |
| } |
| |
| @Override |
| public void onError(final int errorCode, Camera camera) { |
| mExceptionHandler.onCameraError(errorCode); |
| if (errorCode == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) { |
| int lastCameraAction = getCurrentMessage(); |
| mExceptionHandler.onCameraException( |
| new RuntimeException("Media server died."), |
| generateHistoryString(mCameraId), |
| lastCameraAction, |
| mCameraState.getState()); |
| } |
| } |
| |
| /** |
| * 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) { |
| super.handleMessage(msg); |
| |
| if (getCameraState().isInvalid()) { |
| Log.v(TAG, "Skip handleMessage - action = '" + CameraActions.stringify(msg.what) + "'"); |
| return; |
| } |
| Log.v(TAG, "handleMessage - action = '" + CameraActions.stringify(msg.what) + "'"); |
| |
| int cameraAction = msg.what; |
| try { |
| switch (cameraAction) { |
| case CameraActions.OPEN_CAMERA: { |
| final CameraOpenCallback openCallback = (CameraOpenCallback) msg.obj; |
| final int cameraId = msg.arg1; |
| if (mCameraState.getState() != AndroidCameraStateHolder.CAMERA_UNOPENED) { |
| openCallback.onDeviceOpenedAlready(cameraId, generateHistoryString(cameraId)); |
| break; |
| } |
| |
| Log.i(TAG, "Opening camera " + cameraId + " with camera1 API"); |
| mCamera = android.hardware.Camera.open(cameraId); |
| if (mCamera != null) { |
| mCameraId = cameraId; |
| mParameterCache = new ParametersCache(mCamera); |
| |
| mCharacteristics = |
| AndroidCameraDeviceInfo.create().getCharacteristics(cameraId); |
| mCapabilities = new AndroidCameraCapabilities( |
| mParameterCache.getBlocking()); |
| |
| mCamera.setErrorCallback(this); |
| |
| mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE); |
| if (openCallback != null) { |
| CameraProxy cameraProxy = new AndroidCameraProxyImpl( |
| mAgent, cameraId, mCamera, mCharacteristics, mCapabilities); |
| openCallback.onCameraOpened(cameraProxy); |
| } |
| } else { |
| if (openCallback != null) { |
| openCallback.onDeviceOpenFailure(cameraId, generateHistoryString(cameraId)); |
| } |
| } |
| break; |
| } |
| |
| case CameraActions.RELEASE: { |
| if (mCamera != null) { |
| mCamera.release(); |
| mCameraState.setState(AndroidCameraStateHolder.CAMERA_UNOPENED); |
| mCamera = null; |
| mCameraId = -1; |
| } else { |
| Log.w(TAG, "Releasing camera without any camera opened."); |
| } |
| break; |
| } |
| |
| case CameraActions.RECONNECT: { |
| final CameraOpenCallbackForward cbForward = |
| (CameraOpenCallbackForward) msg.obj; |
| final int cameraId = msg.arg1; |
| try { |
| mCamera.reconnect(); |
| } catch (IOException ex) { |
| if (cbForward != null) { |
| cbForward.onReconnectionFailure(mAgent, generateHistoryString(mCameraId)); |
| } |
| break; |
| } |
| |
| mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE); |
| if (cbForward != null) { |
| cbForward.onCameraOpened( |
| new AndroidCameraProxyImpl(AndroidCameraAgentImpl.this, |
| cameraId, mCamera, mCharacteristics, mCapabilities)); |
| } |
| break; |
| } |
| |
| case CameraActions.UNLOCK: { |
| mCamera.unlock(); |
| mCameraState.setState(AndroidCameraStateHolder.CAMERA_UNLOCKED); |
| break; |
| } |
| |
| case CameraActions.LOCK: { |
| mCamera.lock(); |
| mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE); |
| break; |
| } |
| |
| // TODO: Lock the CameraSettings object's sizes |
| case CameraActions.SET_PREVIEW_TEXTURE_ASYNC: { |
| setPreviewTexture(msg.obj); |
| break; |
| } |
| |
| case CameraActions.SET_PREVIEW_DISPLAY_ASYNC: { |
| try { |
| mCamera.setPreviewDisplay((SurfaceHolder) msg.obj); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| break; |
| } |
| |
| case CameraActions.START_PREVIEW_ASYNC: { |
| final CameraStartPreviewCallbackForward cbForward = |
| (CameraStartPreviewCallbackForward) msg.obj; |
| mCamera.startPreview(); |
| if (cbForward != null) { |
| cbForward.onPreviewStarted(); |
| } |
| break; |
| } |
| |
| // TODO: Unlock the CameraSettings object's sizes |
| case CameraActions.STOP_PREVIEW: { |
| mCamera.stopPreview(); |
| break; |
| } |
| |
| case CameraActions.SET_PREVIEW_CALLBACK_WITH_BUFFER: { |
| mCamera.setPreviewCallbackWithBuffer((PreviewCallback) msg.obj); |
| break; |
| } |
| |
| case CameraActions.SET_ONE_SHOT_PREVIEW_CALLBACK: { |
| mCamera.setOneShotPreviewCallback((PreviewCallback) msg.obj); |
| break; |
| } |
| |
| case CameraActions.ADD_CALLBACK_BUFFER: { |
| mCamera.addCallbackBuffer((byte[]) msg.obj); |
| break; |
| } |
| |
| case CameraActions.AUTO_FOCUS: { |
| if (mCancelAfPending > 0) { |
| Log.v(TAG, "handleMessage - Ignored AUTO_FOCUS because there was " |
| + mCancelAfPending + " pending CANCEL_AUTO_FOCUS messages"); |
| break; // ignore AF because a CANCEL_AF is queued after this |
| } |
| mCameraState.setState(AndroidCameraStateHolder.CAMERA_FOCUSING); |
| mCamera.autoFocus((AutoFocusCallback) msg.obj); |
| break; |
| } |
| |
| case CameraActions.CANCEL_AUTO_FOCUS: { |
| // Ignore all AFs that were already queued until we see |
| // a CANCEL_AUTO_FOCUS_FINISH |
| mCancelAfPending++; |
| mCamera.cancelAutoFocus(); |
| mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE); |
| break; |
| } |
| |
| case CameraActions.CANCEL_AUTO_FOCUS_FINISH: { |
| // Stop ignoring AUTO_FOCUS messages unless there are additional |
| // CANCEL_AUTO_FOCUSes that were added |
| mCancelAfPending--; |
| break; |
| } |
| |
| case CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK: { |
| setAutoFocusMoveCallback(mCamera, msg.obj); |
| break; |
| } |
| |
| case CameraActions.SET_DISPLAY_ORIENTATION: { |
| // Update preview orientation |
| mCamera.setDisplayOrientation( |
| mCharacteristics.getPreviewOrientation(msg.arg1)); |
| // Only set the JPEG capture orientation if requested to do so; otherwise, |
| // capture in the sensor's physical orientation. (e.g., JPEG rotation is |
| // necessary in auto-rotate mode. |
| Parameters parameters = mParameterCache.getBlocking(); |
| parameters.setRotation( |
| msg.arg2 > 0 ? mCharacteristics.getJpegOrientation(msg.arg1) : 0); |
| mCamera.setParameters(parameters); |
| mParameterCache.invalidate(); |
| break; |
| } |
| |
| case CameraActions.SET_JPEG_ORIENTATION: { |
| Parameters parameters = mParameterCache.getBlocking(); |
| parameters.setRotation(msg.arg1); |
| mCamera.setParameters(parameters); |
| mParameterCache.invalidate(); |
| break; |
| } |
| |
| case CameraActions.SET_ZOOM_CHANGE_LISTENER: { |
| mCamera.setZoomChangeListener((OnZoomChangeListener) msg.obj); |
| break; |
| } |
| |
| case CameraActions.SET_FACE_DETECTION_LISTENER: { |
| setFaceDetectionListener((FaceDetectionListener) msg.obj); |
| break; |
| } |
| |
| case CameraActions.START_FACE_DETECTION: { |
| startFaceDetection(); |
| break; |
| } |
| |
| case CameraActions.STOP_FACE_DETECTION: { |
| stopFaceDetection(); |
| break; |
| } |
| |
| case CameraActions.APPLY_SETTINGS: { |
| Parameters parameters = mParameterCache.getBlocking(); |
| CameraSettings settings = (CameraSettings) msg.obj; |
| applySettingsToParameters(settings, parameters); |
| mCamera.setParameters(parameters); |
| mParameterCache.invalidate(); |
| break; |
| } |
| |
| case CameraActions.SET_PARAMETERS: { |
| Parameters parameters = mParameterCache.getBlocking(); |
| parameters.unflatten((String) msg.obj); |
| mCamera.setParameters(parameters); |
| mParameterCache.invalidate(); |
| break; |
| } |
| |
| case CameraActions.GET_PARAMETERS: { |
| Parameters[] parametersHolder = (Parameters[]) msg.obj; |
| Parameters parameters = mParameterCache.getBlocking(); |
| parametersHolder[0] = parameters; |
| break; |
| } |
| |
| case CameraActions.SET_PREVIEW_CALLBACK: { |
| mCamera.setPreviewCallback((PreviewCallback) msg.obj); |
| break; |
| } |
| |
| case CameraActions.ENABLE_SHUTTER_SOUND: { |
| enableShutterSound((msg.arg1 == 1) ? true : false); |
| break; |
| } |
| |
| case CameraActions.REFRESH_PARAMETERS: { |
| mParameterCache.invalidate();; |
| break; |
| } |
| |
| case CameraActions.CAPTURE_PHOTO: { |
| mCameraState.setState(AndroidCameraStateHolder.CAMERA_CAPTURING); |
| CaptureCallbacks captureCallbacks = (CaptureCallbacks) msg.obj; |
| mCamera.takePicture( |
| captureCallbacks.mShutter, |
| captureCallbacks.mRaw, |
| captureCallbacks.mPostView, |
| captureCallbacks.mJpeg); |
| break; |
| } |
| |
| default: { |
| Log.e(TAG, "Invalid CameraProxy message=" + msg.what); |
| } |
| } |
| } catch (final RuntimeException ex) { |
| int cameraState = mCameraState.getState(); |
| String errorContext = "CameraAction[" + CameraActions.stringify(cameraAction) + |
| "] at CameraState[" + cameraState + "]"; |
| Log.e(TAG, "RuntimeException during " + errorContext, ex); |
| |
| if (mCamera != null) { |
| Log.i(TAG, "Release camera since mCamera is not null."); |
| try { |
| mCamera.release(); |
| } catch (Exception e) { |
| Log.e(TAG, "Fail when calling Camera.release().", e); |
| } finally { |
| mCamera = null; |
| } |
| } |
| |
| // Invoke error callback. |
| if (msg.what == CameraActions.OPEN_CAMERA && mCamera == null) { |
| final int cameraId = msg.arg1; |
| if (msg.obj != null) { |
| ((CameraOpenCallback) msg.obj).onDeviceOpenFailure( |
| msg.arg1, generateHistoryString(cameraId)); |
| } |
| } else { |
| CameraExceptionHandler exceptionHandler = mAgent.getCameraExceptionHandler(); |
| exceptionHandler.onCameraException( |
| ex, generateHistoryString(mCameraId), cameraAction, cameraState); |
| } |
| } finally { |
| WaitDoneBundle.unblockSyncWaiters(msg); |
| } |
| } |
| |
| private void applySettingsToParameters(final CameraSettings settings, |
| final Parameters parameters) { |
| final CameraCapabilities.Stringifier stringifier = mCapabilities.getStringifier(); |
| Size photoSize = settings.getCurrentPhotoSize(); |
| parameters.setPictureSize(photoSize.width(), photoSize.height()); |
| Size previewSize = settings.getCurrentPreviewSize(); |
| parameters.setPreviewSize(previewSize.width(), previewSize.height()); |
| if (settings.getPreviewFrameRate() == -1) { |
| parameters.setPreviewFpsRange(settings.getPreviewFpsRangeMin(), |
| settings.getPreviewFpsRangeMax()); |
| } else { |
| parameters.setPreviewFrameRate(settings.getPreviewFrameRate()); |
| } |
| parameters.setPreviewFormat(settings.getCurrentPreviewFormat()); |
| parameters.setJpegQuality(settings.getPhotoJpegCompressionQuality()); |
| if (mCapabilities.supports(CameraCapabilities.Feature.ZOOM)) { |
| parameters.setZoom(zoomRatioToIndex(settings.getCurrentZoomRatio(), |
| parameters.getZoomRatios())); |
| } |
| parameters.setExposureCompensation(settings.getExposureCompensationIndex()); |
| if (mCapabilities.supports(CameraCapabilities.Feature.AUTO_EXPOSURE_LOCK)) { |
| parameters.setAutoExposureLock(settings.isAutoExposureLocked()); |
| } |
| parameters.setFocusMode(stringifier.stringify(settings.getCurrentFocusMode())); |
| if (mCapabilities.supports(CameraCapabilities.Feature.AUTO_WHITE_BALANCE_LOCK)) { |
| parameters.setAutoWhiteBalanceLock(settings.isAutoWhiteBalanceLocked()); |
| } |
| if (settings.getWhiteBalance() != null) { |
| parameters.setWhiteBalance(stringifier.stringify(settings.getWhiteBalance())); |
| } |
| if (mCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA)) { |
| if (settings.getFocusAreas().size() != 0) { |
| parameters.setFocusAreas(settings.getFocusAreas()); |
| } else { |
| parameters.setFocusAreas(null); |
| } |
| } |
| if (mCapabilities.supports(CameraCapabilities.Feature.METERING_AREA)) { |
| if (settings.getMeteringAreas().size() != 0) { |
| parameters.setMeteringAreas(settings.getMeteringAreas()); |
| } else { |
| parameters.setMeteringAreas(null); |
| } |
| } |
| if (settings.getCurrentFlashMode() != CameraCapabilities.FlashMode.NO_FLASH) { |
| parameters.setFlashMode(stringifier.stringify(settings.getCurrentFlashMode())); |
| } |
| if (settings.getCurrentSceneMode() != CameraCapabilities.SceneMode.NO_SCENE_MODE) { |
| if (settings.getCurrentSceneMode() != null) { |
| parameters |
| .setSceneMode(stringifier.stringify(settings.getCurrentSceneMode())); |
| } |
| } |
| parameters.setRecordingHint(settings.isRecordingHintEnabled()); |
| Size jpegThumbSize = settings.getExifThumbnailSize(); |
| if (jpegThumbSize != null) { |
| parameters.setJpegThumbnailSize(jpegThumbSize.width(), jpegThumbSize.height()); |
| } |
| parameters.setPictureFormat(settings.getCurrentPhotoFormat()); |
| |
| CameraSettings.GpsData gpsData = settings.getGpsData(); |
| if (gpsData == null) { |
| parameters.removeGpsData(); |
| } else { |
| parameters.setGpsTimestamp(gpsData.timeStamp); |
| if (gpsData.processingMethod != null) { |
| // It's a hack since we always use GPS time stamp but does |
| // not use other fields sometimes. Setting processing |
| // method to null means the other fields should not be used. |
| parameters.setGpsAltitude(gpsData.altitude); |
| parameters.setGpsLatitude(gpsData.latitude); |
| parameters.setGpsLongitude(gpsData.longitude); |
| parameters.setGpsProcessingMethod(gpsData.processingMethod); |
| } |
| } |
| |
| } |
| |
| /** |
| * @param ratio Desired zoom ratio, in [1.0f,+Inf). |
| * @param percentages Available zoom ratios, as percentages. |
| * @return Index of the closest corresponding ratio, rounded up toward |
| * that of the maximum available ratio. |
| */ |
| private int zoomRatioToIndex(float ratio, List<Integer> percentages) { |
| int percent = (int) (ratio * AndroidCameraCapabilities.ZOOM_MULTIPLIER); |
| int index = Collections.binarySearch(percentages, percent); |
| if (index >= 0) { |
| // Found the desired ratio in the supported list |
| return index; |
| } else { |
| // Didn't find an exact match. Where would it have been? |
| index = -(index + 1); |
| if (index == percentages.size()) { |
| // Put it back in bounds by setting to the maximum allowable zoom |
| --index; |
| } |
| return index; |
| } |
| } |
| } |
| |
| /** |
| * A class which implements {@link CameraAgent.CameraProxy} and |
| * camera handler thread. |
| */ |
| private class AndroidCameraProxyImpl extends CameraAgent.CameraProxy { |
| private final CameraAgent mCameraAgent; |
| private final int mCameraId; |
| /* TODO: remove this Camera instance. */ |
| private final Camera mCamera; |
| private final CameraDeviceInfo.Characteristics mCharacteristics; |
| private final AndroidCameraCapabilities mCapabilities; |
| |
| private AndroidCameraProxyImpl( |
| CameraAgent cameraAgent, |
| int cameraId, |
| Camera camera, |
| CameraDeviceInfo.Characteristics characteristics, |
| AndroidCameraCapabilities capabilities) { |
| mCameraAgent = cameraAgent; |
| mCamera = camera; |
| mCameraId = cameraId; |
| mCharacteristics = characteristics; |
| mCapabilities = capabilities; |
| } |
| |
| @Deprecated |
| @Override |
| public android.hardware.Camera getCamera() { |
| if (getCameraState().isInvalid()) { |
| return null; |
| } |
| return mCamera; |
| } |
| |
| @Override |
| public int getCameraId() { |
| return mCameraId; |
| } |
| |
| @Override |
| public CameraDeviceInfo.Characteristics getCharacteristics() { |
| return mCharacteristics; |
| } |
| |
| @Override |
| public CameraCapabilities getCapabilities() { |
| return new AndroidCameraCapabilities(mCapabilities); |
| } |
| |
| @Override |
| public CameraAgent getAgent() { |
| return mCameraAgent; |
| } |
| |
| @Override |
| public void setPreviewDataCallback( |
| final Handler handler, final CameraPreviewDataCallback cb) { |
| mDispatchThread.runJob(new Runnable() { |
| @Override |
| public void run() { |
| mCameraHandler.obtainMessage(CameraActions.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(CameraActions.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(CameraActions.SET_PREVIEW_CALLBACK_WITH_BUFFER, |
| PreviewCallbackForward |
| .getNewInstance(handler, AndroidCameraProxyImpl.this, cb)) |
| .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() != AndroidCameraStateHolder.CAMERA_FOCUSING) { |
| Log.w(TAG, "onAutoFocus callback returning when not focusing"); |
| } else { |
| mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE); |
| } |
| handler.post(new Runnable() { |
| @Override |
| public void run() { |
| cb.onAutoFocus(b, AndroidCameraProxyImpl.this); |
| } |
| }); |
| } |
| }; |
| mDispatchThread.runJob(new Runnable() { |
| @Override |
| public void run() { |
| // Don't bother to wait since camera is in bad state. |
| if (getCameraState().isInvalid()) { |
| return; |
| } |
| mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE); |
| mCameraHandler.obtainMessage(CameraActions.AUTO_FOCUS, afCallback) |
| .sendToTarget(); |
| } |
| }); |
| } |
| |
| @TargetApi(Build.VERSION_CODES.JELLY_BEAN) |
| @Override |
| public void setAutoFocusMoveCallback( |
| final Handler handler, final CameraAFMoveCallback cb) { |
| try { |
| mDispatchThread.runJob(new Runnable() { |
| @Override |
| public void run() { |
| mCameraHandler.obtainMessage(CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK, |
| AFMoveCallbackForward.getNewInstance( |
| handler, AndroidCameraProxyImpl.this, cb)) |
| .sendToTarget(); |
| } |
| }); |
| } catch (final RuntimeException ex) { |
| mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); |
| } |
| } |
| |
| @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() != AndroidCameraStateHolder.CAMERA_CAPTURING) { |
| Log.w(TAG, "picture callback returning when not capturing"); |
| } else { |
| mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE); |
| } |
| handler.post(new Runnable() { |
| @Override |
| public void run() { |
| jpeg.onPictureTaken(data, AndroidCameraProxyImpl.this); |
| } |
| }); |
| } |
| }; |
| |
| try { |
| mDispatchThread.runJob(new Runnable() { |
| @Override |
| public void run() { |
| // Don't bother to wait since camera is in bad state. |
| if (getCameraState().isInvalid()) { |
| return; |
| } |
| mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE | |
| AndroidCameraStateHolder.CAMERA_UNLOCKED); |
| mCameraHandler.requestTakePicture(ShutterCallbackForward |
| .getNewInstance(handler, AndroidCameraProxyImpl.this, shutter), |
| PictureCallbackForward |
| .getNewInstance(handler, AndroidCameraProxyImpl.this, raw), |
| PictureCallbackForward |
| .getNewInstance(handler, AndroidCameraProxyImpl.this, post), |
| jpegCallback |
| ); |
| } |
| }); |
| } catch (final RuntimeException ex) { |
| mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); |
| } |
| } |
| |
| @Override |
| public void setZoomChangeListener(final OnZoomChangeListener listener) { |
| try { |
| mDispatchThread.runJob(new Runnable() { |
| @Override |
| public void run() { |
| mCameraHandler.obtainMessage(CameraActions.SET_ZOOM_CHANGE_LISTENER, listener) |
| .sendToTarget(); |
| } |
| }); |
| } catch (final RuntimeException ex) { |
| mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); |
| } |
| } |
| |
| @Override |
| public void setFaceDetectionCallback(final Handler handler, |
| final CameraFaceDetectionCallback cb) { |
| try { |
| mDispatchThread.runJob(new Runnable() { |
| @Override |
| public void run() { |
| mCameraHandler.obtainMessage(CameraActions.SET_FACE_DETECTION_LISTENER, |
| FaceDetectionCallbackForward |
| .getNewInstance(handler, AndroidCameraProxyImpl.this, cb)) |
| .sendToTarget(); |
| } |
| }); |
| } catch (final RuntimeException ex) { |
| mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); |
| } |
| } |
| |
| @Deprecated |
| @Override |
| public void setParameters(final Parameters params) { |
| if (params == null) { |
| Log.v(TAG, "null parameters in setParameters()"); |
| return; |
| } |
| final String flattenedParameters = params.flatten(); |
| try { |
| mDispatchThread.runJob(new Runnable() { |
| @Override |
| public void run() { |
| mCameraState.waitForStates(AndroidCameraStateHolder.CAMERA_IDLE | |
| AndroidCameraStateHolder.CAMERA_UNLOCKED); |
| mCameraHandler.obtainMessage(CameraActions.SET_PARAMETERS, flattenedParameters) |
| .sendToTarget(); |
| } |
| }); |
| } catch (final RuntimeException ex) { |
| mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); |
| } |
| } |
| |
| @Deprecated |
| @Override |
| public Parameters getParameters() { |
| final WaitDoneBundle bundle = new WaitDoneBundle(); |
| final Parameters[] parametersHolder = new Parameters[1]; |
| try { |
| mDispatchThread.runJobSync(new Runnable() { |
| @Override |
| public void run() { |
| mCameraHandler.obtainMessage( |
| CameraActions.GET_PARAMETERS, parametersHolder).sendToTarget(); |
| mCameraHandler.post(bundle.mUnlockRunnable); |
| } |
| }, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "get parameters"); |
| } catch (final RuntimeException ex) { |
| mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex); |
| } |
| return parametersHolder[0]; |
| } |
| |
| @Override |
| public CameraSettings getSettings() { |
| return new AndroidCameraSettings(mCapabilities, getParameters()); |
| } |
| |
| @Override |
| public boolean applySettings(CameraSettings settings) { |
| return applySettingsHelper(settings, AndroidCameraStateHolder.CAMERA_IDLE | |
| AndroidCameraStateHolder.CAMERA_UNLOCKED); |
| } |
| |
| @Override |
| public String dumpDeviceSettings() { |
| Parameters parameters = getParameters(); |
| if (parameters != null) { |
| String flattened = getParameters().flatten(); |
| StringTokenizer tokenizer = new StringTokenizer(flattened, ";"); |
| String dumpedSettings = new String(); |
| while (tokenizer.hasMoreElements()) { |
| dumpedSettings += tokenizer.nextToken() + '\n'; |
| } |
| |
| return dumpedSettings; |
| } else { |
| return "[no parameters retrieved]"; |
| } |
| } |
| |
| @Override |
| public Handler getCameraHandler() { |
| return AndroidCameraAgentImpl.this.getCameraHandler(); |
| } |
| |
| @Override |
| public DispatchThread getDispatchThread() { |
| return AndroidCameraAgentImpl.this.getDispatchThread(); |
| } |
| |
| @Override |
| public CameraStateHolder getCameraState() { |
| return mCameraState; |
| } |
| } |
| |
| private static class AndroidCameraStateHolder extends CameraStateHolder { |
| /* Camera states */ |
| // These states are defined bitwise so we can easily to specify a set of |
| // states together. |
| public static final int CAMERA_UNOPENED = 1; |
| public static final int CAMERA_IDLE = 1 << 1; |
| public static final int CAMERA_UNLOCKED = 1 << 2; |
| public static final int CAMERA_CAPTURING = 1 << 3; |
| public static final int CAMERA_FOCUSING = 1 << 4; |
| |
| public AndroidCameraStateHolder() { |
| this(CAMERA_UNOPENED); |
| } |
| |
| public AndroidCameraStateHolder(int state) { |
| super(state); |
| } |
| } |
| |
| /** |
| * 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(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); |
| } |
| }); |
| } |
| } |
| } |