summaryrefslogtreecommitdiffstats
path: root/camera2
diff options
context:
space:
mode:
Diffstat (limited to 'camera2')
-rw-r--r--camera2/portability/Android.mk17
-rw-r--r--camera2/portability/portability.mk24
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java1207
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java259
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java472
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java1255
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraCapabilities.java255
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java75
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraActions.java55
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java830
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraAgentFactory.java166
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java765
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilitiesFactory.java34
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraDeviceInfo.java210
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java471
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraStateHolder.java110
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/DispatchThread.java155
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/HistoryHandler.java58
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/Size.java178
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/debug/Log.java135
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/debug/LogHelper.java32
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/util/SystemProperties.java51
-rw-r--r--camera2/portability/tests/Android.mk25
-rw-r--r--camera2/portability/tests/AndroidManifest.xml27
-rw-r--r--camera2/portability/tests/src/com/android/ex/camera2/portability/Camera2PortabilityTest.java208
-rw-r--r--camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java20
-rw-r--r--camera2/public/src/com/android/ex/camera2/blocking/BlockingCaptureListener.java164
-rw-r--r--camera2/public/src/com/android/ex/camera2/blocking/BlockingSessionListener.java228
-rw-r--r--camera2/public/src/com/android/ex/camera2/blocking/BlockingStateListener.java64
-rw-r--r--camera2/public/src/com/android/ex/camera2/pos/AutoFocusStateMachine.java2
-rw-r--r--camera2/public/src/com/android/ex/camera2/utils/StateChangeListener.java21
-rw-r--r--camera2/public/src/com/android/ex/camera2/utils/StateWaiter.java220
-rw-r--r--camera2/utils/Android.mk17
-rw-r--r--camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureListenerForwarder.java99
-rw-r--r--camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureListenerSplitter.java95
-rw-r--r--camera2/utils/src/com/android/ex/camera2/utils/Camera2RequestSettingsSet.java238
-rw-r--r--camera2/utils/tests/Android.mk24
-rw-r--r--camera2/utils/tests/AndroidManifest.xml27
-rw-r--r--camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2DeviceTester.java93
-rw-r--r--camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2UtilsTest.java410
-rw-r--r--camera2/utils/utils.mk23
41 files changed, 8742 insertions, 77 deletions
diff --git a/camera2/portability/Android.mk b/camera2/portability/Android.mk
new file mode 100644
index 0000000..95b6448
--- /dev/null
+++ b/camera2/portability/Android.mk
@@ -0,0 +1,17 @@
+# Copyright 2014 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(LOCAL_PATH)/portability.mk \
+ $(call all-subdir-makefiles)
diff --git a/camera2/portability/portability.mk b/camera2/portability/portability.mk
new file mode 100644
index 0000000..2ecc1df
--- /dev/null
+++ b/camera2/portability/portability.mk
@@ -0,0 +1,24 @@
+# Copyright 2014 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android-ex-camera2-portability
+LOCAL_MODULE_TAGS := optional
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2-utils
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java
new file mode 100644
index 0000000..d139c62
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2AgentImpl.java
@@ -0,0 +1,1207 @@
+/*
+ * Copyright (C) 2014 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.content.Context;
+import android.graphics.ImageFormat;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.media.Image;
+import android.media.ImageReader;
+import android.media.MediaActionSound;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.view.Surface;
+
+import com.android.ex.camera2.portability.debug.Log;
+import com.android.ex.camera2.utils.Camera2RequestSettingsSet;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A class to implement {@link CameraAgent} of the Android camera2 framework.
+ */
+class AndroidCamera2AgentImpl extends CameraAgent {
+ private static final Log.Tag TAG = new Log.Tag("AndCam2AgntImp");
+
+ private final Camera2Handler mCameraHandler;
+ private final HandlerThread mCameraHandlerThread;
+ private final CameraStateHolder mCameraState;
+ private final DispatchThread mDispatchThread;
+ private final CameraManager mCameraManager;
+ private final MediaActionSound mNoisemaker;
+
+ /**
+ * Number of camera devices. The length of {@code mCameraDevices} does not reveal this
+ * information because that list may contain since-invalidated indices.
+ */
+ private int mNumCameraDevices;
+
+ /**
+ * Transformation between integral camera indices and the {@link java.lang.String} indices used
+ * by the underlying API. Note that devices may disappear because they've been disconnected or
+ * have otherwise gone offline. Because we need to keep the meanings of whatever indices we
+ * expose stable, we cannot simply remove them in such a case; instead, we insert {@code null}s
+ * to invalidate any such indices. Whenever new devices appear, they are appended to the end of
+ * the list, and thereby assigned the lowest index that has never yet been used.
+ */
+ private final List<String> mCameraDevices;
+
+ AndroidCamera2AgentImpl(Context context) {
+ mCameraHandlerThread = new HandlerThread("Camera2 Handler Thread");
+ mCameraHandlerThread.start();
+ mCameraHandler = new Camera2Handler(mCameraHandlerThread.getLooper());
+ mCameraState = new AndroidCamera2StateHolder();
+ mDispatchThread = new DispatchThread(mCameraHandler, mCameraHandlerThread);
+ mDispatchThread.start();
+ mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+ mNoisemaker = new MediaActionSound();
+ mNoisemaker.load(MediaActionSound.SHUTTER_CLICK);
+
+ mNumCameraDevices = 0;
+ mCameraDevices = new ArrayList<String>();
+ updateCameraDevices();
+ }
+
+ /**
+ * Updates the camera device index assignments stored in {@link mCameraDevices}, without
+ * reappropriating any currently-assigned index.
+ * @return Whether the operation was successful
+ */
+ private boolean updateCameraDevices() {
+ try {
+ String[] currentCameraDevices = mCameraManager.getCameraIdList();
+ Set<String> currentSet = new HashSet<String>(Arrays.asList(currentCameraDevices));
+
+ // Invalidate the indices assigned to any camera devices that are no longer present
+ for (int index = 0; index < mCameraDevices.size(); ++index) {
+ if (!currentSet.contains(mCameraDevices.get(index))) {
+ mCameraDevices.set(index, null);
+ --mNumCameraDevices;
+ }
+ }
+
+ // Assign fresh indices to any new camera devices
+ currentSet.removeAll(mCameraDevices); // The devices we didn't know about
+ for (String device : currentCameraDevices) {
+ if (currentSet.contains(device)) {
+ mCameraDevices.add(device);
+ ++mNumCameraDevices;
+ }
+ }
+
+ return true;
+ } catch (CameraAccessException ex) {
+ Log.e(TAG, "Could not get device listing from camera subsystem", ex);
+ return false;
+ }
+ }
+
+ // TODO: Implement
+ @Override
+ public void setCameraDefaultExceptionCallback(CameraExceptionCallback callback,
+ Handler handler) {}
+
+ // TODO: Implement
+ @Override
+ public void recycle() {}
+
+ // TODO: Some indices may now be invalid; ensure everyone can handle that and update the docs
+ @Override
+ public CameraDeviceInfo getCameraDeviceInfo() {
+ updateCameraDevices();
+ return new AndroidCamera2DeviceInfo(mCameraManager, mCameraDevices.toArray(new String[0]),
+ mNumCameraDevices);
+ }
+
+ @Override
+ protected Handler getCameraHandler() {
+ return mCameraHandler;
+ }
+
+ @Override
+ protected DispatchThread getDispatchThread() {
+ return mDispatchThread;
+ }
+
+ private static abstract class CaptureAvailableListener
+ extends CameraCaptureSession.CaptureListener
+ implements ImageReader.OnImageAvailableListener {};
+
+ private class Camera2Handler extends HistoryHandler {
+ // Caller-provided when leaving CAMERA_UNOPENED state:
+ private CameraOpenCallback mOpenCallback;
+ private int mCameraIndex;
+ private String mCameraId;
+
+ // Available in CAMERA_UNCONFIGURED state and above:
+ private CameraDevice mCamera;
+ private AndroidCamera2ProxyImpl mCameraProxy;
+ private Camera2RequestSettingsSet mPersistentSettings;
+ private Rect mActiveArray;
+ private boolean mLegacyDevice;
+
+ // Available in CAMERA_CONFIGURED state and above:
+ private Size mPreviewSize;
+ private Size mPhotoSize;
+
+ // Available in PREVIEW_READY state and above:
+ private SurfaceTexture mPreviewTexture;
+ private Surface mPreviewSurface;
+ private CameraCaptureSession mSession;
+ private ImageReader mCaptureReader;
+
+ // Available from the beginning of PREVIEW_ACTIVE until the first preview frame arrives:
+ private CameraStartPreviewCallback mOneshotPreviewingCallback;
+
+ // Available in FOCUS_LOCKED between AF trigger receipt and whenever the lens stops moving:
+ private CameraAFCallback mOneshotAfCallback;
+
+ // Available when taking picture between AE trigger receipt and autoexposure convergence
+ private CaptureAvailableListener mOneshotCaptureCallback;
+
+ // Available whenever setAutoFocusMoveCallback() was last invoked with a non-null argument:
+ private CameraAFMoveCallback mPassiveAfCallback;
+
+ Camera2Handler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(final Message msg) {
+ super.handleMessage(msg);
+ try {
+ switch(msg.what) {
+ case CameraActions.OPEN_CAMERA:
+ case CameraActions.RECONNECT: {
+ CameraOpenCallback openCallback = (CameraOpenCallback) msg.obj;
+ int cameraIndex = msg.arg1;
+
+ if (mCameraState.getState() != AndroidCamera2StateHolder.CAMERA_UNOPENED) {
+ openCallback.onDeviceOpenedAlready(cameraIndex,
+ generateHistoryString(cameraIndex));
+ break;
+ }
+
+ mOpenCallback = openCallback;
+ mCameraIndex = cameraIndex;
+ mCameraId = mCameraDevices.get(mCameraIndex);
+ Log.i(TAG, String.format("Opening camera index %d (id %s) with camera2 API",
+ cameraIndex, mCameraId));
+
+ if (mCameraId == null) {
+ mOpenCallback.onCameraDisabled(msg.arg1);
+ break;
+ }
+ mCameraManager.openCamera(mCameraId, mCameraDeviceStateListener, this);
+
+ break;
+ }
+
+ case CameraActions.RELEASE: {
+ if (mCameraState.getState() == AndroidCamera2StateHolder.CAMERA_UNOPENED) {
+ Log.w(TAG, "Ignoring release at inappropriate time");
+ break;
+ }
+
+ if (mSession != null) {
+ closePreviewSession();
+ mSession = null;
+ }
+ if (mCamera != null) {
+ mCamera.close();
+ mCamera = null;
+ }
+ mCameraProxy = null;
+ mPersistentSettings = null;
+ mActiveArray = null;
+ if (mPreviewSurface != null) {
+ mPreviewSurface.release();
+ mPreviewSurface = null;
+ }
+ mPreviewTexture = null;
+ if (mCaptureReader != null) {
+ mCaptureReader.close();
+ mCaptureReader = null;
+ }
+ mPreviewSize = null;
+ mPhotoSize = null;
+ mCameraIndex = 0;
+ mCameraId = null;
+ mCameraState.setState(AndroidCamera2StateHolder.CAMERA_UNOPENED);
+ break;
+ }
+
+ /*case CameraActions.UNLOCK: {
+ break;
+ }
+
+ case CameraActions.LOCK: {
+ break;
+ }*/
+
+ case CameraActions.SET_PREVIEW_TEXTURE_ASYNC: {
+ setPreviewTexture((SurfaceTexture) msg.obj);
+ break;
+ }
+
+ case CameraActions.START_PREVIEW_ASYNC: {
+ if (mCameraState.getState() !=
+ AndroidCamera2StateHolder.CAMERA_PREVIEW_READY) {
+ // TODO: Provide better feedback here?
+ Log.w(TAG, "Refusing to start preview at inappropriate time");
+ break;
+ }
+
+ mOneshotPreviewingCallback = (CameraStartPreviewCallback) msg.obj;
+ mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE);
+ try {
+ mSession.setRepeatingRequest(
+ mPersistentSettings.createRequest(mCamera,
+ CameraDevice.TEMPLATE_PREVIEW, mPreviewSurface),
+ /*listener*/mCameraFocusStateListener, /*handler*/this);
+ } catch(CameraAccessException ex) {
+ Log.w(TAG, "Unable to start preview", ex);
+ mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY);
+ }
+ break;
+ }
+
+ case CameraActions.STOP_PREVIEW: {
+ if (mCameraState.getState() <
+ AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) {
+ Log.w(TAG, "Refusing to stop preview at inappropriate time");
+ break;
+ }
+
+ mSession.stopRepeating();
+ mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY);
+ break;
+ }
+
+ /*case CameraActions.SET_PREVIEW_CALLBACK_WITH_BUFFER: {
+ break;
+ }
+
+ case CameraActions.ADD_CALLBACK_BUFFER: {
+ break;
+ }
+
+ case CameraActions.SET_PREVIEW_DISPLAY_ASYNC: {
+ break;
+ }
+
+ case CameraActions.SET_PREVIEW_CALLBACK: {
+ break;
+ }
+
+ case CameraActions.SET_ONE_SHOT_PREVIEW_CALLBACK: {
+ break;
+ }
+
+ case CameraActions.SET_PARAMETERS: {
+ break;
+ }
+
+ case CameraActions.GET_PARAMETERS: {
+ break;
+ }
+
+ case CameraActions.REFRESH_PARAMETERS: {
+ break;
+ }*/
+
+ case CameraActions.APPLY_SETTINGS: {
+ AndroidCamera2Settings settings = (AndroidCamera2Settings) msg.obj;
+ applyToRequest(settings);
+ break;
+ }
+
+ case CameraActions.AUTO_FOCUS: {
+ // We only support locking the focus while a preview is being displayed.
+ // However, it can be requested multiple times in succession; the effect of
+ // the subsequent invocations is determined by the focus mode defined in the
+ // provided CameraSettings object. In passive (CONTINUOUS_*) mode, the
+ // duplicate requests are no-ops and leave the lens locked at its current
+ // position, but in active (AUTO) mode, they perform another scan and lock
+ // once that is finished. In any manual focus mode, this call is a no-op,
+ // and most notably, this is the only case where the callback isn't invoked.
+ if (mCameraState.getState() <
+ AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) {
+ Log.w(TAG, "Ignoring attempt to autofocus without preview");
+ break;
+ }
+
+ // The earliest we can reliably tell whether the autofocus has locked in
+ // response to our latest request is when our one-time capture completes.
+ // However, it will probably take longer than that, so once that happens,
+ // just start checking the repeating preview requests as they complete.
+ final CameraAFCallback callback = (CameraAFCallback) msg.obj;
+ CameraCaptureSession.CaptureListener deferredCallbackSetter =
+ new CameraCaptureSession.CaptureListener() {
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session,
+ CaptureRequest request,
+ TotalCaptureResult result) {
+ // Now our mCameraFocusStateListener will invoke the callback the
+ // first time it finds the focus motor to be locked.
+ mOneshotAfCallback = callback;
+ }
+
+ @Override
+ public void onCaptureFailed(CameraCaptureSession session,
+ CaptureRequest request,
+ CaptureFailure failure) {
+ Log.e(TAG, "Focusing failed with reason " + failure.getReason());
+ callback.onAutoFocus(false, mCameraProxy);
+ }};
+
+ // Send a one-time capture to trigger the camera driver to lock focus.
+ mCameraState.setState(AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED);
+ Camera2RequestSettingsSet trigger =
+ new Camera2RequestSettingsSet(mPersistentSettings);
+ trigger.set(CaptureRequest.CONTROL_AF_TRIGGER,
+ CaptureRequest.CONTROL_AF_TRIGGER_START);
+ try {
+ mSession.capture(
+ trigger.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW,
+ mPreviewSurface),
+ /*listener*/deferredCallbackSetter, /*handler*/ this);
+ } catch(CameraAccessException ex) {
+ Log.e(TAG, "Unable to lock autofocus", ex);
+ mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE);
+ }
+ break;
+ }
+
+ case CameraActions.CANCEL_AUTO_FOCUS: {
+ // Why would you want to unlock the lens if it isn't already locked?
+ if (mCameraState.getState() <
+ AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) {
+ Log.w(TAG, "Ignoring attempt to release focus lock without preview");
+ break;
+ }
+
+ // Send a one-time capture to trigger the camera driver to resume scanning.
+ mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE);
+ Camera2RequestSettingsSet cancel =
+ new Camera2RequestSettingsSet(mPersistentSettings);
+ cancel.set(CaptureRequest.CONTROL_AF_TRIGGER,
+ CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
+ try {
+ mSession.capture(
+ cancel.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW,
+ mPreviewSurface),
+ /*listener*/null, /*handler*/this);
+ } catch(CameraAccessException ex) {
+ Log.e(TAG, "Unable to cancel autofocus", ex);
+ mCameraState.setState(
+ AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED);
+ }
+ break;
+ }
+
+ case CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK: {
+ mPassiveAfCallback = (CameraAFMoveCallback) msg.obj;
+ break;
+ }
+
+ /*case CameraActions.SET_ZOOM_CHANGE_LISTENER: {
+ break;
+ }
+
+ case CameraActions.SET_FACE_DETECTION_LISTENER: {
+ break;
+ }
+
+ case CameraActions.START_FACE_DETECTION: {
+ break;
+ }
+
+ case CameraActions.STOP_FACE_DETECTION: {
+ break;
+ }
+
+ case CameraActions.SET_ERROR_CALLBACK: {
+ break;
+ }
+
+ case CameraActions.ENABLE_SHUTTER_SOUND: {
+ break;
+ }*/
+
+ case CameraActions.SET_DISPLAY_ORIENTATION: {
+ // Only set the JPEG capture orientation if requested to do so; otherwise,
+ // capture in the sensor's physical orientation
+ mPersistentSettings.set(CaptureRequest.JPEG_ORIENTATION, msg.arg2 > 0 ?
+ mCameraProxy.getCharacteristics().getJpegOrientation(msg.arg1) : 0);
+ break;
+ }
+
+ case CameraActions.CAPTURE_PHOTO: {
+ if (mCameraState.getState() <
+ AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) {
+ Log.e(TAG, "Photos may only be taken when a preview is active");
+ break;
+ }
+ if (mCameraState.getState() !=
+ AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED) {
+ Log.w(TAG, "Taking a (likely blurry) photo without the lens locked");
+ }
+
+ final CaptureAvailableListener listener =
+ (CaptureAvailableListener) msg.obj;
+ if (mLegacyDevice) {
+ // Just snap the shot
+ mCaptureReader.setOnImageAvailableListener(listener, /*handler*/this);
+ try {
+ mSession.capture(
+ mPersistentSettings.createRequest(mCamera,
+ CameraDevice.TEMPLATE_STILL_CAPTURE,
+ mCaptureReader.getSurface()),
+ listener, /*handler*/this);
+ } catch (CameraAccessException ex) {
+ Log.e(TAG, "Unable to initiate legacy capture", ex);
+ }
+ } else {
+ // Not a legacy device, so we need to let AE converge before capturing
+ CameraCaptureSession.CaptureListener deferredCallbackSetter =
+ new CameraCaptureSession.CaptureListener() {
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session,
+ CaptureRequest request,
+ TotalCaptureResult result) {
+ mOneshotCaptureCallback = listener;
+ }
+
+ @Override
+ public void onCaptureFailed(CameraCaptureSession session,
+ CaptureRequest request,
+ CaptureFailure failure) {
+ Log.e(TAG, "Autoexposure and capture failed with reason " +
+ failure.getReason());
+ // TODO: Make an error callback?
+ }};
+
+ // Set a one-time capture to trigger the camera driver's autoexposure:
+ Camera2RequestSettingsSet expose =
+ new Camera2RequestSettingsSet(mPersistentSettings);
+ expose.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
+ try {
+ mSession.capture(
+ expose.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW,
+ mPreviewSurface),
+ /*listener*/deferredCallbackSetter, /*handler*/this);
+ } catch (CameraAccessException ex) {
+ Log.e(TAG, "Unable to run autoexposure and perform capture", ex);
+ }
+ }
+ break;
+ }
+
+ default: {
+ // TODO: Rephrase once everything has been implemented
+ throw new RuntimeException("Unimplemented CameraProxy message=" + msg.what);
+ }
+ }
+ } catch (final Exception ex) {
+ if (msg.what != CameraActions.RELEASE && mCamera != null) {
+ // TODO: Handle this better
+ mCamera.close();
+ mCamera = null;
+ } else if (mCamera == null) {
+ if (msg.what == CameraActions.OPEN_CAMERA) {
+ if (mOpenCallback != null) {
+ mOpenCallback.onDeviceOpenFailure(mCameraIndex,
+ generateHistoryString(mCameraIndex));
+ }
+ } else {
+ Log.w(TAG, "Cannot handle message " + msg.what + ", mCamera is null");
+ }
+ return;
+ }
+
+ if (ex instanceof RuntimeException) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ sCameraExceptionCallback.onCameraException((RuntimeException) ex);
+ }});
+ }
+ }
+ }
+
+ public CameraSettings buildSettings(AndroidCamera2Capabilities caps) {
+ try {
+ return new AndroidCamera2Settings(mCamera, CameraDevice.TEMPLATE_PREVIEW,
+ mActiveArray, mPreviewSize, mPhotoSize);
+ } catch (CameraAccessException ex) {
+ Log.e(TAG, "Unable to query camera device to build settings representation");
+ return null;
+ }
+ }
+
+ /**
+ * Simply propagates settings from provided {@link CameraSettings}
+ * object to our {@link CaptureRequest.Builder} for use in captures.
+ * <p>Most conversions to match the API 2 formats are performed by
+ * {@link AndroidCamera2Capabilities.IntegralStringifier}; otherwise
+ * any final adjustments are done here before updating the builder.</p>
+ *
+ * @param settings The new/updated settings
+ */
+ private void applyToRequest(AndroidCamera2Settings settings) {
+ // TODO: If invoked when in PREVIEW_READY state, a new preview size will not take effect
+
+ mPersistentSettings.union(settings.getRequestSettings());
+ mPreviewSize = settings.getCurrentPreviewSize();
+ mPhotoSize = settings.getCurrentPhotoSize();
+
+ if (mCameraState.getState() >= AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE) {
+ // If we're already previewing, reflect most settings immediately
+ try {
+ mSession.setRepeatingRequest(
+ mPersistentSettings.createRequest(mCamera,
+ CameraDevice.TEMPLATE_PREVIEW, mPreviewSurface),
+ /*listener*/mCameraFocusStateListener, /*handler*/this);
+ } catch (CameraAccessException ex) {
+ Log.e(TAG, "Failed to apply updated request settings", ex);
+ }
+ } else if (mCameraState.getState() < AndroidCamera2StateHolder.CAMERA_PREVIEW_READY) {
+ // If we're already ready to preview, this doesn't regress our state
+ mCameraState.setState(AndroidCamera2StateHolder.CAMERA_CONFIGURED);
+ }
+ }
+
+ private void setPreviewTexture(SurfaceTexture surfaceTexture) {
+ // TODO: Must be called after providing a .*Settings populated with sizes
+ // TODO: We don't technically offer a selection of sizes tailored to SurfaceTextures!
+
+ // TODO: Handle this error condition with a callback or exception
+ if (mCameraState.getState() < AndroidCamera2StateHolder.CAMERA_CONFIGURED) {
+ Log.w(TAG, "Ignoring texture setting at inappropriate time");
+ return;
+ }
+
+ // Avoid initializing another capture session unless we absolutely have to
+ if (surfaceTexture == mPreviewTexture) {
+ Log.i(TAG, "Optimizing out redundant preview texture setting");
+ return;
+ }
+
+ if (mSession != null) {
+ closePreviewSession();
+ }
+
+ mPreviewTexture = surfaceTexture;
+ surfaceTexture.setDefaultBufferSize(mPreviewSize.width(), mPreviewSize.height());
+
+ if (mPreviewSurface != null) {
+ mPreviewSurface.release();
+ }
+ mPreviewSurface = new Surface(surfaceTexture);
+
+ if (mCaptureReader != null) {
+ mCaptureReader.close();
+ }
+ mCaptureReader = ImageReader.newInstance(
+ mPhotoSize.width(), mPhotoSize.height(), ImageFormat.JPEG, 1);
+
+ try {
+ mCamera.createCaptureSession(
+ Arrays.asList(mPreviewSurface, mCaptureReader.getSurface()),
+ mCameraPreviewStateListener, this);
+ } catch (CameraAccessException ex) {
+ Log.e(TAG, "Failed to create camera capture session", ex);
+ }
+ }
+
+ private void closePreviewSession() {
+ try {
+ mSession.abortCaptures();
+ mSession = null;
+ } catch (CameraAccessException ex) {
+ Log.e(TAG, "Failed to close existing camera capture session", ex);
+ }
+ mCameraState.setState(AndroidCamera2StateHolder.CAMERA_CONFIGURED);
+ }
+
+ // This listener monitors our connection to and disconnection from camera devices.
+ private CameraDevice.StateListener mCameraDeviceStateListener =
+ new CameraDevice.StateListener() {
+ @Override
+ public void onOpened(CameraDevice camera) {
+ mCamera = camera;
+ if (mOpenCallback != null) {
+ try {
+ CameraCharacteristics props =
+ mCameraManager.getCameraCharacteristics(mCameraId);
+ mCameraProxy = new AndroidCamera2ProxyImpl(mCameraIndex, mCamera,
+ getCameraDeviceInfo().getCharacteristics(mCameraIndex), props);
+ mPersistentSettings = new Camera2RequestSettingsSet();
+ mActiveArray =
+ props.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+ mLegacyDevice =
+ props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
+ mCameraState.setState(AndroidCamera2StateHolder.CAMERA_UNCONFIGURED);
+ mOpenCallback.onCameraOpened(mCameraProxy);
+ } catch (CameraAccessException ex) {
+ mOpenCallback.onDeviceOpenFailure(mCameraIndex,
+ generateHistoryString(mCameraIndex));
+ }
+ }
+ }
+
+ @Override
+ public void onDisconnected(CameraDevice camera) {
+ Log.w(TAG, "Camera device '" + mCameraIndex + "' was disconnected");
+ }
+
+ @Override
+ public void onError(CameraDevice camera, int error) {
+ Log.e(TAG, "Camera device '" + mCameraIndex + "' encountered error code '" +
+ error + '\'');
+ if (mOpenCallback != null) {
+ mOpenCallback.onDeviceOpenFailure(mCameraIndex,
+ generateHistoryString(mCameraIndex));
+ }
+ }};
+
+ // This listener monitors our camera session (i.e. our transition into and out of preview).
+ private CameraCaptureSession.StateListener mCameraPreviewStateListener =
+ new CameraCaptureSession.StateListener() {
+ @Override
+ public void onConfigured(CameraCaptureSession session) {
+ mSession = session;
+ mCameraState.setState(AndroidCamera2StateHolder.CAMERA_PREVIEW_READY);
+ }
+
+ @Override
+ public void onConfigureFailed(CameraCaptureSession session) {
+ // TODO: Invoke a callback
+ Log.e(TAG, "Failed to configure the camera for capture");
+ }
+
+ @Override
+ public void onActive(CameraCaptureSession session) {
+ if (mOneshotPreviewingCallback != null) {
+ // The session is up and processing preview requests. Inform the caller.
+ mOneshotPreviewingCallback.onPreviewStarted();
+ mOneshotPreviewingCallback = null;
+ }
+ }};
+
+ // This listener monitors requested captures and notifies any relevant callbacks.
+ private CameraCaptureSession.CaptureListener mCameraFocusStateListener =
+ new CameraCaptureSession.CaptureListener() {
+ private int mLastAfState = -1;
+
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
+ TotalCaptureResult result) {
+ Integer afStateMaybe = result.get(CaptureResult.CONTROL_AF_STATE);
+ if (afStateMaybe != null) {
+ int afState = afStateMaybe;
+ boolean afStateChanged = false;
+ if (afState != mLastAfState) {
+ mLastAfState = afState;
+ afStateChanged = true;
+ }
+
+ switch (afState) {
+ case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN:
+ case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED:
+ case CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED: {
+ if (afStateChanged && mPassiveAfCallback != null) {
+ // A CameraAFMoveCallback is attached. If we just started to scan,
+ // the motor is moving; otherwise, it has settled.
+ mPassiveAfCallback.onAutoFocusMoving(
+ afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN,
+ mCameraProxy);
+ }
+ break;
+ }
+
+ case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED:
+ case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED: {
+ if (mOneshotAfCallback != null) {
+ // A call to autoFocus() was just made to request a focus lock.
+ // Notify the caller that the lens is now indefinitely fixed, and
+ // report whether the image we're now stuck with is in focus.
+ mOneshotAfCallback.onAutoFocus(
+ afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED,
+ mCameraProxy);
+ mOneshotAfCallback = null;
+ }
+ break;
+ }
+ }
+ }
+
+ Integer aeStateMaybe = result.get(CaptureResult.CONTROL_AE_STATE);
+ if (aeStateMaybe != null) {
+ int aeState = aeStateMaybe;
+
+ switch (aeState) {
+ case CaptureResult.CONTROL_AE_STATE_CONVERGED:
+ case CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED:
+ case CaptureResult.CONTROL_AE_STATE_LOCKED: {
+ if (mOneshotCaptureCallback != null) {
+ // A call to takePicture() was just made, and autoexposure converged
+ // so it's time to initiate the capture!
+ mCaptureReader.setOnImageAvailableListener(mOneshotCaptureCallback,
+ /*handler*/Camera2Handler.this);
+ try {
+ mSession.capture(
+ mPersistentSettings.createRequest(mCamera,
+ CameraDevice.TEMPLATE_STILL_CAPTURE,
+ mCaptureReader.getSurface()),
+ /*listener*/mOneshotCaptureCallback,
+ /*handler*/Camera2Handler.this);
+ } catch (CameraAccessException ex) {
+ Log.e(TAG, "Unable to initiate capture", ex);
+ } finally {
+ mOneshotCaptureCallback = null;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request,
+ CaptureFailure failure) {
+ Log.e(TAG, "Capture attempt failed with reason " + failure.getReason());
+ }};
+ }
+
+ private class AndroidCamera2ProxyImpl extends CameraAgent.CameraProxy {
+ private final int mCameraIndex;
+ private final CameraDevice mCamera;
+ private final CameraDeviceInfo.Characteristics mCharacteristics;
+ private final AndroidCamera2Capabilities mCapabilities;
+
+ public AndroidCamera2ProxyImpl(int cameraIndex, CameraDevice camera,
+ CameraDeviceInfo.Characteristics characteristics,
+ CameraCharacteristics properties) {
+ mCameraIndex = cameraIndex;
+ mCamera = camera;
+ mCharacteristics = characteristics;
+ mCapabilities = new AndroidCamera2Capabilities(properties);
+ }
+
+ // TODO: Implement
+ @Override
+ public android.hardware.Camera getCamera() { return null; }
+
+ @Override
+ public int getCameraId() {
+ return mCameraIndex;
+ }
+
+ @Override
+ public CameraDeviceInfo.Characteristics getCharacteristics() {
+ return mCharacteristics;
+ }
+
+ @Override
+ public CameraCapabilities getCapabilities() {
+ return mCapabilities;
+ }
+
+ private AndroidCamera2Capabilities getSpecializedCapabilities() {
+ return mCapabilities;
+ }
+
+ // TODO: Implement
+ @Override
+ public void setPreviewDataCallback(Handler handler, CameraPreviewDataCallback cb) {}
+
+ // TODO: Implement
+ @Override
+ public void setOneShotPreviewCallback(Handler handler, CameraPreviewDataCallback cb) {}
+
+ // TODO: Implement
+ @Override
+ public void setPreviewDataCallbackWithBuffer(Handler handler, CameraPreviewDataCallback cb)
+ {}
+
+ // TODO: Implement
+ public void addCallbackBuffer(final byte[] callbackBuffer) {}
+
+ @Override
+ public void autoFocus(final Handler handler, final CameraAFCallback cb) {
+ mDispatchThread.runJob(new Runnable() {
+ @Override
+ public void run() {
+ CameraAFCallback cbForward = null;
+ if (cb != null) {
+ cbForward = new CameraAFCallback() {
+ @Override
+ public void onAutoFocus(final boolean focused,
+ final CameraProxy camera) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ cb.onAutoFocus(focused, camera);
+ }});
+ }};
+ }
+
+ mCameraState.waitForStates(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE |
+ AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED);
+ mCameraHandler.obtainMessage(CameraActions.AUTO_FOCUS, cbForward)
+ .sendToTarget();
+ }});
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ @Override
+ public void setAutoFocusMoveCallback(final Handler handler, final CameraAFMoveCallback cb) {
+ mDispatchThread.runJob(new Runnable() {
+ @Override
+ public void run() {
+ CameraAFMoveCallback cbForward = null;
+ if (cb != null) {
+ cbForward = new CameraAFMoveCallback() {
+ @Override
+ public void onAutoFocusMoving(final boolean moving,
+ final CameraProxy camera) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ cb.onAutoFocusMoving(moving, camera);
+ }});
+ }};
+ }
+
+ mCameraHandler.obtainMessage(CameraActions.SET_AUTO_FOCUS_MOVE_CALLBACK,
+ cbForward).sendToTarget();
+ }});
+ }
+
+ @Override
+ public void takePicture(final Handler handler,
+ final CameraShutterCallback shutter,
+ CameraPictureCallback raw,
+ CameraPictureCallback postview,
+ final CameraPictureCallback jpeg) {
+ // TODO: We never call raw or postview
+ final CaptureAvailableListener picListener =
+ new CaptureAvailableListener() {
+ @Override
+ public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
+ long timestamp) {
+ if (shutter != null) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ mNoisemaker.play(MediaActionSound.SHUTTER_CLICK);
+ shutter.onShutter(AndroidCamera2ProxyImpl.this);
+ }});
+ }
+ }
+
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ try (Image image = reader.acquireNextImage()) {
+ if (jpeg != null) {
+ ByteBuffer buffer = image.getPlanes()[0].getBuffer();
+ final byte[] pixels = new byte[buffer.remaining()];
+ buffer.get(pixels);
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ jpeg.onPictureTaken(pixels, AndroidCamera2ProxyImpl.this);
+ }});
+ }
+ }
+ }};
+ mDispatchThread.runJob(new Runnable() {
+ @Override
+ public void run() {
+ mCameraState.waitForStates(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE |
+ AndroidCamera2StateHolder.CAMERA_FOCUS_LOCKED);
+ mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO, picListener)
+ .sendToTarget();
+ }});
+ }
+
+ // TODO: Implement
+ @Override
+ public void setZoomChangeListener(android.hardware.Camera.OnZoomChangeListener listener) {}
+
+ // TODO: Implement
+ @Override
+ public void setFaceDetectionCallback(Handler handler, CameraFaceDetectionCallback callback)
+ {}
+
+ // TODO: Remove this method override once we handle this message
+ @Override
+ public void startFaceDetection() {}
+
+ // TODO: Remove this method override once we handle this message
+ @Override
+ public void stopFaceDetection() {}
+
+ // TODO: Implement
+ @Override
+ public void setErrorCallback(Handler handler, CameraErrorCallback cb) {}
+
+ // TODO: Implement
+ @Override
+ public void setParameters(android.hardware.Camera.Parameters params) {}
+
+ // TODO: Implement
+ @Override
+ public android.hardware.Camera.Parameters getParameters() { return null; }
+
+ @Override
+ public CameraSettings getSettings() {
+ return mCameraHandler.buildSettings(mCapabilities);
+ }
+
+ @Override
+ public boolean applySettings(CameraSettings settings) {
+ if (settings == null) {
+ Log.w(TAG, "null parameters in applySettings()");
+ return false;
+ }
+ if (!(settings instanceof AndroidCamera2Settings)) {
+ Log.e(TAG, "Provided settings not compatible with the backing framework API");
+ return false;
+ }
+
+ return applySettingsHelper(settings, AndroidCamera2StateHolder.CAMERA_UNCONFIGURED |
+ AndroidCamera2StateHolder.CAMERA_CONFIGURED |
+ AndroidCamera2StateHolder.CAMERA_PREVIEW_READY);
+ }
+
+ // TODO: Implement
+ @Override
+ public String dumpDeviceSettings() { return null; }
+
+ @Override
+ public Handler getCameraHandler() {
+ return AndroidCamera2AgentImpl.this.getCameraHandler();
+ }
+
+ @Override
+ public DispatchThread getDispatchThread() {
+ return AndroidCamera2AgentImpl.this.getDispatchThread();
+ }
+
+ @Override
+ public CameraStateHolder getCameraState() {
+ return mCameraState;
+ }
+ }
+
+ /** A linear state machine: each state entails all the states below it. */
+ private static class AndroidCamera2StateHolder extends CameraStateHolder {
+ // Usage flow: openCamera() -> applySettings() -> setPreviewTexture() -> startPreview() ->
+ // autoFocus() -> takePicture()
+ /* Camera states */
+ /** No camera device is opened. */
+ public static final int CAMERA_UNOPENED = 1;
+ /** A camera is opened, but no settings have been provided. */
+ public static final int CAMERA_UNCONFIGURED = 2;
+ /** The open camera has been configured by providing it with settings. */
+ public static final int CAMERA_CONFIGURED = 3;
+ /** A capture session is ready to stream a preview, but still has no repeating request. */
+ public static final int CAMERA_PREVIEW_READY = 4;
+ /** A preview is currently being streamed. */
+ public static final int CAMERA_PREVIEW_ACTIVE = 5;
+ /** The lens is locked on a particular region. */
+ public static final int CAMERA_FOCUS_LOCKED = 6;
+
+ public AndroidCamera2StateHolder() {
+ this(CAMERA_UNOPENED);
+ }
+
+ public AndroidCamera2StateHolder(int state) {
+ super(state);
+ }
+ }
+
+ private static class AndroidCamera2DeviceInfo implements CameraDeviceInfo {
+ private final CameraManager mCameraManager;
+ private final String[] mCameraIds;
+ private final int mNumberOfCameras;
+ private final int mFirstBackCameraId;
+ private final int mFirstFrontCameraId;
+
+ public AndroidCamera2DeviceInfo(CameraManager cameraManager,
+ String[] cameraIds, int numberOfCameras) {
+ mCameraManager = cameraManager;
+ mCameraIds = cameraIds;
+ mNumberOfCameras = numberOfCameras;
+
+ int firstBackId = NO_DEVICE;
+ int firstFrontId = NO_DEVICE;
+ for (int id = 0; id < cameraIds.length; ++id) {
+ try {
+ int lensDirection = cameraManager.getCameraCharacteristics(cameraIds[id])
+ .get(CameraCharacteristics.LENS_FACING);
+ if (firstBackId == NO_DEVICE &&
+ lensDirection == CameraCharacteristics.LENS_FACING_BACK) {
+ firstBackId = id;
+ }
+ if (firstFrontId == NO_DEVICE &&
+ lensDirection == CameraCharacteristics.LENS_FACING_FRONT) {
+ firstFrontId = id;
+ }
+ } catch (CameraAccessException ex) {
+ Log.w(TAG, "Couldn't get characteristics of camera '" + id + "'", ex);
+ }
+ }
+ mFirstBackCameraId = firstBackId;
+ mFirstFrontCameraId = firstFrontId;
+ }
+
+ @Override
+ public Characteristics getCharacteristics(int cameraId) {
+ String actualId = mCameraIds[cameraId];
+ try {
+ CameraCharacteristics info = mCameraManager.getCameraCharacteristics(actualId);
+ return new AndroidCharacteristics2(info);
+ } catch (CameraAccessException ex) {
+ return null;
+ }
+ }
+
+ @Override
+ public int getNumberOfCameras() {
+ return mNumberOfCameras;
+ }
+
+ @Override
+ public int getFirstBackCameraId() {
+ return mFirstBackCameraId;
+ }
+
+ @Override
+ public int getFirstFrontCameraId() {
+ return mFirstFrontCameraId;
+ }
+
+ private static class AndroidCharacteristics2 extends Characteristics {
+ private CameraCharacteristics mCameraInfo;
+
+ AndroidCharacteristics2(CameraCharacteristics cameraInfo) {
+ mCameraInfo = cameraInfo;
+ }
+
+ @Override
+ public boolean isFacingBack() {
+ return mCameraInfo.get(CameraCharacteristics.LENS_FACING)
+ .equals(CameraCharacteristics.LENS_FACING_BACK);
+ }
+
+ @Override
+ public boolean isFacingFront() {
+ return mCameraInfo.get(CameraCharacteristics.LENS_FACING)
+ .equals(CameraCharacteristics.LENS_FACING_FRONT);
+ }
+
+ @Override
+ public int getSensorOrientation() {
+ return mCameraInfo.get(CameraCharacteristics.SENSOR_ORIENTATION);
+ }
+
+ @Override
+ public Matrix getPreviewTransform(int currentDisplayOrientation,
+ RectF surfaceDimensions,
+ RectF desiredBounds) {
+ if (!orientationIsValid(currentDisplayOrientation)) {
+ return new Matrix();
+ }
+
+ // The system transparently transforms the image to fill the surface
+ // when the device is in its natural orientation. We rotate the
+ // coordinates of the rectangle's corners to be relative to the
+ // original image, instead of to the current screen orientation.
+ float[] surfacePolygon = rotate(convertRectToPoly(surfaceDimensions),
+ 2 * currentDisplayOrientation / 90);
+ float[] desiredPolygon = convertRectToPoly(desiredBounds);
+
+ Matrix transform = new Matrix();
+ // Use polygons instead of rectangles so that rotation will be
+ // calculated, since that is not done by the new camera API.
+ transform.setPolyToPoly(surfacePolygon, 0, desiredPolygon, 0, 4);
+ return transform;
+ }
+
+ @Override
+ public boolean canDisableShutterSound() {
+ // The new API doesn't support this operation, so don't encourage people to try it.
+ // TODO: What kind of assumptions have callers made about this result's meaning?
+ return false;
+ }
+
+ private static float[] convertRectToPoly(RectF rf) {
+ return new float[] {rf.left, rf.top, rf.right, rf.top,
+ rf.right, rf.bottom, rf.left, rf.bottom};
+ }
+
+ private static float[] rotate(float[] arr, int times) {
+ if (times < 0) {
+ times = times % arr.length + arr.length;
+ }
+
+ float[] res = new float[arr.length];
+ for (int offset = 0; offset < arr.length; ++offset) {
+ res[offset] = arr[(times + offset) % arr.length];
+ }
+ return res;
+ }
+ }
+ }
+
+ private static final CameraExceptionCallback sCameraExceptionCallback =
+ new CameraExceptionCallback() {
+ @Override
+ public synchronized void onCameraException(RuntimeException e) {
+ throw e;
+ }
+ };
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java
new file mode 100644
index 0000000..51c1422
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Capabilities.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2014 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 static android.hardware.camera2.CameraCharacteristics.*;
+
+import android.graphics.ImageFormat;
+import android.graphics.Point;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.MediaRecorder;
+import android.util.Range;
+import android.util.Rational;
+
+import com.android.ex.camera2.portability.debug.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * The subclass of {@link CameraCapabilities} for Android Camera 2 API.
+ */
+public class AndroidCamera2Capabilities extends CameraCapabilities {
+ private static Log.Tag TAG = new Log.Tag("AndCam2Capabs");
+
+ AndroidCamera2Capabilities(CameraCharacteristics p) {
+ super(new Stringifier());
+
+ StreamConfigurationMap s = p.get(SCALER_STREAM_CONFIGURATION_MAP);
+
+ for (Range<Integer> fpsRange : p.get(CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES)) {
+ mSupportedPreviewFpsRange.add(new int[] { fpsRange.getLower(), fpsRange.getUpper() });
+ }
+
+ // TODO: We only support TextureView preview rendering
+ mSupportedPreviewSizes.addAll(Size.buildListFromAndroidSizes(Arrays.asList(
+ s.getOutputSizes(SurfaceTexture.class))));
+ for (int format : s.getOutputFormats()) {
+ mSupportedPreviewFormats.add(format);
+ }
+
+ // TODO: We only support MediaRecorder video capture
+ mSupportedVideoSizes.addAll(Size.buildListFromAndroidSizes(Arrays.asList(
+ s.getOutputSizes(MediaRecorder.class))));
+
+ // TODO: We only support JPEG image capture
+ mSupportedPhotoSizes.addAll(Size.buildListFromAndroidSizes(Arrays.asList(
+ s.getOutputSizes(ImageFormat.JPEG))));
+ mSupportedPhotoFormats.addAll(mSupportedPreviewFormats);
+
+ buildSceneModes(p);
+ buildFlashModes(p);
+ buildFocusModes(p);
+ buildWhiteBalances(p);
+ // TODO: Populate mSupportedFeatures
+
+ // TODO: Populate mPreferredPreviewSizeForVideo
+
+ Range<Integer> ecRange = p.get(CONTROL_AE_COMPENSATION_RANGE);
+ mMinExposureCompensation = ecRange.getLower();
+ mMaxExposureCompensation = ecRange.getUpper();
+
+ Rational ecStep = p.get(CONTROL_AE_COMPENSATION_STEP);
+ mExposureCompensationStep = (float) ecStep.getNumerator() / ecStep.getDenominator();
+
+ mMaxNumOfFacesSupported = p.get(STATISTICS_INFO_MAX_FACE_COUNT);
+ mMaxNumOfMeteringArea = p.get(CONTROL_MAX_REGIONS_AE);
+
+ // TODO: Populate mMaxZoomRatio
+ // TODO: Populate mHorizontalViewAngle
+ // TODO: Populate mVerticalViewAngle
+ // TODO: Populate mZoomRatioList
+ // TODO: Populate mMaxZoomIndex
+
+ if (supports(FocusMode.AUTO)) {
+ mMaxNumOfFocusAreas = p.get(CONTROL_MAX_REGIONS_AF);
+ if (mMaxNumOfFocusAreas > 0) {
+ mSupportedFeatures.add(Feature.FOCUS_AREA);
+ }
+ }
+ if (mMaxNumOfMeteringArea > 0) {
+ mSupportedFeatures.add(Feature.METERING_AREA);
+ }
+
+ // TODO: Detect other features
+ }
+
+ private void buildSceneModes(CameraCharacteristics p) {
+ int[] scenes = p.get(CONTROL_AVAILABLE_SCENE_MODES);
+ if (scenes != null) {
+ for (int scene : scenes) {
+ SceneMode equiv = sceneModeFromInt(scene);
+ if (equiv != null) {
+ mSupportedSceneModes.add(equiv);
+ }
+ }
+ }
+ }
+
+ private void buildFlashModes(CameraCharacteristics p) {
+ mSupportedFlashModes.add(FlashMode.OFF);
+ if (p.get(FLASH_INFO_AVAILABLE)) {
+ mSupportedFlashModes.add(FlashMode.AUTO);
+ mSupportedFlashModes.add(FlashMode.ON);
+ mSupportedFlashModes.add(FlashMode.TORCH);
+ for (int expose : p.get(CONTROL_AE_AVAILABLE_MODES)) {
+ if (expose == CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE) {
+ mSupportedFlashModes.add(FlashMode.RED_EYE);
+ }
+ }
+ }
+ }
+
+ private void buildFocusModes(CameraCharacteristics p) {
+ int[] focuses = p.get(CONTROL_AF_AVAILABLE_MODES);
+ if (focuses != null) {
+ for (int focus : focuses) {
+ FocusMode equiv = focusModeFromInt(focus);
+ if (equiv != null) {
+ mSupportedFocusModes.add(equiv);
+ }
+ }
+ }
+ }
+
+ private void buildWhiteBalances(CameraCharacteristics p) {
+ int[] bals = p.get(CONTROL_AWB_AVAILABLE_MODES);
+ if (bals != null) {
+ for (int bal : bals) {
+ WhiteBalance equiv = whiteBalanceFromInt(bal);
+ if (equiv != null) {
+ mSupportedWhiteBalances.add(equiv);
+ }
+ }
+ }
+ }
+
+ /**
+ * Converts the API-related integer representation of the focus mode to the
+ * abstract representation.
+ *
+ * @param fm The integral representation.
+ * @return The mode represented by the input integer, or {@code null} if it
+ * cannot be converted.
+ */
+ public static FocusMode focusModeFromInt(int fm) {
+ switch (fm) {
+ case CONTROL_AF_MODE_AUTO:
+ return FocusMode.AUTO;
+ case CONTROL_AF_MODE_CONTINUOUS_PICTURE:
+ return FocusMode.CONTINUOUS_PICTURE;
+ case CONTROL_AF_MODE_CONTINUOUS_VIDEO:
+ return FocusMode.CONTINUOUS_VIDEO;
+ case CONTROL_AF_MODE_EDOF:
+ return FocusMode.EXTENDED_DOF;
+ case CONTROL_AF_MODE_OFF:
+ return FocusMode.FIXED;
+ // TODO: We cannot support INFINITY
+ case CONTROL_AF_MODE_MACRO:
+ return FocusMode.MACRO;
+ }
+ Log.w(TAG, "Unable to convert from API 2 focus mode: " + fm);
+ return null;
+ }
+
+ /**
+ * Converts the API-related integer representation of the scene mode to the
+ * abstract representation.
+ *
+ * @param sm The integral representation.
+ * @return The mode represented by the input integer, or {@code null} if it
+ * cannot be converted.
+ */
+ public static SceneMode sceneModeFromInt(int sm) {
+ switch (sm) {
+ case CONTROL_SCENE_MODE_DISABLED:
+ return SceneMode.AUTO;
+ case CONTROL_SCENE_MODE_ACTION:
+ return SceneMode.ACTION;
+ case CONTROL_SCENE_MODE_BARCODE:
+ return SceneMode.BARCODE;
+ case CONTROL_SCENE_MODE_BEACH:
+ return SceneMode.BEACH;
+ case CONTROL_SCENE_MODE_CANDLELIGHT:
+ return SceneMode.CANDLELIGHT;
+ case CONTROL_SCENE_MODE_FIREWORKS:
+ return SceneMode.FIREWORKS;
+ // TODO: We cannot support HDR
+ case CONTROL_SCENE_MODE_LANDSCAPE:
+ return SceneMode.LANDSCAPE;
+ case CONTROL_SCENE_MODE_NIGHT:
+ return SceneMode.NIGHT;
+ // TODO: We cannot support NIGHT_PORTRAIT
+ case CONTROL_SCENE_MODE_PARTY:
+ return SceneMode.PARTY;
+ case CONTROL_SCENE_MODE_PORTRAIT:
+ return SceneMode.PORTRAIT;
+ case CONTROL_SCENE_MODE_SNOW:
+ return SceneMode.SNOW;
+ case CONTROL_SCENE_MODE_SPORTS:
+ return SceneMode.SPORTS;
+ case CONTROL_SCENE_MODE_STEADYPHOTO:
+ return SceneMode.STEADYPHOTO;
+ case CONTROL_SCENE_MODE_SUNSET:
+ return SceneMode.SUNSET;
+ case CONTROL_SCENE_MODE_THEATRE:
+ return SceneMode.THEATRE;
+ // TODO: We cannot expose FACE_PRIORITY, or HIGH_SPEED_VIDEO
+ }
+ Log.w(TAG, "Unable to convert from API 2 scene mode: " + sm);
+ return null;
+ }
+
+ /**
+ * Converts the API-related integer representation of the white balance to
+ * the abstract representation.
+ *
+ * @param wb The integral representation.
+ * @return The balance represented by the input integer, or {@code null} if
+ * it cannot be converted.
+ */
+ public static WhiteBalance whiteBalanceFromInt(int wb) {
+ switch (wb) {
+ case CONTROL_AWB_MODE_AUTO:
+ return WhiteBalance.AUTO;
+ case CONTROL_AWB_MODE_CLOUDY_DAYLIGHT:
+ return WhiteBalance.CLOUDY_DAYLIGHT;
+ case CONTROL_AWB_MODE_DAYLIGHT:
+ return WhiteBalance.DAYLIGHT;
+ case CONTROL_AWB_MODE_FLUORESCENT:
+ return WhiteBalance.FLUORESCENT;
+ case CONTROL_AWB_MODE_INCANDESCENT:
+ return WhiteBalance.INCANDESCENT;
+ case CONTROL_AWB_MODE_SHADE:
+ return WhiteBalance.SHADE;
+ case CONTROL_AWB_MODE_TWILIGHT:
+ return WhiteBalance.TWILIGHT;
+ case CONTROL_AWB_MODE_WARM_FLUORESCENT:
+ return WhiteBalance.WARM_FLUORESCENT;
+ }
+ Log.w(TAG, "Unable to convert from API 2 white balance: " + wb);
+ return null;
+ }
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java
new file mode 100644
index 0000000..efa68e8
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCamera2Settings.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2014 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 static android.hardware.camera2.CaptureRequest.*;
+
+import android.graphics.Rect;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.util.Range;
+
+import com.android.ex.camera2.portability.CameraCapabilities.FlashMode;
+import com.android.ex.camera2.portability.CameraCapabilities.FocusMode;
+import com.android.ex.camera2.portability.CameraCapabilities.SceneMode;
+import com.android.ex.camera2.portability.CameraCapabilities.WhiteBalance;
+import com.android.ex.camera2.portability.debug.Log;
+import com.android.ex.camera2.utils.Camera2RequestSettingsSet;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The subclass of {@link CameraSettings} for Android Camera 2 API.
+ */
+public class AndroidCamera2Settings extends CameraSettings {
+ private static final Log.Tag TAG = new Log.Tag("AndCam2Set");
+
+ private final Builder mTemplateSettings;
+ private final Rect mActiveArray;
+ private final Camera2RequestSettingsSet mRequestSettings;
+
+ /**
+ * Create a settings representation that answers queries of unspecified
+ * options in the same way as the provided template would.
+ *
+ * <p>The default settings provided by the given template are only ever used
+ * for reporting back to the client app (i.e. when it queries an option
+ * it didn't explicitly set first). {@link Camera2RequestSettingsSet}s
+ * generated by an instance of this class will have any settings not
+ * modified using one of that instance's mutators forced to default, so that
+ * their effective values when submitting a capture request will be those of
+ * the template that is provided to the camera framework at that time.</p>
+ *
+ * @param camera Device from which to draw default settings.
+ * @param template Specific template to use for the defaults.
+ * @param activeArray Boundary coordinates of the sensor's active array.
+ * @param preview Dimensions of preview streams.
+ * @param photo Dimensions of captured images.
+ *
+ * @throws CameraAccessException Upon internal framework/driver failure.
+ */
+ public AndroidCamera2Settings(CameraDevice camera, int template, Rect activeArray,
+ Size preview, Size photo) throws CameraAccessException {
+ mTemplateSettings = camera.createCaptureRequest(template);
+ mActiveArray = activeArray;
+ mRequestSettings = new Camera2RequestSettingsSet();
+
+ Range<Integer> previewFpsRange = mTemplateSettings.get(CONTROL_AE_TARGET_FPS_RANGE);
+ if (previewFpsRange != null) {
+ setPreviewFpsRange(previewFpsRange.getLower(), previewFpsRange.getUpper());
+ }
+ setPreviewSize(preview);
+ // TODO: mCurrentPreviewFormat
+ setPhotoSize(photo);
+ mJpegCompressQuality = queryTemplateDefaultOrMakeOneUp(JPEG_QUALITY, (byte) 0);
+ // TODO: mCurrentPhotoFormat
+ // TODO: mCurrentZoomRatio
+ mCurrentZoomRatio = 1.0f;
+ // TODO: mCurrentZoomIndex
+ mExposureCompensationIndex =
+ queryTemplateDefaultOrMakeOneUp(CONTROL_AE_EXPOSURE_COMPENSATION, 0);
+
+ mCurrentFlashMode = flashModeFromRequest();
+ Integer currentFocusMode = mTemplateSettings.get(CONTROL_AF_MODE);
+ if (currentFocusMode != null) {
+ mCurrentFocusMode = AndroidCamera2Capabilities.focusModeFromInt(currentFocusMode);
+ }
+ Integer currentSceneMode = mTemplateSettings.get(CONTROL_SCENE_MODE);
+ if (currentSceneMode != null) {
+ mCurrentSceneMode = AndroidCamera2Capabilities.sceneModeFromInt(currentSceneMode);
+ }
+ Integer whiteBalance = mTemplateSettings.get(CONTROL_AWB_MODE);
+ if (whiteBalance != null) {
+ mWhiteBalance = AndroidCamera2Capabilities.whiteBalanceFromInt(whiteBalance);
+ }
+
+ mVideoStabilizationEnabled = queryTemplateDefaultOrMakeOneUp(
+ CONTROL_VIDEO_STABILIZATION_MODE, CONTROL_VIDEO_STABILIZATION_MODE_OFF) ==
+ CONTROL_VIDEO_STABILIZATION_MODE_ON;
+ mAutoExposureLocked = queryTemplateDefaultOrMakeOneUp(CONTROL_AE_LOCK, false);
+ mAutoWhiteBalanceLocked = queryTemplateDefaultOrMakeOneUp(CONTROL_AWB_LOCK, false);
+ // TODO: mRecordingHintEnabled
+ // TODO: mGpsData
+ android.util.Size exifThumbnailSize = mTemplateSettings.get(JPEG_THUMBNAIL_SIZE);
+ if (exifThumbnailSize != null) {
+ mExifThumbnailSize =
+ new Size(exifThumbnailSize.getWidth(), exifThumbnailSize.getHeight());
+ }
+ }
+
+ public AndroidCamera2Settings(AndroidCamera2Settings other) {
+ super(other);
+ mTemplateSettings = other.mTemplateSettings;
+ mActiveArray = other.mActiveArray;
+ mRequestSettings = new Camera2RequestSettingsSet(other.mRequestSettings);
+ }
+
+ @Override
+ public CameraSettings copy() {
+ return new AndroidCamera2Settings(this);
+ }
+
+ private <T> T queryTemplateDefaultOrMakeOneUp(Key<T> key, T defaultDefault) {
+ T val = mTemplateSettings.get(key);
+ if (val != null) {
+ return val;
+ } else {
+ // Spoof the default so matchesTemplateDefault excludes this key from generated sets.
+ // This approach beats a simple sentinel because it provides basic boolean support.
+ mTemplateSettings.set(key, defaultDefault);
+ return defaultDefault;
+ }
+ }
+
+ private FlashMode flashModeFromRequest() {
+ Integer autoExposure = mTemplateSettings.get(CONTROL_AE_MODE);
+ if (autoExposure != null) {
+ switch (autoExposure) {
+ case CONTROL_AE_MODE_ON:
+ return FlashMode.OFF;
+ case CONTROL_AE_MODE_ON_AUTO_FLASH:
+ return FlashMode.AUTO;
+ case CONTROL_AE_MODE_ON_ALWAYS_FLASH: {
+ if (mTemplateSettings.get(FLASH_MODE) == FLASH_MODE_TORCH) {
+ return FlashMode.TORCH;
+ } else {
+ return FlashMode.ON;
+ }
+ }
+ case CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE:
+ return FlashMode.RED_EYE;
+ }
+ }
+ return null;
+ }
+
+ private boolean matchesTemplateDefault(Key<?> setting) {
+ if (setting == CONTROL_AE_REGIONS) {
+ return mMeteringAreas.size() == 0;
+ } else if (setting == CONTROL_AF_REGIONS) {
+ return mFocusAreas.size() == 0;
+ } else if (setting == CONTROL_AE_TARGET_FPS_RANGE) {
+ Range defaultFpsRange = mTemplateSettings.get(CONTROL_AE_TARGET_FPS_RANGE);
+ return (mPreviewFpsRangeMin == 0 && mPreviewFpsRangeMax == 0) ||
+ (defaultFpsRange != null && mPreviewFpsRangeMin == defaultFpsRange.getLower() &&
+ mPreviewFpsRangeMax == defaultFpsRange.getUpper());
+ } else if (setting == JPEG_QUALITY) {
+ return Objects.equals(mJpegCompressQuality,
+ mTemplateSettings.get(JPEG_QUALITY));
+ } else if (setting == CONTROL_AE_EXPOSURE_COMPENSATION) {
+ return Objects.equals(mExposureCompensationIndex,
+ mTemplateSettings.get(CONTROL_AE_EXPOSURE_COMPENSATION));
+ } else if (setting == CONTROL_VIDEO_STABILIZATION_MODE) {
+ Integer videoStabilization = mTemplateSettings.get(CONTROL_VIDEO_STABILIZATION_MODE);
+ return (videoStabilization != null &&
+ (mVideoStabilizationEnabled && videoStabilization ==
+ CONTROL_VIDEO_STABILIZATION_MODE_ON) ||
+ (!mVideoStabilizationEnabled && videoStabilization ==
+ CONTROL_VIDEO_STABILIZATION_MODE_OFF));
+ } else if (setting == CONTROL_AE_LOCK) {
+ return Objects.equals(mAutoExposureLocked, mTemplateSettings.get(CONTROL_AE_LOCK));
+ } else if (setting == CONTROL_AWB_LOCK) {
+ return Objects.equals(mAutoWhiteBalanceLocked, mTemplateSettings.get(CONTROL_AWB_LOCK));
+ } else if (setting == JPEG_THUMBNAIL_SIZE) {
+ android.util.Size defaultThumbnailSize = mTemplateSettings.get(JPEG_THUMBNAIL_SIZE);
+ return (mExifThumbnailSize.width() == 0 && mExifThumbnailSize.height() == 0) ||
+ (defaultThumbnailSize != null &&
+ mExifThumbnailSize.width() == defaultThumbnailSize.getWidth() &&
+ mExifThumbnailSize.height() == defaultThumbnailSize.getHeight());
+ }
+ Log.w(TAG, "Settings implementation checked default of unhandled option key");
+ // Since this class isn't equipped to handle it, claim it matches the default to prevent
+ // updateRequestSettingOrForceToDefault from going with the user-provided preference
+ return true;
+ }
+
+ private <T> void updateRequestSettingOrForceToDefault(Key<T> setting, T possibleChoice) {
+ mRequestSettings.set(setting, matchesTemplateDefault(setting) ? null : possibleChoice);
+ }
+
+ public Camera2RequestSettingsSet getRequestSettings() {
+ updateRequestSettingOrForceToDefault(CONTROL_AE_REGIONS,
+ legacyAreasToMeteringRectangles(mMeteringAreas));
+ updateRequestSettingOrForceToDefault(CONTROL_AF_REGIONS,
+ legacyAreasToMeteringRectangles(mFocusAreas));
+ updateRequestSettingOrForceToDefault(CONTROL_AE_TARGET_FPS_RANGE,
+ new Range(mPreviewFpsRangeMin, mPreviewFpsRangeMax));
+ // TODO: mCurrentPreviewFormat
+ updateRequestSettingOrForceToDefault(JPEG_QUALITY, mJpegCompressQuality);
+ // TODO: mCurrentPhotoFormat
+ // TODO: mCurrentZoomRatio
+ // TODO: mCurrentZoomIndex
+ updateRequestSettingOrForceToDefault(CONTROL_AE_EXPOSURE_COMPENSATION,
+ mExposureCompensationIndex);
+ updateRequestFlashMode();
+ updateRequestFocusMode();
+ updateRequestSceneMode();
+ updateRequestWhiteBalance();
+ updateRequestSettingOrForceToDefault(CONTROL_VIDEO_STABILIZATION_MODE,
+ mVideoStabilizationEnabled ?
+ CONTROL_VIDEO_STABILIZATION_MODE_ON : CONTROL_VIDEO_STABILIZATION_MODE_OFF);
+ // OIS shouldn't be on if software video stabilization is.
+ mRequestSettings.set(LENS_OPTICAL_STABILIZATION_MODE,
+ mVideoStabilizationEnabled ? LENS_OPTICAL_STABILIZATION_MODE_OFF :
+ null);
+ updateRequestSettingOrForceToDefault(CONTROL_AE_LOCK, mAutoExposureLocked);
+ updateRequestSettingOrForceToDefault(CONTROL_AWB_LOCK, mAutoWhiteBalanceLocked);
+ // TODO: mRecordingHintEnabled
+ // TODO: mGpsData
+ updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE,
+ new android.util.Size(
+ mExifThumbnailSize.width(), mExifThumbnailSize.height()));
+
+ return mRequestSettings;
+ }
+
+ private MeteringRectangle[] legacyAreasToMeteringRectangles(
+ List<android.hardware.Camera.Area> reference) {
+ MeteringRectangle[] transformed = null;
+ if (reference.size() > 0) {
+
+ transformed = new MeteringRectangle[reference.size()];
+ for (int index = 0; index < reference.size(); ++index) {
+ android.hardware.Camera.Area source = reference.get(index);
+ Rect rectangle = source.rect;
+
+ // Old API coordinates were [-1000,1000]; new ones are [0,ACTIVE_ARRAY_SIZE).
+ double oldLeft = (rectangle.left + 1000) / 2000.0;
+ double oldTop = (rectangle.top + 1000) / 2000.0;
+ double oldRight = (rectangle.right + 1000) / 2000.0;
+ double oldBottom = (rectangle.bottom + 1000) / 2000.0;
+ int left = toIntConstrained( mActiveArray.width() * oldLeft + mActiveArray.left,
+ 0, mActiveArray.width() - 1);
+ int top = toIntConstrained( mActiveArray.height() * oldTop + mActiveArray.top,
+ 0, mActiveArray.height() - 1);
+ int right = toIntConstrained( mActiveArray.width() * oldRight + mActiveArray.left,
+ 0, mActiveArray.width() - 1);
+ int bottom = toIntConstrained( mActiveArray.height() * oldBottom + mActiveArray.top,
+ 0, mActiveArray.height() - 1);
+ transformed[index] = new MeteringRectangle(left, top, right - left, bottom - top,
+ source.weight);
+ }
+ }
+ return transformed;
+ }
+
+ private int toIntConstrained(double original, int min, int max) {
+ original = Math.max(original, min);
+ original = Math.min(original, max);
+ return (int) original;
+ }
+
+ private void updateRequestFlashMode() {
+ Integer aeMode = null;
+ Integer flashMode = null;
+ if (mCurrentFlashMode != null) {
+ switch (mCurrentFlashMode) {
+ case AUTO: {
+ aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH;
+ break;
+ }
+ case OFF: {
+ aeMode = CONTROL_AE_MODE_ON;
+ flashMode = FLASH_MODE_OFF;
+ break;
+ }
+ case ON: {
+ aeMode = CONTROL_AE_MODE_ON_ALWAYS_FLASH;
+ flashMode = FLASH_MODE_SINGLE;
+ break;
+ }
+ case TORCH: {
+ flashMode = FLASH_MODE_TORCH;
+ break;
+ }
+ case RED_EYE: {
+ aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE;
+ break;
+ }
+ default: {
+ Log.w(TAG, "Unable to convert to API 2 flash mode: " + mCurrentFlashMode);
+ break;
+ }
+ }
+ }
+ mRequestSettings.set(CONTROL_AE_MODE, aeMode);
+ mRequestSettings.set(FLASH_MODE, flashMode);
+ }
+
+ private void updateRequestFocusMode() {
+ Integer mode = null;
+ if (mCurrentFocusMode != null) {
+ switch (mCurrentFocusMode) {
+ case AUTO: {
+ mode = CONTROL_AF_MODE_AUTO;
+ break;
+ }
+ case CONTINUOUS_PICTURE: {
+ mode = CONTROL_AF_MODE_CONTINUOUS_PICTURE;
+ break;
+ }
+ case CONTINUOUS_VIDEO: {
+ mode = CONTROL_AF_MODE_CONTINUOUS_VIDEO;
+ break;
+ }
+ case EXTENDED_DOF: {
+ mode = CONTROL_AF_MODE_EDOF;
+ break;
+ }
+ case FIXED: {
+ mode = CONTROL_AF_MODE_OFF;
+ break;
+ }
+ // TODO: We cannot support INFINITY
+ case MACRO: {
+ mode = CONTROL_AF_MODE_MACRO;
+ break;
+ }
+ default: {
+ Log.w(TAG, "Unable to convert to API 2 focus mode: " + mCurrentFocusMode);
+ break;
+ }
+ }
+ }
+ mRequestSettings.set(CONTROL_AF_MODE, mode);
+ }
+
+ private void updateRequestSceneMode() {
+ Integer mode = null;
+ if (mCurrentSceneMode != null) {
+ switch (mCurrentSceneMode) {
+ case AUTO: {
+ mode = CONTROL_SCENE_MODE_DISABLED;
+ break;
+ }
+ case ACTION: {
+ mode = CONTROL_SCENE_MODE_ACTION;
+ break;
+ }
+ case BARCODE: {
+ mode = CONTROL_SCENE_MODE_BARCODE;
+ break;
+ }
+ case BEACH: {
+ mode = CONTROL_SCENE_MODE_BEACH;
+ break;
+ }
+ case CANDLELIGHT: {
+ mode = CONTROL_SCENE_MODE_CANDLELIGHT;
+ break;
+ }
+ case FIREWORKS: {
+ mode = CONTROL_SCENE_MODE_FIREWORKS;
+ break;
+ }
+ // TODO: We cannot support HDR
+ case LANDSCAPE: {
+ mode = CONTROL_SCENE_MODE_LANDSCAPE;
+ break;
+ }
+ case NIGHT: {
+ mode = CONTROL_SCENE_MODE_NIGHT;
+ break;
+ }
+ // TODO: We cannot support NIGHT_PORTRAIT
+ case PARTY: {
+ mode = CONTROL_SCENE_MODE_PARTY;
+ break;
+ }
+ case PORTRAIT: {
+ mode = CONTROL_SCENE_MODE_PORTRAIT;
+ break;
+ }
+ case SNOW: {
+ mode = CONTROL_SCENE_MODE_SNOW;
+ break;
+ }
+ case SPORTS: {
+ mode = CONTROL_SCENE_MODE_SPORTS;
+ break;
+ }
+ case STEADYPHOTO: {
+ mode = CONTROL_SCENE_MODE_STEADYPHOTO;
+ break;
+ }
+ case SUNSET: {
+ mode = CONTROL_SCENE_MODE_SUNSET;
+ break;
+ }
+ case THEATRE: {
+ mode = CONTROL_SCENE_MODE_THEATRE;
+ break;
+ }
+ default: {
+ Log.w(TAG, "Unable to convert to API 2 scene mode: " + mCurrentSceneMode);
+ break;
+ }
+ }
+ }
+ mRequestSettings.set(CONTROL_SCENE_MODE, mode);
+ }
+
+ private void updateRequestWhiteBalance() {
+ Integer mode = null;
+ if (mWhiteBalance != null) {
+ switch (mWhiteBalance) {
+ case AUTO: {
+ mode = CONTROL_AWB_MODE_AUTO;
+ break;
+ }
+ case CLOUDY_DAYLIGHT: {
+ mode = CONTROL_AWB_MODE_CLOUDY_DAYLIGHT;
+ break;
+ }
+ case DAYLIGHT: {
+ mode = CONTROL_AWB_MODE_DAYLIGHT;
+ break;
+ }
+ case FLUORESCENT: {
+ mode = CONTROL_AWB_MODE_FLUORESCENT;
+ break;
+ }
+ case INCANDESCENT: {
+ mode = CONTROL_AWB_MODE_INCANDESCENT;
+ break;
+ }
+ case SHADE: {
+ mode = CONTROL_AWB_MODE_SHADE;
+ break;
+ }
+ case TWILIGHT: {
+ mode = CONTROL_AWB_MODE_TWILIGHT;
+ break;
+ }
+ case WARM_FLUORESCENT: {
+ mode = CONTROL_AWB_MODE_WARM_FLUORESCENT;
+ break;
+ }
+ default: {
+ Log.w(TAG, "Unable to convert to API 2 white balance: " + mWhiteBalance);
+ break;
+ }
+ }
+ }
+ mRequestSettings.set(CONTROL_AWB_MODE, mode);
+ }
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java
new file mode 100644
index 0000000..c26a1a3
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraAgentImpl.java
@@ -0,0 +1,1255 @@
+/*
+ * 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.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 Handler mCameraExceptionCallbackHandler;
+ private CameraExceptionCallback mCameraExceptionCallback =
+ new CameraExceptionCallback() {
+ @Override
+ public void onCameraException(RuntimeException e) {
+ throw e;
+ }
+ };
+
+ AndroidCameraAgentImpl() {
+ mCameraHandlerThread = new HandlerThread("Camera Handler Thread");
+ mCameraHandlerThread.start();
+ mCameraHandler = new CameraHandler(mCameraHandlerThread.getLooper());
+ mCameraExceptionCallbackHandler = mCameraHandler;
+ mCameraState = new AndroidCameraStateHolder();
+ mDispatchThread = new DispatchThread(mCameraHandler, mCameraHandlerThread);
+ mDispatchThread.start();
+ }
+
+ @Override
+ public void setCameraDefaultExceptionCallback(CameraExceptionCallback callback,
+ Handler handler) {
+ synchronized (mCameraExceptionCallback) {
+ mCameraExceptionCallback = callback;
+ mCameraExceptionCallbackHandler = handler;
+ }
+ }
+
+ @Override
+ public void recycle() {
+ closeCamera(null, true);
+ mDispatchThread.end();
+ }
+
+ @Override
+ public CameraDeviceInfo getCameraDeviceInfo() {
+ return AndroidCameraDeviceInfo.create();
+ }
+
+ @Override
+ protected Handler getCameraHandler() {
+ return mCameraHandler;
+ }
+
+ @Override
+ protected DispatchThread getDispatchThread() {
+ return mDispatchThread;
+ }
+
+ 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();
+ }
+ return mParameters;
+ }
+ }
+
+ /**
+ * The handler on which the actual camera operations happen.
+ */
+ private class CameraHandler extends HistoryHandler {
+
+ private Camera mCamera;
+ private int mCameraId;
+ private ParametersCache mParameterCache;
+
+ 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(CameraActions.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) {
+ super.handleMessage(msg);
+ try {
+ switch (msg.what) {
+ 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());
+
+ mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE);
+ if (openCallback != null) {
+ openCallback.onCameraOpened(
+ new AndroidCameraProxyImpl(cameraId, mCamera,
+ mCharacteristics, mCapabilities));
+ }
+ } 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;
+ } 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(AndroidCameraAgentImpl.this,
+ generateHistoryString(mCameraId));
+ }
+ break;
+ }
+
+ mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE);
+ if (cbForward != null) {
+ cbForward.onCameraOpened(
+ new AndroidCameraProxyImpl(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;
+ }
+
+ 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;
+ }
+
+ 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: {
+ mCameraState.setState(AndroidCameraStateHolder.CAMERA_FOCUSING);
+ mCamera.autoFocus((AutoFocusCallback) msg.obj);
+ break;
+ }
+
+ case CameraActions.CANCEL_AUTO_FOCUS: {
+ mCamera.cancelAutoFocus();
+ mCameraState.setState(AndroidCameraStateHolder.CAMERA_IDLE);
+ 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
+ Parameters parameters = mParameterCache.getBlocking();
+ parameters.setRotation(
+ msg.arg2 > 0 ? mCharacteristics.getJpegOrientation(msg.arg1) : 0);
+ mCamera.setParameters(parameters);
+ 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.SET_ERROR_CALLBACK: {
+ mCamera.setErrorCallback((ErrorCallback) msg.obj);
+ 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);
+ capture((CaptureCallbacks) msg.obj);
+ break;
+ }
+
+ default: {
+ throw new RuntimeException("Invalid CameraProxy message=" + msg.what);
+ }
+ }
+ } catch (final RuntimeException e) {
+ if (msg.what != CameraActions.RELEASE && mCamera != null) {
+ try {
+ mCamera.release();
+ mCameraState.setState(AndroidCameraStateHolder.CAMERA_UNOPENED);
+ } catch (Exception ex) {
+ Log.e(TAG, "Fail to release the camera.");
+ }
+ mCamera = null;
+ } else {
+ if (mCamera == null) {
+ if (msg.what == CameraActions.OPEN_CAMERA) {
+ final int cameraId = msg.arg1;
+ if (msg.obj != null) {
+ ((CameraOpenCallback) msg.obj).onDeviceOpenFailure(
+ msg.arg1, generateHistoryString(cameraId));
+ }
+ } 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 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)) {
+ // Should use settings.getCurrentZoomRatio() instead here.
+ parameters.setZoom(settings.getCurrentZoomIndex());
+ }
+ 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 (mCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA)) {
+ if (settings.getFocusAreas().size() != 0) {
+ parameters.setFocusAreas(settings.getFocusAreas());
+ }
+ }
+ if (mCapabilities.supports(CameraCapabilities.Feature.METERING_AREA)) {
+ if (settings.getMeteringAreas().size() != 0) {
+ parameters.setMeteringAreas(settings.getMeteringAreas());
+ }
+ }
+ 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();
+ 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);
+ }
+ }
+
+ }
+ }
+
+ /**
+ * A class which implements {@link CameraAgent.CameraProxy} and
+ * camera handler thread.
+ */
+ private class AndroidCameraProxyImpl extends CameraAgent.CameraProxy {
+ private final int mCameraId;
+ /* TODO: remove this Camera instance. */
+ private final Camera mCamera;
+ private final CameraDeviceInfo.Characteristics mCharacteristics;
+ private final AndroidCameraCapabilities mCapabilities;
+
+ private AndroidCameraProxyImpl(int cameraId, Camera camera,
+ CameraDeviceInfo.Characteristics characteristics,
+ AndroidCameraCapabilities capabilities) {
+ mCamera = camera;
+ mCameraId = cameraId;
+ mCharacteristics = characteristics;
+ mCapabilities = capabilities;
+ }
+
+ @Deprecated
+ @Override
+ public android.hardware.Camera getCamera() {
+ 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 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() {
+ 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) {
+ mDispatchThread.runJob(new Runnable() {
+ @Override
+ public void run() {
+ mCameraHandler.obtainMessage(CameraActions.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() != 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);
+ }
+ });
+ }
+ };
+
+ mDispatchThread.runJob(new Runnable() {
+ @Override
+ public void run() {
+ 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
+ );
+ }
+ });
+ }
+
+ @Override
+ public void setZoomChangeListener(final OnZoomChangeListener listener) {
+ mDispatchThread.runJob(new Runnable() {
+ @Override
+ public void run() {
+ mCameraHandler.obtainMessage(CameraActions.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(CameraActions.SET_FACE_DETECTION_LISTENER,
+ FaceDetectionCallbackForward
+ .getNewInstance(handler, AndroidCameraProxyImpl.this, cb))
+ .sendToTarget();
+ }
+ });
+ }
+
+ @Override
+ public void setErrorCallback(final Handler handler, final CameraErrorCallback cb) {
+ mDispatchThread.runJob(new Runnable() {
+ @Override
+ public void run() {
+ mCameraHandler.obtainMessage(CameraActions.SET_ERROR_CALLBACK,
+ ErrorCallbackForward.getNewInstance(
+ handler, AndroidCameraProxyImpl.this, cb))
+ .sendToTarget();
+ }
+ });
+ }
+
+ @Deprecated
+ @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(AndroidCameraStateHolder.CAMERA_IDLE |
+ AndroidCameraStateHolder.CAMERA_UNLOCKED);
+ mCameraHandler.obtainMessage(CameraActions.SET_PARAMETERS, flattenedParameters)
+ .sendToTarget();
+ }
+ });
+ }
+
+ @Deprecated
+ @Override
+ public Parameters getParameters() {
+ final WaitDoneBundle bundle = new WaitDoneBundle();
+ final Parameters[] parametersHolder = new Parameters[1];
+ mDispatchThread.runJobSync(new Runnable() {
+ @Override
+ public void run() {
+ Message getParametersMessage = mCameraHandler.obtainMessage(
+ CameraActions.GET_PARAMETERS, parametersHolder);
+ mCameraHandler.sendMessage(getParametersMessage);
+ mCameraHandler.post(bundle.mUnlockRunnable);
+ }
+ }, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "get parameters");
+ 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 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);
+ }
+ });
+ }
+ }
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraCapabilities.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraCapabilities.java
new file mode 100644
index 0000000..acff9c6
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraCapabilities.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2014 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.hardware.Camera;
+
+import com.android.ex.camera2.portability.debug.Log;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * The subclass of {@link CameraCapabilities} for Android Camera 1 API.
+ */
+class AndroidCameraCapabilities extends CameraCapabilities {
+
+ private static Log.Tag TAG = new Log.Tag("AndCamCapabs");
+
+ private FpsComparator mFpsComparator = new FpsComparator();
+ private SizeComparator mSizeComparator = new SizeComparator();
+
+ AndroidCameraCapabilities(Camera.Parameters p) {
+ super(new Stringifier());
+ mMaxExposureCompensation = p.getMaxExposureCompensation();
+ mMinExposureCompensation = p.getMinExposureCompensation();
+ mExposureCompensationStep = p.getExposureCompensationStep();
+ mMaxNumOfFacesSupported = p.getMaxNumDetectedFaces();
+ mMaxNumOfMeteringArea = p.getMaxNumMeteringAreas();
+ mPreferredPreviewSizeForVideo = new Size(p.getPreferredPreviewSizeForVideo());
+ mSupportedPreviewFormats.addAll(p.getSupportedPreviewFormats());
+ mSupportedPhotoFormats.addAll(p.getSupportedPictureFormats());
+ mMaxZoomIndex = p.getMaxZoom();
+ mZoomRatioList.addAll(p.getZoomRatios());
+ mMaxZoomRatio = mZoomRatioList.get(mMaxZoomIndex);
+ mHorizontalViewAngle = p.getHorizontalViewAngle();
+ mVerticalViewAngle = p.getVerticalViewAngle();
+ buildPreviewFpsRange(p);
+ buildPreviewSizes(p);
+ buildVideoSizes(p);
+ buildPictureSizes(p);
+ buildSceneModes(p);
+ buildFlashModes(p);
+ buildFocusModes(p);
+ buildWhiteBalances(p);
+
+ if (p.isZoomSupported()) {
+ mSupportedFeatures.add(Feature.ZOOM);
+ }
+ if (p.isVideoSnapshotSupported()) {
+ mSupportedFeatures.add(Feature.VIDEO_SNAPSHOT);
+ }
+ if (p.isAutoExposureLockSupported()) {
+ mSupportedFeatures.add(Feature.AUTO_EXPOSURE_LOCK);
+ }
+ if (p.isAutoWhiteBalanceLockSupported()) {
+ mSupportedFeatures.add(Feature.AUTO_WHITE_BALANCE_LOCK);
+ }
+ if (supports(FocusMode.AUTO)) {
+ mMaxNumOfFocusAreas = p.getMaxNumFocusAreas();
+ if (mMaxNumOfFocusAreas > 0) {
+ mSupportedFeatures.add(Feature.FOCUS_AREA);
+ }
+ }
+ if (mMaxNumOfMeteringArea > 0) {
+ mSupportedFeatures.add(Feature.METERING_AREA);
+ }
+ }
+
+ AndroidCameraCapabilities(AndroidCameraCapabilities src) {
+ super(src);
+ }
+
+ private void buildPreviewFpsRange(Camera.Parameters p) {
+ List<int[]> supportedPreviewFpsRange = p.getSupportedPreviewFpsRange();
+ if (supportedPreviewFpsRange != null) {
+ mSupportedPreviewFpsRange.addAll(supportedPreviewFpsRange);
+ }
+ Collections.sort(mSupportedPreviewFpsRange, mFpsComparator);
+ }
+
+ private void buildPreviewSizes(Camera.Parameters p) {
+ List<Camera.Size> supportedPreviewSizes = p.getSupportedPreviewSizes();
+ if (supportedPreviewSizes != null) {
+ for (Camera.Size s : supportedPreviewSizes) {
+ mSupportedPreviewSizes.add(new Size(s.width, s.height));
+ }
+ }
+ Collections.sort(mSupportedPreviewSizes, mSizeComparator);
+ }
+
+ private void buildVideoSizes(Camera.Parameters p) {
+ List<Camera.Size> supportedVideoSizes = p.getSupportedVideoSizes();
+ if (supportedVideoSizes != null) {
+ for (Camera.Size s : supportedVideoSizes) {
+ mSupportedVideoSizes.add(new Size(s.width, s.height));
+ }
+ }
+ Collections.sort(mSupportedVideoSizes, mSizeComparator);
+ }
+
+ private void buildPictureSizes(Camera.Parameters p) {
+ List<Camera.Size> supportedPictureSizes = p.getSupportedPictureSizes();
+ if (supportedPictureSizes != null) {
+ for (Camera.Size s : supportedPictureSizes) {
+ mSupportedPhotoSizes.add(new Size(s.width, s.height));
+ }
+ }
+ Collections.sort(mSupportedPhotoSizes, mSizeComparator);
+
+ }
+
+ private void buildSceneModes(Camera.Parameters p) {
+ List<String> supportedSceneModes = p.getSupportedSceneModes();
+ if (supportedSceneModes != null) {
+ for (String scene : supportedSceneModes) {
+ if (Camera.Parameters.SCENE_MODE_AUTO.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.AUTO);
+ } else if (Camera.Parameters.SCENE_MODE_ACTION.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.ACTION);
+ } else if (Camera.Parameters.SCENE_MODE_BARCODE.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.BARCODE);
+ } else if (Camera.Parameters.SCENE_MODE_BEACH.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.BEACH);
+ } else if (Camera.Parameters.SCENE_MODE_CANDLELIGHT.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.CANDLELIGHT);
+ } else if (Camera.Parameters.SCENE_MODE_FIREWORKS.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.FIREWORKS);
+ } else if (Camera.Parameters.SCENE_MODE_HDR.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.HDR);
+ } else if (Camera.Parameters.SCENE_MODE_LANDSCAPE.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.LANDSCAPE);
+ } else if (Camera.Parameters.SCENE_MODE_NIGHT.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.NIGHT);
+ } else if (Camera.Parameters.SCENE_MODE_NIGHT_PORTRAIT.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.NIGHT_PORTRAIT);
+ } else if (Camera.Parameters.SCENE_MODE_PARTY.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.PARTY);
+ } else if (Camera.Parameters.SCENE_MODE_PORTRAIT.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.PORTRAIT);
+ } else if (Camera.Parameters.SCENE_MODE_SNOW.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.SNOW);
+ } else if (Camera.Parameters.SCENE_MODE_SPORTS.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.SPORTS);
+ } else if (Camera.Parameters.SCENE_MODE_STEADYPHOTO.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.STEADYPHOTO);
+ } else if (Camera.Parameters.SCENE_MODE_SUNSET.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.SUNSET);
+ } else if (Camera.Parameters.SCENE_MODE_THEATRE.equals(scene)) {
+ mSupportedSceneModes.add(SceneMode.THEATRE);
+ }
+ }
+ }
+ }
+
+ private void buildFlashModes(Camera.Parameters p) {
+ List<String> supportedFlashModes = p.getSupportedFlashModes();
+ if (supportedFlashModes == null) {
+ // Camera 1 will return NULL if no flash mode is supported.
+ mSupportedFlashModes.add(FlashMode.NO_FLASH);
+ } else {
+ for (String flash : supportedFlashModes) {
+ if (Camera.Parameters.FLASH_MODE_AUTO.equals(flash)) {
+ mSupportedFlashModes.add(FlashMode.AUTO);
+ } else if (Camera.Parameters.FLASH_MODE_OFF.equals(flash)) {
+ mSupportedFlashModes.add(FlashMode.OFF);
+ } else if (Camera.Parameters.FLASH_MODE_ON.equals(flash)) {
+ mSupportedFlashModes.add(FlashMode.ON);
+ } else if (Camera.Parameters.FLASH_MODE_RED_EYE.equals(flash)) {
+ mSupportedFlashModes.add(FlashMode.RED_EYE);
+ } else if (Camera.Parameters.FLASH_MODE_TORCH.equals(flash)) {
+ mSupportedFlashModes.add(FlashMode.TORCH);
+ }
+ }
+ }
+ }
+
+ private void buildFocusModes(Camera.Parameters p) {
+ List<String> supportedFocusModes = p.getSupportedFocusModes();
+ if (supportedFocusModes != null) {
+ for (String focus : supportedFocusModes) {
+ if (Camera.Parameters.FOCUS_MODE_AUTO.equals(focus)) {
+ mSupportedFocusModes.add(FocusMode.AUTO);
+ } else if (Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(focus)) {
+ mSupportedFocusModes.add(FocusMode.CONTINUOUS_PICTURE);
+ } else if (Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO.equals(focus)) {
+ mSupportedFocusModes.add(FocusMode.CONTINUOUS_VIDEO);
+ } else if (Camera.Parameters.FOCUS_MODE_EDOF.equals(focus)) {
+ mSupportedFocusModes.add(FocusMode.EXTENDED_DOF);
+ } else if (Camera.Parameters.FOCUS_MODE_FIXED.equals(focus)) {
+ mSupportedFocusModes.add(FocusMode.FIXED);
+ } else if (Camera.Parameters.FOCUS_MODE_INFINITY.equals(focus)) {
+ mSupportedFocusModes.add(FocusMode.INFINITY);
+ } else if (Camera.Parameters.FOCUS_MODE_MACRO.equals(focus)) {
+ mSupportedFocusModes.add(FocusMode.MACRO);
+ }
+ }
+ }
+ }
+
+ private void buildWhiteBalances(Camera.Parameters p) {
+ List<String> supportedWhiteBalances = p.getSupportedFocusModes();
+ if (supportedWhiteBalances != null) {
+ for (String wb : supportedWhiteBalances) {
+ if (Camera.Parameters.WHITE_BALANCE_AUTO.equals(wb)) {
+ mSupportedWhiteBalances.add(WhiteBalance.AUTO);
+ } else if (Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT.equals(wb)) {
+ mSupportedWhiteBalances.add(WhiteBalance.CLOUDY_DAYLIGHT);
+ } else if (Camera.Parameters.WHITE_BALANCE_DAYLIGHT.equals(wb)) {
+ mSupportedWhiteBalances.add(WhiteBalance.DAYLIGHT);
+ } else if (Camera.Parameters.WHITE_BALANCE_FLUORESCENT.equals(wb)) {
+ mSupportedWhiteBalances.add(WhiteBalance.FLUORESCENT);
+ } else if (Camera.Parameters.WHITE_BALANCE_INCANDESCENT.equals(wb)) {
+ mSupportedWhiteBalances.add(WhiteBalance.INCANDESCENT);
+ } else if (Camera.Parameters.WHITE_BALANCE_SHADE.equals(wb)) {
+ mSupportedWhiteBalances.add(WhiteBalance.SHADE);
+ } else if (Camera.Parameters.WHITE_BALANCE_TWILIGHT.equals(wb)) {
+ mSupportedWhiteBalances.add(WhiteBalance.TWILIGHT);
+ } else if (Camera.Parameters.WHITE_BALANCE_WARM_FLUORESCENT.equals(wb)) {
+ mSupportedWhiteBalances.add(WhiteBalance.WARM_FLUORESCENT);
+ }
+ }
+ }
+ }
+
+ private static class FpsComparator implements Comparator<int[]> {
+ @Override
+ public int compare(int[] fps1, int[] fps2) {
+ return (fps1[0] == fps2[0] ? fps1[1] - fps2[1] : fps1[0] - fps2[0]);
+ }
+ }
+
+ private static class SizeComparator implements Comparator<Size> {
+
+ @Override
+ public int compare(Size size1, Size size2) {
+ return (size1.width() == size2.width() ? size1.height() - size2.height() :
+ size1.width() - size2.width());
+ }
+ }
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java
new file mode 100644
index 0000000..ceab7fe
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/AndroidCameraSettings.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014 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.hardware.Camera;
+
+/**
+ * The subclass of {@link CameraSettings} for Android Camera 1 API.
+ */
+public class AndroidCameraSettings extends CameraSettings {
+ private static final String TRUE = "true";
+ private static final String RECORDING_HINT = "recording-hint";
+
+ public AndroidCameraSettings(CameraCapabilities capabilities, Camera.Parameters params) {
+ CameraCapabilities.Stringifier stringifier = capabilities.getStringifier();
+
+ // Preview
+ Camera.Size paramPreviewSize = params.getPreviewSize();
+ setPreviewSize(new Size(paramPreviewSize.width, paramPreviewSize.height));
+ setPreviewFrameRate(params.getPreviewFrameRate());
+ int[] previewFpsRange = new int[2];
+ params.getPreviewFpsRange(previewFpsRange);
+ setPreviewFpsRange(previewFpsRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+ previewFpsRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+ setPreviewFormat(params.getPreviewFormat());
+
+ // Capture: Focus, flash, zoom, exposure, scene mode.
+ if (capabilities.supports(CameraCapabilities.Feature.ZOOM)) {
+ setZoomRatio(params.getZoomRatios().get(params.getZoom()) / 100f);
+ setZoomIndex(params.getZoom());
+ } else {
+ setZoomRatio(1.0f);
+ setZoomIndex(0);
+ }
+ setExposureCompensationIndex(params.getExposureCompensation());
+ setFlashMode(stringifier.flashModeFromString(params.getFlashMode()));
+ setFocusMode(stringifier.focusModeFromString(params.getFocusMode()));
+ setSceneMode(stringifier.sceneModeFromString(params.getSceneMode()));
+
+ // Video capture.
+ if (capabilities.supports(CameraCapabilities.Feature.VIDEO_STABILIZATION)) {
+ setVideoStabilization(isVideoStabilizationEnabled());
+ }
+ setRecordingHintEnabled(TRUE.equals(params.get(RECORDING_HINT)));
+
+ // Output: Photo size, compression quality
+ setPhotoJpegCompressionQuality(params.getJpegQuality());
+ Camera.Size paramPictureSize = params.getPictureSize();
+ setPhotoSize(new Size(paramPictureSize.width, paramPictureSize.height));
+ setPhotoFormat(params.getPictureFormat());
+ }
+
+ public AndroidCameraSettings(AndroidCameraSettings other) {
+ super(other);
+ }
+
+ @Override
+ public CameraSettings copy() {
+ return new AndroidCameraSettings(this);
+ }
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraActions.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraActions.java
new file mode 100644
index 0000000..aae122b
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraActions.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 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;
+
+class CameraActions {
+ // Camera initialization/finalization
+ public static final int OPEN_CAMERA = 1;
+ public static final int RELEASE = 2;
+ public static final int RECONNECT = 3;
+ public static final int UNLOCK = 4;
+ public static final int LOCK = 5;
+ // Preview
+ public static final int SET_PREVIEW_TEXTURE_ASYNC = 101;
+ public static final int START_PREVIEW_ASYNC = 102;
+ public static final int STOP_PREVIEW = 103;
+ public static final int SET_PREVIEW_CALLBACK_WITH_BUFFER = 104;
+ public static final int ADD_CALLBACK_BUFFER = 105;
+ public static final int SET_PREVIEW_DISPLAY_ASYNC = 106;
+ public static final int SET_PREVIEW_CALLBACK = 107;
+ public static final int SET_ONE_SHOT_PREVIEW_CALLBACK = 108;
+ // Parameters
+ public static final int SET_PARAMETERS = 201;
+ public static final int GET_PARAMETERS = 202;
+ public static final int REFRESH_PARAMETERS = 203;
+ public static final int APPLY_SETTINGS = 204;
+ // Focus, Zoom
+ public static final int AUTO_FOCUS = 301;
+ public static final int CANCEL_AUTO_FOCUS = 302;
+ public static final int SET_AUTO_FOCUS_MOVE_CALLBACK = 303;
+ public static final int SET_ZOOM_CHANGE_LISTENER = 304;
+ // Face detection
+ public static final int SET_FACE_DETECTION_LISTENER = 461;
+ public static final int START_FACE_DETECTION = 462;
+ public static final int STOP_FACE_DETECTION = 463;
+ public static final int SET_ERROR_CALLBACK = 464;
+ // Presentation
+ public static final int ENABLE_SHUTTER_SOUND = 501;
+ public static final int SET_DISPLAY_ORIENTATION = 502;
+ // Capture
+ public static final int CAPTURE_PHOTO = 601;
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java
new file mode 100644
index 0000000..dd4f77c
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java
@@ -0,0 +1,830 @@
+/*
+ * Copyright (C) 2012 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.OnZoomChangeListener;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.SurfaceHolder;
+
+import com.android.ex.camera2.portability.debug.Log;
+
+/**
+ * An interface which provides possible camera device operations.
+ *
+ * The client should call {@code CameraAgent.openCamera} to get an instance
+ * of {@link CameraAgent.CameraProxy} to control the camera. Classes
+ * implementing this interface should have its own one unique {@code Thread}
+ * other than the main thread for camera operations. Camera device callbacks
+ * are wrapped since the client should not deal with
+ * {@code android.hardware.Camera} directly.
+ *
+ * TODO: provide callback interfaces for:
+ * {@code android.hardware.Camera.ErrorCallback},
+ * {@code android.hardware.Camera.OnZoomChangeListener}, and
+ */
+public abstract class CameraAgent {
+ public static final long CAMERA_OPERATION_TIMEOUT_MS = 2500;
+
+ private static final Log.Tag TAG = new Log.Tag("CamAgnt");
+
+ public 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();
+ }});
+ }
+ }
+
+ /**
+ * A callback helps to invoke the original callback on another
+ * {@link android.os.Handler}.
+ */
+ public 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, final String info) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onDeviceOpenFailure(cameraId, info);
+ }});
+ }
+
+ @Override
+ public void onDeviceOpenedAlready(final int cameraId, final String info) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onDeviceOpenedAlready(cameraId, info);
+ }});
+ }
+
+ @Override
+ public void onReconnectionFailure(final CameraAgent mgr, final String info) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onReconnectionFailure(mgr, info);
+ }});
+ }
+ }
+
+ /**
+ * A handler for all camera api runtime exceptions.
+ * The default behavior is to throw the runtime exception.
+ */
+ public static interface CameraExceptionCallback {
+ public void onCameraException(RuntimeException e);
+ }
+
+ /**
+ * An interface which wraps
+ * {@link android.hardware.Camera.ErrorCallback}
+ */
+ public static interface CameraErrorCallback {
+ public void onError(int error, CameraProxy camera);
+ }
+
+ /**
+ * An interface which wraps
+ * {@link android.hardware.Camera.AutoFocusCallback}.
+ */
+ public static interface CameraAFCallback {
+ public void onAutoFocus(boolean focused, CameraProxy camera);
+ }
+
+ /**
+ * An interface which wraps
+ * {@link android.hardware.Camera.AutoFocusMoveCallback}.
+ */
+ public static interface CameraAFMoveCallback {
+ public void onAutoFocusMoving(boolean moving, CameraProxy camera);
+ }
+
+ /**
+ * An interface which wraps
+ * {@link android.hardware.Camera.ShutterCallback}.
+ */
+ public static interface CameraShutterCallback {
+ public void onShutter(CameraProxy camera);
+ }
+
+ /**
+ * An interface which wraps
+ * {@link android.hardware.Camera.PictureCallback}.
+ */
+ public static interface CameraPictureCallback {
+ public void onPictureTaken(byte[] data, CameraProxy camera);
+ }
+
+ /**
+ * An interface which wraps
+ * {@link android.hardware.Camera.PreviewCallback}.
+ */
+ public static interface CameraPreviewDataCallback {
+ public void onPreviewFrame(byte[] data, CameraProxy camera);
+ }
+
+ /**
+ * An interface which wraps
+ * {@link android.hardware.Camera.FaceDetectionListener}.
+ */
+ public static interface CameraFaceDetectionCallback {
+ /**
+ * Callback for face detection.
+ *
+ * @param faces Recognized face in the preview.
+ * @param camera The camera which the preview image comes from.
+ */
+ public void onFaceDetection(Camera.Face[] faces, CameraProxy camera);
+ }
+
+ /**
+ * An interface to be called when the camera preview has started.
+ */
+ public static interface CameraStartPreviewCallback {
+ /**
+ * Callback when the preview starts.
+ */
+ public void onPreviewStarted();
+ }
+
+ /**
+ * An interface to be called for any events when opening or closing the
+ * camera device. This error callback is different from the one defined
+ * in the framework, {@link android.hardware.Camera.ErrorCallback}, which
+ * is used after the camera is opened.
+ */
+ public static interface CameraOpenCallback {
+ /**
+ * Callback when camera open succeeds.
+ */
+ public void onCameraOpened(CameraProxy camera);
+
+ /**
+ * Callback when {@link com.android.camera.CameraDisabledException} is
+ * caught.
+ *
+ * @param cameraId The disabled camera.
+ */
+ public void onCameraDisabled(int cameraId);
+
+ /**
+ * Callback when {@link com.android.camera.CameraHardwareException} is
+ * caught.
+ *
+ * @param cameraId The camera with the hardware failure.
+ * @param info The extra info regarding this failure.
+ */
+ public void onDeviceOpenFailure(int cameraId, String info);
+
+ /**
+ * Callback when trying to open the camera which is already opened.
+ *
+ * @param cameraId The camera which is causing the open error.
+ */
+ public void onDeviceOpenedAlready(int cameraId, String info);
+
+ /**
+ * Callback when {@link java.io.IOException} is caught during
+ * {@link android.hardware.Camera#reconnect()}.
+ *
+ * @param mgr The {@link CameraAgent}
+ * with the reconnect failure.
+ */
+ public void onReconnectionFailure(CameraAgent mgr, String info);
+ }
+
+ /**
+ * Opens the camera of the specified ID asynchronously. The camera device
+ * will be opened in the camera handler thread and will be returned through
+ * the {@link CameraAgent.CameraOpenCallback#
+ * onCameraOpened(com.android.camera.cameradevice.CameraAgent.CameraProxy)}.
+ *
+ * @param handler The {@link android.os.Handler} in which the callback
+ * was handled.
+ * @param callback The callback for the result.
+ * @param cameraId The camera ID to open.
+ */
+ public void openCamera(final Handler handler, final int cameraId,
+ final CameraOpenCallback callback) {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().obtainMessage(CameraActions.OPEN_CAMERA, cameraId, 0,
+ CameraOpenCallbackForward.getNewInstance(handler, callback)).sendToTarget();
+ }});
+ }
+
+ /**
+ * Closes the camera device.
+ *
+ * @param camera The camera to close. {@code null} means all.
+ * @param synced Whether this call should be synchronous.
+ */
+ public void closeCamera(CameraProxy camera, boolean synced) {
+ if (synced) {
+ final WaitDoneBundle bundle = new WaitDoneBundle();
+
+ getDispatchThread().runJobSync(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().obtainMessage(CameraActions.RELEASE).sendToTarget();
+ getCameraHandler().post(bundle.mUnlockRunnable);
+ }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "camera release");
+ } else {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().removeCallbacksAndMessages(null);
+ getCameraHandler().obtainMessage(CameraActions.RELEASE).sendToTarget();
+ }});
+ }
+ }
+
+ /**
+ * Sets a callback for handling camera api runtime exceptions on
+ * a handler.
+ */
+ public abstract void setCameraDefaultExceptionCallback(CameraExceptionCallback callback,
+ Handler handler);
+
+ /**
+ * Recycles the resources used by this instance. CameraAgent will be in
+ * an unusable state after calling this.
+ */
+ public abstract void recycle();
+
+ /**
+ * @return The camera devices info.
+ */
+ public abstract CameraDeviceInfo getCameraDeviceInfo();
+
+ /**
+ * @return The handler to which camera tasks should be posted.
+ */
+ protected abstract Handler getCameraHandler();
+
+ /**
+ * @return The thread used on which client callbacks are served.
+ */
+ protected abstract DispatchThread getDispatchThread();
+
+ /**
+ * An interface that takes camera operation requests and post messages to the
+ * camera handler thread. All camera operations made through this interface is
+ * asynchronous by default except those mentioned specifically.
+ */
+ public static abstract class CameraProxy {
+
+ /**
+ * Returns the underlying {@link android.hardware.Camera} object used
+ * by this proxy. This method should only be used when handing the
+ * camera device over to {@link android.media.MediaRecorder} for
+ * recording.
+ */
+ @Deprecated
+ public abstract android.hardware.Camera getCamera();
+
+ /**
+ * @return The camera ID associated to by this
+ * {@link CameraAgent.CameraProxy}.
+ */
+ public abstract int getCameraId();
+
+ /**
+ * @return The camera characteristics.
+ */
+ public abstract CameraDeviceInfo.Characteristics getCharacteristics();
+
+ /**
+ * @return The camera capabilities.
+ */
+ public abstract CameraCapabilities getCapabilities();
+
+ /**
+ * Reconnects to the camera device. On success, the camera device will
+ * be returned through {@link CameraAgent
+ * .CameraOpenCallback#onCameraOpened(com.android.camera.cameradevice.CameraAgent
+ * .CameraProxy)}.
+ * @see android.hardware.Camera#reconnect()
+ *
+ * @param handler The {@link android.os.Handler} in which the callback
+ * was handled.
+ * @param cb The callback when any error happens.
+ */
+ public void reconnect(final Handler handler, final CameraOpenCallback cb) {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().obtainMessage(CameraActions.RECONNECT, getCameraId(), 0,
+ CameraOpenCallbackForward.getNewInstance(handler, cb)).sendToTarget();
+ }});
+ }
+
+ /**
+ * Unlocks the camera device.
+ *
+ * @see android.hardware.Camera#unlock()
+ */
+ public void unlock() {
+ final WaitDoneBundle bundle = new WaitDoneBundle();
+ getDispatchThread().runJobSync(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().sendEmptyMessage(CameraActions.UNLOCK);
+ getCameraHandler().post(bundle.mUnlockRunnable);
+ }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "camera unlock");
+ }
+
+ /**
+ * Locks the camera device.
+ * @see android.hardware.Camera#lock()
+ */
+ public void lock() {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().sendEmptyMessage(CameraActions.LOCK);
+ }});
+ }
+
+ /**
+ * Sets the {@link android.graphics.SurfaceTexture} for preview.
+ *
+ * @param surfaceTexture The {@link SurfaceTexture} for preview.
+ */
+ public void setPreviewTexture(final SurfaceTexture surfaceTexture) {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler()
+ .obtainMessage(CameraActions.SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture)
+ .sendToTarget();
+ }});
+ }
+
+ /**
+ * Blocks until a {@link android.graphics.SurfaceTexture} has been set
+ * for preview.
+ *
+ * @param surfaceTexture The {@link SurfaceTexture} for preview.
+ */
+ public void setPreviewTextureSync(final SurfaceTexture surfaceTexture) {
+ final WaitDoneBundle bundle = new WaitDoneBundle();
+ getDispatchThread().runJobSync(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler()
+ .obtainMessage(CameraActions.SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture)
+ .sendToTarget();
+ getCameraHandler().post(bundle.mUnlockRunnable);
+ }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "set preview texture");
+ }
+
+ /**
+ * Sets the {@link android.view.SurfaceHolder} for preview.
+ *
+ * @param surfaceHolder The {@link SurfaceHolder} for preview.
+ */
+ public void setPreviewDisplay(final SurfaceHolder surfaceHolder) {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler()
+ .obtainMessage(CameraActions.SET_PREVIEW_DISPLAY_ASYNC, surfaceHolder)
+ .sendToTarget();
+ }});
+ }
+
+ /**
+ * Starts the camera preview.
+ */
+ public void startPreview() {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler()
+ .obtainMessage(CameraActions.START_PREVIEW_ASYNC, null).sendToTarget();
+ }});
+ }
+
+ /**
+ * Starts the camera preview and executes a callback on a handler once
+ * the preview starts.
+ */
+ public void startPreviewWithCallback(final Handler h, final CameraStartPreviewCallback cb) {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().obtainMessage(CameraActions.START_PREVIEW_ASYNC,
+ CameraStartPreviewCallbackForward.getNewInstance(h, cb))
+ .sendToTarget();
+ }});
+ }
+
+ /**
+ * Stops the camera preview synchronously.
+ * {@code stopPreview()} must be synchronous to ensure that the caller can
+ * continues to release resources related to camera preview.
+ */
+ public void stopPreview() {
+ final WaitDoneBundle bundle = new WaitDoneBundle();
+ getDispatchThread().runJobSync(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().sendEmptyMessage(CameraActions.STOP_PREVIEW);
+ getCameraHandler().post(bundle.mUnlockRunnable);
+ }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "stop preview");
+ }
+
+ /**
+ * Sets the callback for preview data.
+ *
+ * @param handler The {@link android.os.Handler} in which the callback was handled.
+ * @param cb The callback to be invoked when the preview data is available.
+ * @see android.hardware.Camera#setPreviewCallback(android.hardware.Camera.PreviewCallback)
+ */
+ public abstract void setPreviewDataCallback(Handler handler, CameraPreviewDataCallback cb);
+
+ /**
+ * Sets the one-time callback for preview data.
+ *
+ * @param handler The {@link android.os.Handler} in which the callback was handled.
+ * @param cb The callback to be invoked when the preview data for
+ * next frame is available.
+ * @see android.hardware.Camera#setPreviewCallback(android.hardware.Camera.PreviewCallback)
+ */
+ public abstract void setOneShotPreviewCallback(Handler handler,
+ CameraPreviewDataCallback cb);
+
+ /**
+ * Sets the callback for preview data.
+ *
+ * @param handler The handler in which the callback will be invoked.
+ * @param cb The callback to be invoked when the preview data is available.
+ * @see android.hardware.Camera#setPreviewCallbackWithBuffer(android.hardware.Camera.PreviewCallback)
+ */
+ public abstract void setPreviewDataCallbackWithBuffer(Handler handler,
+ CameraPreviewDataCallback cb);
+
+ /**
+ * Adds buffer for the preview callback.
+ *
+ * @param callbackBuffer The buffer allocated for the preview data.
+ */
+ public void addCallbackBuffer(final byte[] callbackBuffer) {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler()
+ .obtainMessage(CameraActions.ADD_CALLBACK_BUFFER, callbackBuffer)
+ .sendToTarget();
+ }
+ });
+ }
+
+ /**
+ * Starts the auto-focus process. The result will be returned through the callback.
+ *
+ * @param handler The handler in which the callback will be invoked.
+ * @param cb The auto-focus callback.
+ */
+ public abstract void autoFocus(Handler handler, CameraAFCallback cb);
+
+ /**
+ * Cancels the auto-focus process.
+ */
+ public void cancelAutoFocus() {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().removeMessages(CameraActions.AUTO_FOCUS);
+ getCameraHandler().sendEmptyMessage(CameraActions.CANCEL_AUTO_FOCUS);
+ }});
+ }
+
+ /**
+ * Sets the auto-focus callback
+ *
+ * @param handler The handler in which the callback will be invoked.
+ * @param cb The callback to be invoked when the preview data is available.
+ */
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ public abstract void setAutoFocusMoveCallback(Handler handler, CameraAFMoveCallback cb);
+
+ /**
+ * Instrument the camera to take a picture.
+ *
+ * @param handler The handler in which the callback will be invoked.
+ * @param shutter The callback for shutter action, may be null.
+ * @param raw The callback for uncompressed data, may be null.
+ * @param postview The callback for postview image data, may be null.
+ * @param jpeg The callback for jpeg image data, may be null.
+ * @see android.hardware.Camera#takePicture(
+ * android.hardware.Camera.ShutterCallback,
+ * android.hardware.Camera.PictureCallback,
+ * android.hardware.Camera.PictureCallback)
+ */
+ public abstract void takePicture(
+ Handler handler,
+ CameraShutterCallback shutter,
+ CameraPictureCallback raw,
+ CameraPictureCallback postview,
+ CameraPictureCallback jpeg);
+
+ /**
+ * Sets the display orientation for camera to adjust the preview and JPEG orientation.
+ *
+ * @param degrees The counterclockwise rotation in degrees, relative to the device's natural
+ * orientation. Should be 0, 90, 180 or 270.
+ */
+ public void setDisplayOrientation(final int degrees) {
+ setDisplayOrientation(degrees, true);
+ }
+
+ /**
+ * Sets the display orientation for camera to adjust the preview&mdash;and, optionally,
+ * JPEG&mdash;orientations.
+ * <p>If capture rotation is not requested, future captures will be returned in the sensor's
+ * physical rotation, which does not necessarily match the device's natural orientation.</p>
+ *
+ * @param degrees The counterclockwise rotation in degrees, relative to the device's natural
+ * orientation. Should be 0, 90, 180 or 270.
+ * @param capture Whether to adjust the JPEG capture orientation as well as the preview one.
+ */
+ public void setDisplayOrientation(final int degrees, final boolean capture) {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler()
+ .obtainMessage(CameraActions.SET_DISPLAY_ORIENTATION, degrees,
+ capture ? 1 : 0)
+ .sendToTarget();
+ }});
+ }
+
+ /**
+ * Sets the listener for zoom change.
+ *
+ * @param listener The listener.
+ */
+ public abstract void setZoomChangeListener(OnZoomChangeListener listener);
+
+ /**
+ * Sets the face detection listener.
+ *
+ * @param handler The handler in which the callback will be invoked.
+ * @param callback The callback for face detection results.
+ */
+ public abstract void setFaceDetectionCallback(Handler handler,
+ CameraFaceDetectionCallback callback);
+
+ /**
+ * Starts the face detection.
+ */
+ public void startFaceDetection() {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().sendEmptyMessage(CameraActions.START_FACE_DETECTION);
+ }});
+ }
+
+ /**
+ * Stops the face detection.
+ */
+ public void stopFaceDetection() {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().sendEmptyMessage(CameraActions.STOP_FACE_DETECTION);
+ }});
+ }
+
+ /**
+ * Registers an error callback.
+ *
+ * @param handler The handler on which the callback will be invoked.
+ * @param cb The error callback.
+ * @see android.hardware.Camera#setErrorCallback(android.hardware.Camera.ErrorCallback)
+ */
+ public abstract void setErrorCallback(Handler handler, CameraErrorCallback cb);
+
+ /**
+ * Sets the camera parameters.
+ *
+ * @param params The camera parameters to use.
+ */
+ @Deprecated
+ public abstract void setParameters(Camera.Parameters params);
+
+ /**
+ * Gets the current camera parameters synchronously. This method is
+ * synchronous since the caller has to wait for the camera to return
+ * the parameters. If the parameters are already cached, it returns
+ * immediately.
+ */
+ @Deprecated
+ public abstract Camera.Parameters getParameters();
+
+ /**
+ * Gets the current camera settings synchronously.
+ * <p>This method is synchronous since the caller has to wait for the
+ * camera to return the parameters. If the parameters are already
+ * cached, it returns immediately.</p>
+ */
+ public abstract CameraSettings getSettings();
+
+ /**
+ * Default implementation of {@link #applySettings(CameraSettings)}
+ * that is only missing the set of states it needs to wait for
+ * before applying the settings.
+ *
+ * @param settings The settings to use on the device.
+ * @param statesToAwait Bitwise OR of the required camera states.
+ * @return Whether the settings can be applied.
+ */
+ protected boolean applySettingsHelper(CameraSettings settings,
+ final int statesToAwait) {
+ if (settings == null) {
+ Log.v(TAG, "null parameters in applySettings()");
+ return false;
+ }
+ if (!getCapabilities().supports(settings)) {
+ return false;
+ }
+
+ final CameraSettings copyOfSettings = settings.copy();
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraState().waitForStates(statesToAwait);
+ getCameraHandler().obtainMessage(CameraActions.APPLY_SETTINGS, copyOfSettings)
+ .sendToTarget();
+ }});
+ return true;
+ }
+
+ /**
+ * Applies the settings to the camera device.
+ *
+ * @param settings The settings to use on the device.
+ * @return Whether the settings can be applied.
+ */
+ public abstract boolean applySettings(CameraSettings settings);
+
+ /**
+ * Forces {@code CameraProxy} to update the cached version of the camera
+ * settings regardless of the dirty bit.
+ */
+ public void refreshSettings() {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().sendEmptyMessage(CameraActions.REFRESH_PARAMETERS);
+ }});
+ }
+
+ /**
+ * Enables/Disables the camera shutter sound.
+ *
+ * @param enable {@code true} to enable the shutter sound,
+ * {@code false} to disable it.
+ */
+ public void enableShutterSound(final boolean enable) {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler()
+ .obtainMessage(CameraActions.ENABLE_SHUTTER_SOUND, (enable ? 1 : 0), 0)
+ .sendToTarget();
+ }});
+ }
+
+ /**
+ * Dumps the current settings of the camera device.
+ *
+ * <p>The content varies based on the underlying camera API settings
+ * implementation.</p>
+ *
+ * @return The content of the device settings represented by a string.
+ */
+ public abstract String dumpDeviceSettings();
+
+ /**
+ * @return The handler to which camera tasks should be posted.
+ */
+ public abstract Handler getCameraHandler();
+
+ /**
+ * @return The thread used on which client callbacks are served.
+ */
+ public abstract DispatchThread getDispatchThread();
+
+ /**
+ * @return The state machine tracking the camera API's current mode.
+ */
+ public abstract CameraStateHolder getCameraState();
+ }
+
+ public static class WaitDoneBundle {
+ public final Runnable mUnlockRunnable;
+ public final Object mWaitLock;
+
+ WaitDoneBundle() {
+ mWaitLock = new Object();
+ mUnlockRunnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mWaitLock) {
+ mWaitLock.notifyAll();
+ }
+ }};
+ }
+ }
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraAgentFactory.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgentFactory.java
new file mode 100644
index 0000000..77ad0c3
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgentFactory.java
@@ -0,0 +1,166 @@
+/*
+ * 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.content.Context;
+import android.os.Build;
+
+import com.android.ex.camera2.portability.debug.Log;
+import com.android.ex.camera2.portability.util.SystemProperties;
+
+/**
+ * A factory class for {@link CameraAgent}.
+ *
+ * <p>The choice of framework API to use can be made automatically based on the
+ * system API level, explicitly forced by the client app, or overridden entirely
+ * by setting the system property com.camera2.portability.fwk_api to 1 or 2.</p>
+ */
+public class CameraAgentFactory {
+ private static final Log.Tag TAG = new Log.Tag("CamAgntFact");
+
+ /** Android release replacing the Camera class with the camera2 package. */
+ private static final int FIRST_SDK_WITH_API_2 = 21;
+
+ // The debugging override, which overrides *all* API level selections if set
+ // to API_LEVEL_OVERRIDE_API{1,2}; otherwise, this has no effect. Note that
+ // we check this once when the library is first loaded so that #recycle()
+ // doesn't try to clean up the wrong type of CameraAgent.
+ private static final String API_LEVEL_OVERRIDE_KEY = "camera2.portability.force_api";
+ private static final String API_LEVEL_OVERRIDE_DEFAULT = "0";
+ private static final String API_LEVEL_OVERRIDE_API1 = "1";
+ private static final String API_LEVEL_OVERRIDE_API2 = "2";
+ private static final String API_LEVEL_OVERRIDE_VALUE =
+ SystemProperties.get(API_LEVEL_OVERRIDE_KEY, API_LEVEL_OVERRIDE_DEFAULT);
+
+ private static CameraAgent sAndroidCameraAgent;
+ private static CameraAgent sAndroidCamera2Agent;
+ private static int sAndroidCameraAgentClientCount;
+ private static int sAndroidCamera2AgentClientCount;
+
+ /**
+ * Used to indicate which camera framework should be used.
+ */
+ public static enum CameraApi {
+ /** Automatically select based on the device's SDK level. */
+ AUTO,
+
+ /** Use the {@link android.hardware.Camera} class. */
+ API_1,
+
+ /** Use the {@link android.hardware.camera2} package. */
+ API_2
+ };
+
+ private static CameraApi highestSupportedApi() {
+ // TODO: Check SDK_INT instead of RELEASE before L launch
+ if (Build.VERSION.SDK_INT >= FIRST_SDK_WITH_API_2 || Build.VERSION.CODENAME.equals("L")) {
+ return CameraApi.API_2;
+ } else {
+ return CameraApi.API_1;
+ }
+ }
+
+ private static CameraApi validateApiChoice(CameraApi choice) {
+ if (API_LEVEL_OVERRIDE_VALUE.equals(API_LEVEL_OVERRIDE_API1)) {
+ Log.d(TAG, "API level overridden by system property: forced to 1");
+ return CameraApi.API_1;
+ } else if (API_LEVEL_OVERRIDE_VALUE.equals(API_LEVEL_OVERRIDE_API2)) {
+ Log.d(TAG, "API level overridden by system property: forced to 2");
+ return CameraApi.API_2;
+ }
+
+ if (choice == null) {
+ Log.w(TAG, "null API level request, so assuming AUTO");
+ choice = CameraApi.AUTO;
+ }
+ if (choice == CameraApi.AUTO) {
+ choice = highestSupportedApi();
+ }
+
+ return choice;
+ }
+
+ /**
+ * Returns the android camera implementation of
+ * {@link com.android.camera.cameradevice.CameraAgent}.
+ *
+ * <p>To clean up the resources allocated by this call, be sure to invoke
+ * {@link #recycle(boolean)} with the same {@code api} value provided
+ * here.</p>
+ *
+ * @param context The application context.
+ * @param api Which camera framework to use.
+ * @return The {@link CameraAgent} to control the camera device.
+ *
+ * @throws UnsupportedOperationException If {@code CameraApi.API_2} was
+ * requested on an unsupported device.
+ */
+ public static synchronized CameraAgent getAndroidCameraAgent(Context context, CameraApi api) {
+ api = validateApiChoice(api);
+
+ if (api == CameraApi.API_1) {
+ if (sAndroidCameraAgent == null) {
+ sAndroidCameraAgent = new AndroidCameraAgentImpl();
+ sAndroidCameraAgentClientCount = 1;
+ } else {
+ ++sAndroidCameraAgentClientCount;
+ }
+ return sAndroidCameraAgent;
+ } else { // API_2
+ if (highestSupportedApi() == CameraApi.API_1) {
+ throw new UnsupportedOperationException("Camera API_2 unavailable on this device");
+ }
+
+ if (sAndroidCamera2Agent == null) {
+ sAndroidCamera2Agent = new AndroidCamera2AgentImpl(context);
+ sAndroidCamera2AgentClientCount = 1;
+ } else {
+ ++sAndroidCamera2AgentClientCount;
+ }
+ return sAndroidCamera2Agent;
+ }
+ }
+
+ /**
+ * Recycles the resources. Always call this method when the activity is
+ * stopped.
+ *
+ * @param api Which camera framework handle to recycle.
+ *
+ * @throws UnsupportedOperationException If {@code CameraApi.API_2} was
+ * requested on an unsupported device.
+ */
+ public static synchronized void recycle(CameraApi api) {
+ api = validateApiChoice(api);
+
+ if (api == CameraApi.API_1) {
+ if (--sAndroidCameraAgentClientCount == 0 && sAndroidCameraAgent != null) {
+ sAndroidCameraAgent.recycle();
+ sAndroidCameraAgent = null;
+ }
+ } else { // API_2
+ if (highestSupportedApi() == CameraApi.API_1) {
+ throw new UnsupportedOperationException("Camera API_2 unavailable on this device");
+ }
+
+ if (--sAndroidCamera2AgentClientCount == 0 && sAndroidCamera2Agent != null) {
+ sAndroidCamera2Agent.recycle();
+ sAndroidCamera2Agent = null;
+ }
+ }
+ }
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java
new file mode 100644
index 0000000..6a4c72c
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilities.java
@@ -0,0 +1,765 @@
+/*
+ * Copyright (C) 2014 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 com.android.ex.camera2.portability.debug.Log;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * This class holds all the static information of a camera's capabilities.
+ * <p>
+ * The design of this class is thread-safe and can be passed around regardless
+ * of which thread using it.
+ * </p>
+ */
+public class CameraCapabilities {
+
+ private static Log.Tag TAG = new Log.Tag("CamCapabs");
+
+ /* All internal states are declared final and should be thread-safe. */
+
+ protected final ArrayList<int[]> mSupportedPreviewFpsRange = new ArrayList<int[]>();
+ protected final ArrayList<Size> mSupportedPreviewSizes = new ArrayList<Size>();
+ protected final TreeSet<Integer> mSupportedPreviewFormats = new TreeSet<Integer>();
+ protected final ArrayList<Size> mSupportedVideoSizes = new ArrayList<Size>();
+ protected final ArrayList<Size> mSupportedPhotoSizes = new ArrayList<Size>();
+ protected final TreeSet<Integer> mSupportedPhotoFormats = new TreeSet<Integer>();
+ protected final EnumSet<SceneMode> mSupportedSceneModes = EnumSet.noneOf(SceneMode.class);
+ protected final EnumSet<FlashMode> mSupportedFlashModes = EnumSet.noneOf(FlashMode.class);
+ protected final EnumSet<FocusMode> mSupportedFocusModes = EnumSet.noneOf(FocusMode.class);
+ protected final EnumSet<WhiteBalance> mSupportedWhiteBalances =
+ EnumSet.noneOf(WhiteBalance.class);
+ protected final EnumSet<Feature> mSupportedFeatures = EnumSet.noneOf(Feature.class);
+ protected Size mPreferredPreviewSizeForVideo;
+ protected int mMinExposureCompensation;
+ protected int mMaxExposureCompensation;
+ protected float mExposureCompensationStep;
+ protected int mMaxNumOfFacesSupported;
+ protected int mMaxNumOfFocusAreas;
+ protected int mMaxNumOfMeteringArea;
+ protected int mMaxZoomRatio;
+ protected float mHorizontalViewAngle;
+ protected float mVerticalViewAngle;
+ private final Stringifier mStringifier;
+ protected final ArrayList<Integer> mZoomRatioList = new ArrayList<Integer>();
+ protected int mMaxZoomIndex;
+
+ /**
+ * Focus modes.
+ */
+ public enum FocusMode {
+ /**
+ * Continuous auto focus mode intended for taking pictures.
+ * @see {@link android.hardware.Camera.Parameters#FOCUS_MODE_AUTO}.
+ */
+ AUTO,
+ /**
+ * Continuous auto focus mode intended for taking pictures.
+ * @see {@link android.hardware.Camera.Parameters#FOCUS_MODE_CONTINUOUS_PICTURE}.
+ */
+ CONTINUOUS_PICTURE,
+ /**
+ * Continuous auto focus mode intended for video recording.
+ * @see {@link android.hardware.Camera.Parameters#FOCUS_MODE_CONTINUOUS_VIDEO}.
+ */
+ CONTINUOUS_VIDEO,
+ /**
+ * Extended depth of field (EDOF).
+ * @see {@link android.hardware.Camera.Parameters#FOCUS_MODE_EDOF}.
+ */
+ EXTENDED_DOF,
+ /**
+ * Focus is fixed.
+ * @see {@link android.hardware.Camera.Parameters#FOCUS_MODE_FIXED}.
+ */
+ FIXED,
+ /**
+ * Focus is set at infinity.
+ * @see {@link android.hardware.Camera.Parameters#FOCUS_MODE_INFINITY}.
+ */
+ // TODO: Unsupported on API 2
+ INFINITY,
+ /**
+ * Macro (close-up) focus mode.
+ * @see {@link android.hardware.Camera.Parameters#FOCUS_MODE_MACRO}.
+ */
+ MACRO,
+ }
+
+ /**
+ * Flash modes.
+ */
+ public enum FlashMode {
+ /**
+ * No flash.
+ */
+ NO_FLASH,
+ /**
+ * Flash will be fired automatically when required.
+ * @see {@link android.hardware.Camera.Parameters#FLASH_MODE_OFF}.
+ */
+ AUTO,
+ /**
+ * Flash will not be fired.
+ * @see {@link android.hardware.Camera.Parameters#FLASH_MODE_OFF}.
+ */
+ OFF,
+ /**
+ * Flash will always be fired during snapshot.
+ * @see {@link android.hardware.Camera.Parameters#FLASH_MODE_ON}.
+ */
+ ON,
+ /**
+ * Constant emission of light during preview, auto-focus and snapshot.
+ * @see {@link android.hardware.Camera.Parameters#FLASH_MODE_TORCH}.
+ */
+ TORCH,
+ /**
+ * Flash will be fired in red-eye reduction mode.
+ * @see {@link android.hardware.Camera.Parameters#FLASH_MODE_RED_EYE}.
+ */
+ RED_EYE,
+ }
+
+ /**
+ * Scene modes.
+ */
+ public enum SceneMode {
+ /**
+ * No supported scene mode.
+ */
+ NO_SCENE_MODE,
+ /**
+ * Scene mode is off.
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_AUTO}.
+ */
+ AUTO,
+ /**
+ * Take photos of fast moving objects.
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_ACTION}.
+ */
+ ACTION,
+ /**
+ * Applications are looking for a barcode.
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_BARCODE}.
+ */
+ BARCODE,
+ /**
+ * Take pictures on the beach.
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_BEACH}.
+ */
+ BEACH,
+ /**
+ * Capture the naturally warm color of scenes lit by candles.
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_CANDLELIGHT}.
+ */
+ CANDLELIGHT,
+ /**
+ * For shooting firework displays.
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_FIREWORKS}.
+ */
+ FIREWORKS,
+ /**
+ * Capture a scene using high dynamic range imaging techniques.
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_HDR}.
+ */
+ // TODO: Unsupported on API 2
+ HDR,
+ /**
+ * Take pictures on distant objects.
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_LANDSCAPE}.
+ */
+ LANDSCAPE,
+ /**
+ * Take photos at night.
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_NIGHT}.
+ */
+ NIGHT,
+ /**
+ * Take people pictures at night.
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_NIGHT_PORTRAIT}.
+ */
+ // TODO: Unsupported on API 2
+ NIGHT_PORTRAIT,
+ /**
+ * Take indoor low-light shot.
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_PARTY}.
+ */
+ PARTY,
+ /**
+ * Take people pictures.
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_PORTRAIT}.
+ */
+ PORTRAIT,
+ /**
+ * Take pictures on the snow.
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_SNOW}.
+ */
+ SNOW,
+ /**
+ * Take photos of fast moving objects.
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_SPORTS}.
+ */
+ SPORTS,
+ /**
+ * Avoid blurry pictures (for example, due to hand shake).
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_STEADYPHOTO}.
+ */
+ STEADYPHOTO,
+ /**
+ * Take sunset photos.
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_SUNSET}.
+ */
+ SUNSET,
+ /**
+ * Take photos in a theater.
+ * @see {@link android.hardware.Camera.Parameters#SCENE_MODE_THEATRE}.
+ */
+ THEATRE,
+ }
+
+ /**
+ * White blances.
+ */
+ public enum WhiteBalance {
+ /**
+ * @see {@link android.hardware.Camera.Parameters#WHITE_BALANCE_AUTO}.
+ */
+ AUTO,
+ /**
+ * @see {@link android.hardware.Camera.Parameters#WHITE_BALANCE_CLOUDY_DAYLIGHT}.
+ */
+ CLOUDY_DAYLIGHT,
+ /**
+ * @see {@link android.hardware.Camera.Parameters#WHITE_BALANCE_DAYLIGHT}.
+ */
+ DAYLIGHT,
+ /**
+ * @see {@link android.hardware.Camera.Parameters#WHITE_BALANCE_FLUORESCENT}.
+ */
+ FLUORESCENT,
+ /**
+ * @see {@link android.hardware.Camera.Parameters#WHITE_BALANCE_INCANDESCENT}.
+ */
+ INCANDESCENT,
+ /**
+ * @see {@link android.hardware.Camera.Parameters#WHITE_BALANCE_SHADE}.
+ */
+ SHADE,
+ /**
+ * @see {@link android.hardware.Camera.Parameters#WHITE_BALANCE_TWILIGHT}.
+ */
+ TWILIGHT,
+ /**
+ * @see {@link android.hardware.Camera.Parameters#WHITE_BALANCE_WARM_FLUORESCENT}.
+ */
+ WARM_FLUORESCENT,
+ }
+
+ /**
+ * Features.
+ */
+ public enum Feature {
+ /**
+ * Support zoom-related methods.
+ */
+ ZOOM,
+ /**
+ * Support for photo capturing during video recording.
+ */
+ VIDEO_SNAPSHOT,
+ /**
+ * Support for focus area settings.
+ */
+ FOCUS_AREA,
+ /**
+ * Support for metering area settings.
+ */
+ METERING_AREA,
+ /**
+ * Support for automatic exposure lock.
+ */
+ AUTO_EXPOSURE_LOCK,
+ /**
+ * Support for automatic white balance lock.
+ */
+ AUTO_WHITE_BALANCE_LOCK,
+ /**
+ * Support for video stabilization.
+ */
+ VIDEO_STABILIZATION,
+ }
+
+ /**
+ * A interface stringifier to convert abstract representations to API
+ * related string representation.
+ */
+ public static class Stringifier {
+ /**
+ * Converts the string to hyphen-delimited lowercase for compatibility with multiple APIs.
+ *
+ * @param enumCase The name of an enum constant.
+ * @return The converted string.
+ */
+ private static String toApiCase(String enumCase) {
+ return enumCase.toLowerCase().replaceAll("_", "-");
+ }
+
+ /**
+ * Conerts the string to underscore-delimited uppercase to match the enum constant names.
+ *
+ * @param apiCase An API-related string representation.
+ * @return The converted string.
+ */
+ private static String toEnumCase(String apiCase) {
+ return apiCase.toUpperCase().replaceAll("-", "_");
+ }
+
+ /**
+ * Converts the focus mode to API-related string representation.
+ *
+ * @param focus The focus mode to convert.
+ * @return The string used by the camera framework API to represent the
+ * focus mode.
+ */
+ public String stringify(FocusMode focus) {
+ return toApiCase(focus.name());
+ }
+
+ /**
+ * Converts the API-related string representation of the focus mode to the
+ * abstract representation.
+ *
+ * @param val The string representation.
+ * @return The focus mode represented by the input string, or the focus
+ * mode with the lowest ordinal if it cannot be converted.
+ */
+ public FocusMode focusModeFromString(String val) {
+ if (val == null) {
+ return FocusMode.values()[0];
+ }
+ try {
+ return FocusMode.valueOf(toEnumCase(val));
+ } catch (IllegalArgumentException ex) {
+ return FocusMode.values()[0];
+ }
+ }
+
+ /**
+ * Converts the flash mode to API-related string representation.
+ *
+ * @param flash The focus mode to convert.
+ * @return The string used by the camera framework API to represent the
+ * flash mode.
+ */
+ public String stringify(FlashMode flash) {
+ return toApiCase(flash.name());
+ }
+
+ /**
+ * Converts the API-related string representation of the flash mode to the
+ * abstract representation.
+ *
+ * @param val The string representation.
+ * @return The flash mode represented by the input string, or the flash
+ * mode with the lowest ordinal if it cannot be converted.
+ */
+ public FlashMode flashModeFromString(String val) {
+ if (val == null) {
+ return FlashMode.values()[0];
+ }
+ try {
+ return FlashMode.valueOf(toEnumCase(val));
+ } catch (IllegalArgumentException ex) {
+ return FlashMode.values()[0];
+ }
+ }
+
+ /**
+ * Converts the scene mode to API-related string representation.
+ *
+ * @param scene The focus mode to convert.
+ * @return The string used by the camera framework API to represent the
+ * scene mode.
+ */
+ public String stringify(SceneMode scene) {
+ return toApiCase(scene.name());
+ }
+
+ /**
+ * Converts the API-related string representation of the scene mode to the
+ * abstract representation.
+ *
+ * @param val The string representation.
+ * @return The scene mode represented by the input string, or the scene
+ * mode with the lowest ordinal if it cannot be converted.
+ */
+ public SceneMode sceneModeFromString(String val) {
+ if (val == null) {
+ return SceneMode.values()[0];
+ }
+ try {
+ return SceneMode.valueOf(toEnumCase(val));
+ } catch (IllegalArgumentException ex) {
+ return SceneMode.values()[0];
+ }
+ }
+
+ /**
+ * Converts the white balance to API-related string representation.
+ *
+ * @param wb The focus mode to convert.
+ * @return The string used by the camera framework API to represent the
+ * white balance.
+ */
+ public String stringify(WhiteBalance wb) {
+ return toApiCase(wb.name());
+ }
+
+ /**
+ * Converts the API-related string representation of the white balance to
+ * the abstract representation.
+ *
+ * @param val The string representation.
+ * @return The white balance represented by the input string, or the
+ * white balance with the lowest ordinal if it cannot be
+ * converted.
+ */
+ public WhiteBalance whiteBalanceFromString(String val) {
+ if (val == null) {
+ return WhiteBalance.values()[0];
+ }
+ try {
+ return WhiteBalance.valueOf(toEnumCase(val));
+ } catch (IllegalArgumentException ex) {
+ return WhiteBalance.values()[0];
+ }
+ }
+ }
+
+ /**
+ * Constructor.
+ * @param stringifier The API-specific stringifier for this instance.
+ */
+ CameraCapabilities(Stringifier stringifier) {
+ mStringifier = stringifier;
+ }
+
+ /**
+ * Copy constructor.
+ * @param src The source instance.
+ */
+ public CameraCapabilities(CameraCapabilities src) {
+ mSupportedPreviewFpsRange.addAll(src.mSupportedPreviewFpsRange);
+ mSupportedPreviewSizes.addAll(src.mSupportedPreviewSizes);
+ mSupportedPreviewFormats.addAll(src.mSupportedPreviewFormats);
+ mSupportedVideoSizes.addAll(src.mSupportedVideoSizes);
+ mSupportedPhotoSizes.addAll(src.mSupportedPhotoSizes);
+ mSupportedPhotoFormats.addAll(src.mSupportedPhotoFormats);
+ mSupportedSceneModes.addAll(src.mSupportedSceneModes);
+ mSupportedFlashModes.addAll(src.mSupportedFlashModes);
+ mSupportedFocusModes.addAll(src.mSupportedFocusModes);
+ mSupportedWhiteBalances.addAll(src.mSupportedWhiteBalances);
+ mSupportedFeatures.addAll(src.mSupportedFeatures);
+ mPreferredPreviewSizeForVideo = src.mPreferredPreviewSizeForVideo;
+ mMaxExposureCompensation = src.mMaxExposureCompensation;
+ mMinExposureCompensation = src.mMinExposureCompensation;
+ mExposureCompensationStep = src.mExposureCompensationStep;
+ mMaxNumOfFacesSupported = src.mMaxNumOfFacesSupported;
+ mMaxNumOfFocusAreas = src.mMaxNumOfFocusAreas;
+ mMaxNumOfMeteringArea = src.mMaxNumOfMeteringArea;
+ mMaxZoomIndex = src.mMaxZoomIndex;
+ mZoomRatioList.addAll(src.mZoomRatioList);
+ mMaxZoomRatio = src.mMaxZoomRatio;
+ mHorizontalViewAngle = src.mHorizontalViewAngle;
+ mVerticalViewAngle = src.mVerticalViewAngle;
+ mStringifier = src.mStringifier;
+ }
+
+ public float getHorizontalViewAngle() {
+ return mHorizontalViewAngle;
+ }
+
+ public float getVerticalViewAngle() {
+ return mVerticalViewAngle;
+ }
+
+ /**
+ * @return the supported picture formats. See {@link android.graphics.ImageFormat}.
+ */
+ public Set<Integer> getSupportedPhotoFormats() {
+ return new TreeSet<Integer>(mSupportedPhotoFormats);
+ }
+
+ /**
+ * Gets the supported preview formats.
+ * @return The supported preview {@link android.graphics.ImageFormat}s.
+ */
+ public Set<Integer> getSupportedPreviewFormats() {
+ return new TreeSet<Integer>(mSupportedPreviewFormats);
+ }
+
+ /**
+ * Gets the supported picture sizes.
+ */
+ public List<Size> getSupportedPhotoSizes() {
+ return new ArrayList<Size>(mSupportedPhotoSizes);
+ }
+
+ /**
+ * @return The supported preview fps (frame-per-second) ranges. The returned
+ * list is sorted by maximum fps then minimum fps in a descending order.
+ * The values are multiplied by 1000.
+ */
+ public final List<int[]> getSupportedPreviewFpsRange() {
+ return new ArrayList<int[]>(mSupportedPreviewFpsRange);
+ }
+
+ /**
+ * @return The supported preview sizes. The list is sorted by width then
+ * height in a descending order.
+ */
+ public final List<Size> getSupportedPreviewSizes() {
+ return new ArrayList<Size>(mSupportedPreviewSizes);
+ }
+
+ public final Size getPreferredPreviewSizeForVideo() {
+ return new Size(mPreferredPreviewSizeForVideo);
+ }
+
+ /**
+ * @return The supported video frame sizes that can be used by MediaRecorder.
+ * The list is sorted by width then height in a descending order.
+ */
+ public final List<Size> getSupportedVideoSizes() {
+ return new ArrayList<Size>(mSupportedVideoSizes);
+ }
+
+ /**
+ * @return The supported scene modes.
+ */
+ public final Set<SceneMode> getSupportedSceneModes() {
+ return new HashSet<SceneMode>(mSupportedSceneModes);
+ }
+
+ /**
+ * @return Whether the scene mode is supported.
+ */
+ public final boolean supports(SceneMode scene) {
+ return (scene != null && mSupportedSceneModes.contains(scene));
+ }
+
+ public boolean supports(final CameraSettings settings) {
+ if (zoomCheck(settings) && exposureCheck(settings) && focusCheck(settings) &&
+ flashCheck(settings) && photoSizeCheck(settings) && previewSizeCheck(settings) &&
+ videoStabilizationCheck(settings)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return The supported flash modes.
+ */
+ public final Set<FlashMode> getSupportedFlashModes() {
+ return new HashSet<FlashMode>(mSupportedFlashModes);
+ }
+
+ /**
+ * @return Whether the flash mode is supported.
+ */
+ public final boolean supports(FlashMode flash) {
+ return (flash != null && mSupportedFlashModes.contains(flash));
+ }
+
+ /**
+ * @return The supported focus modes.
+ */
+ public final Set<FocusMode> getSupportedFocusModes() {
+ return new HashSet<FocusMode>(mSupportedFocusModes);
+ }
+
+ /**
+ * @return Whether the focus mode is supported.
+ */
+ public final boolean supports(FocusMode focus) {
+ return (focus != null && mSupportedFocusModes.contains(focus));
+ }
+
+ /**
+ * @return The supported white balanceas.
+ */
+ public final Set<WhiteBalance> getSupportedWhiteBalance() {
+ return new HashSet<WhiteBalance>(mSupportedWhiteBalances);
+ }
+
+ /**
+ * @return Whether the white balance is supported.
+ */
+ public boolean supports(WhiteBalance wb) {
+ return (wb != null && mSupportedWhiteBalances.contains(wb));
+ }
+
+ public final Set<Feature> getSupportedFeature() {
+ return new HashSet<Feature>(mSupportedFeatures);
+ }
+
+ public boolean supports(Feature ft) {
+ return (ft != null && mSupportedFeatures.contains(ft));
+ }
+
+ /**
+ * @return The maximal supported zoom ratio.
+ */
+ public float getMaxZoomRatio() {
+ return mMaxZoomRatio;
+ }
+
+ // We'll replace these old style methods with new ones.
+ @Deprecated
+ public int getMaxZoomIndex() {
+ return mMaxZoomIndex;
+ }
+
+ @Deprecated
+ public List<Integer> getZoomRatioList() {
+ return new ArrayList<Integer>(mZoomRatioList);
+ }
+
+ /**
+ * @return The min exposure compensation index. The EV is the compensation
+ * index multiplied by the step value. If unsupported, both this method and
+ * {@link #getMaxExposureCompensation()} return 0.
+ */
+ public final int getMinExposureCompensation() {
+ return mMinExposureCompensation;
+ }
+
+ /**
+ * @return The max exposure compensation index. The EV is the compensation
+ * index multiplied by the step value. If unsupported, both this method and
+ * {@link #getMinExposureCompensation()} return 0.
+ */
+ public final int getMaxExposureCompensation() {
+ return mMaxExposureCompensation;
+ }
+
+ /**
+ * @return The exposure compensation step. The EV is the compensation index
+ * multiplied by the step value.
+ */
+ public final float getExposureCompensationStep() {
+ return mExposureCompensationStep;
+ }
+
+ /**
+ * @return The max number of faces supported by the face detection. 0 if
+ * unsupported.
+ */
+ public final int getMaxNumOfFacesSupported() {
+ return mMaxNumOfFacesSupported;
+ }
+
+ /**
+ * @return The stringifier used by this instance.
+ */
+ public Stringifier getStringifier() {
+ return mStringifier;
+ }
+
+ private boolean zoomCheck(final CameraSettings settings) {
+ final float ratio = settings.getCurrentZoomRatio();
+ final int index = settings.getCurrentZoomIndex();
+ if (!supports(Feature.ZOOM)) {
+ if (ratio != 1.0f || index != 0) {
+ Log.v(TAG, "Zoom is not supported");
+ return false;
+ }
+ } else {
+ if (settings.getCurrentZoomRatio() > getMaxZoomRatio() ||
+ index > getMaxZoomIndex()) {
+ Log.v(TAG, "Zoom ratio is not supported: ratio = " +
+ settings.getCurrentZoomRatio() + ", index = " + index);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean exposureCheck(final CameraSettings settings) {
+ final int index = settings.getExposureCompensationIndex();
+ if (index > getMaxExposureCompensation() || index < getMinExposureCompensation()) {
+ Log.v(TAG, "Exposure compensation index is not supported. Min = " +
+ getMinExposureCompensation() + ", max = " + getMaxExposureCompensation() + "," +
+ " setting = " + index);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean focusCheck(final CameraSettings settings) {
+ FocusMode focusMode = settings.getCurrentFocusMode();
+ if (!supports(focusMode)) {
+ Log.v(TAG,
+ "Focus mode not supported:" + (focusMode != null ? focusMode.name() : "null"));
+ return false;
+ }
+ return true;
+ }
+
+ private boolean flashCheck(final CameraSettings settings) {
+ FlashMode flashMode = settings.getCurrentFlashMode();
+ if (!supports(flashMode)) {
+ Log.v(TAG,
+ "Flash mode not supported:" + (flashMode != null ? flashMode.name() : "null"));
+ return false;
+ }
+ return true;
+ }
+
+ private boolean photoSizeCheck(final CameraSettings settings) {
+ Size photoSize = settings.getCurrentPhotoSize();
+ if (mSupportedPhotoSizes.contains(photoSize)) {
+ return true;
+ }
+ Log.v(TAG, "Unsupported photo size:" + photoSize);
+ return false;
+ }
+
+ private boolean previewSizeCheck(final CameraSettings settings) {
+ final Size previewSize = settings.getCurrentPreviewSize();
+ if (mSupportedPreviewSizes.contains(previewSize)) {
+ return true;
+ }
+ Log.v(TAG, "Unsupported preview size:" + previewSize);
+ return false;
+ }
+
+ private boolean videoStabilizationCheck(final CameraSettings settings) {
+ if (!settings.isVideoStabilizationEnabled() || supports(Feature.VIDEO_STABILIZATION)) {
+ return true;
+ }
+ Log.v(TAG, "Video stabilization is not supported");
+ return false;
+ }
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilitiesFactory.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilitiesFactory.java
new file mode 100644
index 0000000..6c2bc54
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraCapabilitiesFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014 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.hardware.Camera;
+
+import com.android.ex.camera2.portability.debug.Log;
+
+public class CameraCapabilitiesFactory {
+
+ private static Log.Tag TAG = new Log.Tag("CamCapabsFact");
+
+ public static CameraCapabilities createFrom(Camera.Parameters p) {
+ if (p == null) {
+ Log.w(TAG, "Null parameter passed in.");
+ return null;
+ }
+ return new AndroidCameraCapabilities(p);
+ }
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraDeviceInfo.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraDeviceInfo.java
new file mode 100644
index 0000000..72a641e
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraDeviceInfo.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2014 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.graphics.Matrix;
+import android.graphics.RectF;
+
+import com.android.ex.camera2.portability.debug.Log;
+
+/**
+ * The device info for all attached cameras.
+ */
+public interface CameraDeviceInfo {
+
+ static final int NO_DEVICE = -1;
+
+ /**
+ * @param cameraId Which device to interrogate.
+ * @return The static characteristics of the specified device, or {@code null} on error.
+ */
+ Characteristics getCharacteristics(int cameraId);
+
+ /**
+ * @return The total number of the available camera devices.
+ */
+ int getNumberOfCameras();
+
+ /**
+ * @return The first (lowest) ID of the back cameras or {@code NO_DEVICE}
+ * if not available.
+ */
+ int getFirstBackCameraId();
+
+ /**
+ * @return The first (lowest) ID of the front cameras or {@code NO_DEVICE}
+ * if not available.
+ */
+ int getFirstFrontCameraId();
+
+ /**
+ * Device characteristics for a single camera.
+ */
+ public abstract class Characteristics {
+ private static final Log.Tag TAG = new Log.Tag("CamDvcInfChar");
+
+ /**
+ * @return Whether the camera faces the back of the device.
+ */
+ public abstract boolean isFacingBack();
+
+ /**
+ * @return Whether the camera faces the device's screen.
+ */
+ public abstract boolean isFacingFront();
+
+ /**
+ * @return The camera sensor orientation, or the counterclockwise angle
+ * from its natural position that the device must be held at
+ * for the sensor to be right side up (in degrees, always a
+ * multiple of 90, and between 0 and 270, inclusive).
+ */
+ public abstract int getSensorOrientation();
+
+ /**
+ * @param currentDisplayOrientation
+ * The current display orientation, measured counterclockwise
+ * from to the device's natural orientation (in degrees, always
+ * a multiple of 90, and between 0 and 270, inclusive).
+ * @return
+ * The relative preview image orientation, or the clockwise
+ * rotation angle that must be applied to display preview
+ * frames in the matching orientation, accounting for implicit
+ * mirroring, if applicable (in degrees, always a multiple of
+ * 90, and between 0 and 270, inclusive).
+ */
+ public int getPreviewOrientation(int currentDisplayOrientation) {
+ // Drivers tend to mirror the image during front camera preview.
+ return getRelativeImageOrientation(currentDisplayOrientation, true);
+ }
+
+ /**
+ * @param currentDisplayOrientation
+ * The current display orientation, measured counterclockwise
+ * from to the device's natural orientation (in degrees, always
+ * a multiple of 90, and between 0 and 270, inclusive).
+ * @return
+ * The relative capture image orientation, or the clockwise
+ * rotation angle that must be applied to display these frames
+ * in the matching orientation (in degrees, always a multiple
+ * of 90, and between 0 and 270, inclusive).
+ */
+ public int getJpegOrientation(int currentDisplayOrientation) {
+ // Don't mirror during capture!
+ return getRelativeImageOrientation(currentDisplayOrientation, false);
+ }
+
+ /**
+ * @param currentDisplayOrientaiton
+ * {@link #getPreviewOrientation}, {@link #getJpegOrientation}
+ * @param compensateForMirroring
+ * Whether to account for mirroring in the case of front-facing
+ * cameras, which is necessary iff the OS/driver is
+ * automatically reflecting the image.
+ * @return
+ * {@link #getPreviewOrientation}, {@link #getJpegOrientation}
+ *
+ * @see android.hardware.Camera.setDisplayOrientation
+ */
+ protected int getRelativeImageOrientation(int currentDisplayOrientation,
+ boolean compensateForMirroring) {
+ if (!orientationIsValid(currentDisplayOrientation)) {
+ return 0;
+ }
+
+ int result = 0;
+ if (isFacingFront()) {
+ result = (getSensorOrientation() + currentDisplayOrientation) % 360;
+ if (compensateForMirroring) {
+ result = (360 - result) % 360;
+ }
+ } else if (isFacingBack()) {
+ result = (getSensorOrientation() - currentDisplayOrientation + 360) % 360;
+ } else {
+ Log.e(TAG, "Camera is facing unhandled direction");
+ }
+ return result;
+ }
+
+ /**
+ * @param currentDisplayOrientation
+ * The current display orientation, measured counterclockwise
+ * from to the device's natural orientation (in degrees, always
+ * a multiple of 90, and between 0 and 270, inclusive).
+ * @param surfaceDimensions
+ * The dimensions of the {@link android.view.Surface} on which
+ * the preview image is being rendered. It usually only makes
+ * sense for the upper-left corner to be at the origin.
+ * @return
+ * The transform matrix that should be applied to the
+ * {@link android.view.Surface} in order for the image to
+ * display properly in the device's current orientation.
+ */
+ public Matrix getPreviewTransform(int currentDisplayOrientation, RectF surfaceDimensions) {
+ return getPreviewTransform(currentDisplayOrientation, surfaceDimensions,
+ new RectF(surfaceDimensions));
+ }
+
+ /**
+ * @param currentDisplayOrientation
+ * The current display orientation, measured counterclockwise
+ * from to the device's natural orientation (in degrees, always
+ * a multiple of 90, and between 0 and 270, inclusive).
+ * @param surfaceDimensions
+ * The dimensions of the {@link android.view.Surface} on which
+ * the preview image is being rendered. It usually only makes
+ * sense for the upper-left corner to be at the origin.
+ * @param desiredBounds
+ * The boundaries within the {@link android.view.Surface} where
+ * the final image should appear. These can be used to
+ * translate and scale the output, but note that the image will
+ * be stretched to fit, possibly changing its aspect ratio.
+ * @return
+ * The transform matrix that should be applied to the
+ * {@link android.view.Surface} in order for the image to
+ * display properly in the device's current orientation.
+ */
+ public Matrix getPreviewTransform(int currentDisplayOrientation, RectF surfaceDimensions,
+ RectF desiredBounds) {
+ if (!orientationIsValid(currentDisplayOrientation) ||
+ surfaceDimensions.equals(desiredBounds)) {
+ return new Matrix();
+ }
+
+ Matrix transform = new Matrix();
+ transform.setRectToRect(surfaceDimensions, desiredBounds, Matrix.ScaleToFit.FILL);
+ return transform;
+ }
+
+ /**
+ * @return Whether the shutter sound can be disabled.
+ */
+ public abstract boolean canDisableShutterSound();
+
+ protected static boolean orientationIsValid(int angle) {
+ if (angle % 90 != 0) {
+ Log.e(TAG, "Provided display orientation is not divisible by 90");
+ return false;
+ }
+ if (angle < 0 || angle > 270) {
+ Log.e(TAG, "Provided display orientation is outside expected range");
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java
new file mode 100644
index 0000000..26d0f85
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraSettings.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2014 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.hardware.Camera;
+
+import com.android.ex.camera2.portability.debug.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * A class which stores the camera settings.
+ */
+public abstract class CameraSettings {
+ private static final Log.Tag TAG = new Log.Tag("CamSet");
+
+ // Attempts to provide a value outside this range will be ignored.
+ private static final int MIN_JPEG_COMPRESSION_QUALITY = 1;
+ private static final int MAX_JPEG_COMPRESSION_QUALITY = 100;
+
+ protected final Map<String, String> mGeneralSetting = new TreeMap<>();
+ protected final List<Camera.Area> mMeteringAreas = new ArrayList<>();
+ protected final List<Camera.Area> mFocusAreas = new ArrayList<>();
+ protected int mPreviewFpsRangeMin;
+ protected int mPreviewFpsRangeMax;
+ protected int mPreviewFrameRate;
+ protected Size mCurrentPreviewSize;
+ private int mCurrentPreviewFormat;
+ protected Size mCurrentPhotoSize;
+ protected byte mJpegCompressQuality;
+ protected int mCurrentPhotoFormat;
+ protected float mCurrentZoomRatio;
+ protected int mCurrentZoomIndex;
+ protected int mExposureCompensationIndex;
+ protected CameraCapabilities.FlashMode mCurrentFlashMode;
+ protected CameraCapabilities.FocusMode mCurrentFocusMode;
+ protected CameraCapabilities.SceneMode mCurrentSceneMode;
+ protected CameraCapabilities.WhiteBalance mWhiteBalance;
+ protected boolean mVideoStabilizationEnabled;
+ protected boolean mAutoExposureLocked;
+ protected boolean mAutoWhiteBalanceLocked;
+ protected boolean mRecordingHintEnabled;
+ protected GpsData mGpsData;
+ protected Size mExifThumbnailSize = new Size(0,0);
+
+ /**
+ * An immutable class storing GPS related information.
+ * <p>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.</p>
+ */
+ public static class GpsData {
+ public final double latitude;
+ public final double longitude;
+ public final double altitude;
+ public final long timeStamp;
+ public final String processingMethod;
+
+ /** Constructor. */
+ public GpsData(double latitude, double longitude, double altitude, long timeStamp,
+ String processingMethod) {
+ this.latitude = latitude;
+ this.longitude = longitude;
+ this.altitude = altitude;
+ this.timeStamp = timeStamp;
+ this.processingMethod = processingMethod;
+ }
+
+ /** Copy constructor. */
+ public GpsData(GpsData src) {
+ this.latitude = src.latitude;
+ this.longitude = src.longitude;
+ this.altitude = src.altitude;
+ this.timeStamp = src.timeStamp;
+ this.processingMethod = src.processingMethod;
+ }
+ }
+
+ protected CameraSettings() {
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * @param src The source settings.
+ * @return The copy of the source.
+ */
+ protected CameraSettings(CameraSettings src) {
+ mGeneralSetting.putAll(src.mGeneralSetting);
+ mMeteringAreas.addAll(src.mMeteringAreas);
+ mFocusAreas.addAll(src.mFocusAreas);
+ mPreviewFpsRangeMin = src.mPreviewFpsRangeMin;
+ mPreviewFpsRangeMax = src.mPreviewFpsRangeMax;
+ mPreviewFrameRate = src.mPreviewFrameRate;
+ mCurrentPreviewSize =
+ (src.mCurrentPreviewSize == null ? null : new Size(src.mCurrentPreviewSize));
+ mCurrentPreviewFormat = src.mCurrentPreviewFormat;
+ mCurrentPhotoSize =
+ (src.mCurrentPhotoSize == null ? null : new Size(src.mCurrentPhotoSize));
+ mJpegCompressQuality = src.mJpegCompressQuality;
+ mCurrentPhotoFormat = src.mCurrentPhotoFormat;
+ mCurrentZoomRatio = src.mCurrentZoomRatio;
+ mCurrentZoomIndex = src.mCurrentZoomIndex;
+ mExposureCompensationIndex = src.mExposureCompensationIndex;
+ mCurrentFlashMode = src.mCurrentFlashMode;
+ mCurrentFocusMode = src.mCurrentFocusMode;
+ mCurrentSceneMode = src.mCurrentSceneMode;
+ mWhiteBalance = src.mWhiteBalance;
+ mVideoStabilizationEnabled = src.mVideoStabilizationEnabled;
+ mAutoExposureLocked = src.mAutoExposureLocked;
+ mAutoWhiteBalanceLocked = src.mAutoWhiteBalanceLocked;
+ mRecordingHintEnabled = src.mRecordingHintEnabled;
+ mGpsData = src.mGpsData;
+ mExifThumbnailSize = src.mExifThumbnailSize;
+ }
+
+ /**
+ * @return A copy of this object, as an instance of the implementing class.
+ */
+ public abstract CameraSettings copy();
+
+ /** General setting **/
+ @Deprecated
+ public void setSetting(String key, String value) {
+ mGeneralSetting.put(key, value);
+ }
+
+ /** Preview **/
+
+ /**
+ * Sets the preview FPS range. This call will invalidate prior calls to
+ * {@link #setPreviewFrameRate(int)}.
+ *
+ * @param min The min FPS.
+ * @param max The max FPS.
+ */
+ public void setPreviewFpsRange(int min, int max) {
+ if (min > max) {
+ int temp = max;
+ max = min;
+ min = temp;
+ }
+ mPreviewFpsRangeMax = max;
+ mPreviewFpsRangeMin = min;
+ mPreviewFrameRate = -1;
+ }
+
+ /**
+ * @return The min of the preview FPS range.
+ */
+ public int getPreviewFpsRangeMin() {
+ return mPreviewFpsRangeMin;
+ }
+
+ /**
+ * @return The max of the preview FPS range.
+ */
+ public int getPreviewFpsRangeMax() {
+ return mPreviewFpsRangeMax;
+ }
+
+ /**
+ * Sets the preview FPS. This call will invalidate prior calls to
+ * {@link #setPreviewFpsRange(int, int)}.
+ *
+ * @param frameRate The target frame rate.
+ */
+ public void setPreviewFrameRate(int frameRate) {
+ if (frameRate > 0) {
+ mPreviewFrameRate = frameRate;
+ mPreviewFpsRangeMax = frameRate;
+ mPreviewFpsRangeMin = frameRate;
+ }
+ }
+
+ public int getPreviewFrameRate() {
+ return mPreviewFrameRate;
+ }
+
+ /**
+ * @return The current preview size.
+ */
+ public Size getCurrentPreviewSize() {
+ return new Size(mCurrentPreviewSize);
+ }
+
+ /**
+ * @param previewSize The size to use for preview.
+ */
+ public void setPreviewSize(Size previewSize) {
+ mCurrentPreviewSize = new Size(previewSize);
+ }
+
+ /**
+ * Sets the preview format.
+ *
+ * @param format
+ * @see {@link android.graphics.ImageFormat}.
+ */
+ public void setPreviewFormat(int format) {
+ mCurrentPreviewFormat = format;
+ }
+
+ /**
+ * @return The preview format.
+ * @see {@link android.graphics.ImageFormat}.
+ */
+ public int getCurrentPreviewFormat() {
+ return mCurrentPreviewFormat;
+ }
+
+ /** Picture **/
+
+ /**
+ * @return The current photo size.
+ */
+ public Size getCurrentPhotoSize() {
+ return new Size(mCurrentPhotoSize);
+ }
+
+ /**
+ * Sets the size for the photo.
+ *
+ * @param photoSize The photo size.
+ */
+ public void setPhotoSize(Size photoSize) {
+ mCurrentPhotoSize = new Size(photoSize);
+ }
+
+ /**
+ * Sets the format for the photo.
+ *
+ * @param format The format for the photos taken.
+ * @see {@link android.graphics.ImageFormat}.
+ */
+ public void setPhotoFormat(int format) {
+ mCurrentPhotoFormat = format;
+ }
+
+ /**
+ * @return The format for the photos taken.
+ * @see {@link android.graphics.ImageFormat}.
+ */
+ public int getCurrentPhotoFormat() {
+ return mCurrentPhotoFormat;
+ }
+
+ /**
+ * Sets the JPEG compression quality.
+ *
+ * @param quality The quality for JPEG.
+ */
+ public void setPhotoJpegCompressionQuality(int quality) {
+ if (quality < MIN_JPEG_COMPRESSION_QUALITY || quality > MAX_JPEG_COMPRESSION_QUALITY) {
+ Log.w(TAG, "Ignoring JPEG quality that falls outside the expected range");
+ return;
+ }
+ // This is safe because the positive numbers go up to 127.
+ mJpegCompressQuality = (byte) quality;
+ }
+
+ public int getPhotoJpegCompressionQuality() {
+ return mJpegCompressQuality;
+ }
+
+ /** Zoom **/
+
+ /**
+ * @return The current zoom ratio. The min is 1.0f.
+ */
+ public float getCurrentZoomRatio() {
+ return mCurrentZoomRatio;
+ }
+
+ /**
+ * Sets the zoom ratio.
+ * @param ratio The new zoom ratio. Should be in the range between 1.0 to
+ * the value returned from {@link
+ * com.android.camera.cameradevice.CameraCapabilities#getMaxZoomRatio()}.
+ * @throws java.lang.UnsupportedOperationException if the ratio is not
+ * supported.
+ */
+ public void setZoomRatio(float ratio) {
+ mCurrentZoomRatio = ratio;
+ }
+
+ @Deprecated
+ public int getCurrentZoomIndex() {
+ return mCurrentZoomIndex;
+ }
+
+ @Deprecated
+ public void setZoomIndex(int index) {
+ mCurrentZoomIndex = index;
+ }
+
+ /** Exposure **/
+
+ public void setExposureCompensationIndex(int index) {
+ mExposureCompensationIndex = index;
+ }
+
+ /**
+ * @return The exposure compensation, with 0 meaning unadjusted.
+ */
+ public int getExposureCompensationIndex() {
+ return mExposureCompensationIndex;
+ }
+
+ public void setAutoExposureLock(boolean locked) {
+ mAutoExposureLocked = locked;
+ }
+
+ public boolean isAutoExposureLocked() {
+ return mAutoExposureLocked;
+ }
+
+ public void setMeteringAreas(List<Camera.Area> areas) {
+ mMeteringAreas.clear();
+ if (areas != null) {
+ mMeteringAreas.addAll(areas);
+ }
+ }
+
+ public List<Camera.Area> getMeteringAreas() {
+ return new ArrayList<Camera.Area>(mMeteringAreas);
+ }
+
+ /** Flash **/
+
+ public CameraCapabilities.FlashMode getCurrentFlashMode() {
+ return mCurrentFlashMode;
+ }
+
+ public void setFlashMode(CameraCapabilities.FlashMode flashMode) {
+ mCurrentFlashMode = flashMode;
+ }
+
+ /** Focus **/
+
+ /**
+ * Sets the focus mode.
+ * @param focusMode The focus mode to use.
+ */
+ public void setFocusMode(CameraCapabilities.FocusMode focusMode) {
+ mCurrentFocusMode = focusMode;
+ }
+
+ /**
+ * @return The current focus mode.
+ */
+ public CameraCapabilities.FocusMode getCurrentFocusMode() {
+ return mCurrentFocusMode;
+ }
+
+ /**
+ * @param areas The areas to focus.
+ */
+ public void setFocusAreas(List<Camera.Area> areas) {
+ mFocusAreas.clear();
+ if (areas != null) {
+ mFocusAreas.addAll(areas);
+ }
+ }
+
+ public List<Camera.Area> getFocusAreas() {
+ return new ArrayList<Camera.Area>(mFocusAreas);
+ }
+
+ /** White balance **/
+
+ public void setWhiteBalance(CameraCapabilities.WhiteBalance whiteBalance) {
+ mWhiteBalance = whiteBalance;
+ }
+
+ public CameraCapabilities.WhiteBalance getWhiteBalance() {
+ return mWhiteBalance;
+ }
+
+ public void setAutoWhiteBalanceLock(boolean locked) {
+ mAutoWhiteBalanceLocked = locked;
+ }
+
+ public boolean isAutoWhiteBalanceLocked() {
+ return mAutoWhiteBalanceLocked;
+ }
+
+ /** Scene mode **/
+
+ /**
+ * @return The current scene mode.
+ */
+ public CameraCapabilities.SceneMode getCurrentSceneMode() {
+ return mCurrentSceneMode;
+ }
+
+ /**
+ * Sets the scene mode for capturing.
+ *
+ * @param sceneMode The scene mode to use.
+ * @throws java.lang.UnsupportedOperationException if it's not supported.
+ */
+ public void setSceneMode(CameraCapabilities.SceneMode sceneMode) {
+ mCurrentSceneMode = sceneMode;
+ }
+
+ /** Other Features **/
+
+ public void setVideoStabilization(boolean enabled) {
+ mVideoStabilizationEnabled = enabled;
+ }
+
+ public boolean isVideoStabilizationEnabled() {
+ return mVideoStabilizationEnabled;
+ }
+
+ public void setRecordingHintEnabled(boolean hintEnabled) {
+ mRecordingHintEnabled = hintEnabled;
+ }
+
+ public boolean isRecordingHintEnabled() {
+ return mRecordingHintEnabled;
+ }
+
+ public void setGpsData(GpsData data) {
+ mGpsData = new GpsData(data);
+ }
+
+ public GpsData getGpsData() {
+ return (mGpsData == null ? null : new GpsData(mGpsData));
+ }
+
+ public void clearGpsData() {
+ mGpsData = null;
+ }
+
+ /**
+ * Sets the size of the thumbnail in EXIF header.
+ *
+ * @param s The size for the thumbnail. {@code null} will clear the size to
+ * (0,0).
+ */
+ public void setExifThumbnailSize(Size s) {
+ if (s != null) {
+ mExifThumbnailSize = s;
+ } else {
+ mExifThumbnailSize = new Size(0,0);
+ }
+ }
+
+ public Size getExifThumbnailSize() {
+ return new Size(mExifThumbnailSize);
+ }
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraStateHolder.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraStateHolder.java
new file mode 100644
index 0000000..35ae51c
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraStateHolder.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2014 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.os.SystemClock;
+
+import com.android.ex.camera2.portability.debug.Log;
+
+public abstract class CameraStateHolder {
+ private static final Log.Tag TAG = new Log.Tag("CamStateHolder");
+
+ private int mState;
+
+ public CameraStateHolder(int state) {
+ setState(state);
+ }
+
+ public synchronized void setState(int state) {
+ mState = state;
+ this.notifyAll();
+ }
+
+ public synchronized int getState() {
+ return mState;
+ }
+
+ private static 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 | getState()) == states;
+ }
+ }, CameraAgent.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 & getState()) == 0;
+ }
+ }, CameraAgent.CAMERA_OPERATION_TIMEOUT_MS);
+ }
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/DispatchThread.java b/camera2/portability/src/com/android/ex/camera2/portability/DispatchThread.java
new file mode 100644
index 0000000..bc77259
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/DispatchThread.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2014 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.os.Handler;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+
+import com.android.ex.camera2.portability.debug.Log;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+public class DispatchThread extends Thread {
+ private static final Log.Tag TAG = new Log.Tag("DispatchThread");
+ private static final long MAX_MESSAGE_QUEUE_LENGTH = 256;
+
+ private final Queue<Runnable> mJobQueue;
+ private Boolean mIsEnded;
+ private Handler mCameraHandler;
+ private HandlerThread mCameraHandlerThread;
+
+ public DispatchThread(Handler cameraHandler, HandlerThread cameraHandlerThread) {
+ super("Camera Job Dispatch Thread");
+ mJobQueue = new LinkedList<Runnable>();
+ mIsEnded = new Boolean(false);
+ mCameraHandler = cameraHandler;
+ mCameraHandlerThread = cameraHandlerThread;
+ }
+
+ /**
+ * 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 void runJobSync(final Runnable job, Object waitLock, long timeoutMs, String jobMsg) {
+ String timeoutMsg = "Timeout waiting " + timeoutMs + "ms for " + jobMsg;
+ synchronized (waitLock) {
+ long timeoutBound = SystemClock.uptimeMillis() + timeoutMs;
+ try {
+ runJob(job);
+ waitLock.wait(timeoutMs);
+ if (SystemClock.uptimeMillis() > timeoutBound) {
+ throw new IllegalStateException(timeoutMsg);
+ }
+ } catch (InterruptedException ex) {
+ if (SystemClock.uptimeMillis() > timeoutBound) {
+ throw new IllegalStateException(timeoutMsg);
+ }
+ }
+ }
+ }
+
+ /**
+ * 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.w(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();
+ }
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/HistoryHandler.java b/camera2/portability/src/com/android/ex/camera2/portability/HistoryHandler.java
new file mode 100644
index 0000000..ec2a555
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/HistoryHandler.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2014 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.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import java.util.LinkedList;
+
+class HistoryHandler extends Handler {
+ private static final int MAX_HISTORY_SIZE = 400;
+
+ final LinkedList<Integer> mMsgHistory;
+
+ HistoryHandler(Looper looper) {
+ super(looper);
+ mMsgHistory = new LinkedList<Integer>();
+ // We add a -1 at the beginning to mark the very beginning of the
+ // history.
+ mMsgHistory.offerLast(-1);
+ }
+
+ String generateHistoryString(int cameraId) {
+ String info = new String("HIST");
+ info += "_ID" + cameraId;
+ for (Integer msg : mMsgHistory) {
+ info = info + '_' + msg.toString();
+ }
+ info += "_HEND";
+ return info;
+ }
+
+ /**
+ * Subclasses' implementations should call this one before doing their work.
+ */
+ @Override
+ public void handleMessage(Message msg) {
+ mMsgHistory.offerLast(msg.what);
+ while (mMsgHistory.size() > MAX_HISTORY_SIZE) {
+ mMsgHistory.pollFirst();
+ }
+ }
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/Size.java b/camera2/portability/src/com/android/ex/camera2/portability/Size.java
new file mode 100644
index 0000000..042c443
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/Size.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2014 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.graphics.Point;
+import android.hardware.Camera;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An immutable simple size container.
+ */
+public class Size {
+ public static final String DELIMITER = ",";
+
+ /**
+ * An helper method to build a list of this class from a list of
+ * {@link android.hardware.Camera.Size}.
+ *
+ * @param cameraSizes Source.
+ * @return The built list.
+ */
+ public static List<Size> buildListFromCameraSizes(List<Camera.Size> cameraSizes) {
+ ArrayList<Size> list = new ArrayList<Size>(cameraSizes.size());
+ for (Camera.Size cameraSize : cameraSizes) {
+ list.add(new Size(cameraSize));
+ }
+ return list;
+ }
+
+ /**
+ * A helper method to build a list of this class from a list of {@link android.util.Size}.
+ *
+ * @param cameraSizes Source.
+ * @return The built list.
+ */
+ public static List<Size> buildListFromAndroidSizes(List<android.util.Size> androidSizes) {
+ ArrayList<Size> list = new ArrayList<Size>(androidSizes.size());
+ for (android.util.Size androidSize : androidSizes) {
+ list.add(new Size(androidSize));
+ }
+ return list;
+ }
+
+ /**
+ * Encode List of this class as comma-separated list of integers.
+ *
+ * @param sizes List of this class to encode.
+ * @return encoded string.
+ */
+ public static String listToString(List<Size> sizes) {
+ ArrayList<Integer> flatSizes = new ArrayList<>();
+ for (Size s : sizes) {
+ flatSizes.add(s.width());
+ flatSizes.add(s.height());
+ }
+ return TextUtils.join(DELIMITER, flatSizes);
+ }
+
+ /**
+ * Decode comma-separated even-length list of integers into a List of this class.
+ *
+ * @param encodedSizes encoded string.
+ * @return List of this class.
+ */
+ public static List<Size> stringToList(String encodedSizes) {
+ String[] flatSizes = TextUtils.split(encodedSizes, DELIMITER);
+ ArrayList<Size> list = new ArrayList<>();
+ for (int i = 0; i < flatSizes.length; i += 2) {
+ int width = Integer.parseInt(flatSizes[i]);
+ int height = Integer.parseInt(flatSizes[i + 1]);
+ list.add(new Size(width,height));
+ }
+ return list;
+ }
+
+ private final Point val;
+
+ /**
+ * Constructor.
+ */
+ public Size(int width, int height) {
+ val = new Point(width, height);
+ }
+
+ /**
+ * Copy constructor.
+ */
+ public Size(Size other) {
+ if (other == null) {
+ val = new Point(0, 0);
+ } else {
+ val = new Point(other.width(), other.height());
+ }
+ }
+
+ /**
+ * Constructor from a source {@link android.hardware.Camera.Size}.
+ *
+ * @param other The source size.
+ */
+ public Size(Camera.Size other) {
+ if (other == null) {
+ val = new Point(0, 0);
+ } else {
+ val = new Point(other.width, other.height);
+ }
+ }
+
+ /**
+ * Constructor from a source {@link android.util.Size}.
+ *
+ * @param other The source size.
+ */
+ public Size(android.util.Size other) {
+ if (other == null) {
+ val = new Point(0, 0);
+ } else {
+ val = new Point(other.getWidth(), other.getHeight());
+ }
+ }
+
+ /**
+ * Constructor from a source {@link android.graphics.Point}.
+ *
+ * @param p The source size.
+ */
+ public Size(Point p) {
+ if (p == null) {
+ val = new Point(0, 0);
+ } else {
+ val = new Point(p);
+ }
+ }
+
+ public int width() {
+ return val.x;
+ }
+
+ public int height() {
+ return val.y;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof Size) {
+ Size other = (Size) o;
+ return val.equals(other.val);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return val.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "Size: (" + this.width() + " x " + this.height() + ")";
+ }
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/debug/Log.java b/camera2/portability/src/com/android/ex/camera2/portability/debug/Log.java
new file mode 100644
index 0000000..07e758e
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/debug/Log.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2014 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.debug;
+
+public class Log {
+ /**
+ * All Camera logging using this class will use this tag prefix.
+ * Additionally, the prefix itself is checked in isLoggable and
+ * serves as an override. So, to toggle all logs allowed by the
+ * current {@link Configuration}, you can set properties:
+ *
+ * adb shell setprop log.tag.CAM2PORT_ VERBOSE
+ * adb shell setprop log.tag.CAM2PORT_ ""
+ */
+ public static final String CAMERA_LOGTAG_PREFIX = "CAM2PORT_";
+ private static final Log.Tag TAG = new Log.Tag("Log");
+
+ /**
+ * This class restricts the length of the log tag to be less than the
+ * framework limit and also prepends the common tag prefix defined by
+ * {@code CAMERA_LOGTAG_PREFIX}.
+ */
+ public static final class Tag {
+
+ // The length limit from Android framework is 23.
+ private static final int MAX_TAG_LEN = 23 - CAMERA_LOGTAG_PREFIX.length();
+
+ final String mValue;
+
+ public Tag(String tag) {
+ final int lenDiff = tag.length() - MAX_TAG_LEN;
+ if (lenDiff > 0) {
+ w(TAG, "Tag " + tag + " is " + lenDiff + " chars longer than limit.");
+ }
+ mValue = CAMERA_LOGTAG_PREFIX + (lenDiff > 0 ? tag.substring(0, MAX_TAG_LEN) : tag);
+ }
+
+ @Override
+ public String toString() {
+ return mValue;
+ }
+ }
+
+ public static void d(Tag tag, String msg) {
+ if (isLoggable(tag, android.util.Log.DEBUG)) {
+ android.util.Log.d(tag.toString(), msg);
+ }
+ }
+
+ public static void d(Tag tag, String msg, Throwable tr) {
+ if (isLoggable(tag, android.util.Log.DEBUG)) {
+ android.util.Log.d(tag.toString(), msg, tr);
+ }
+ }
+
+ public static void e(Tag tag, String msg) {
+ if (isLoggable(tag, android.util.Log.ERROR)) {
+ android.util.Log.e(tag.toString(), msg);
+ }
+ }
+
+ public static void e(Tag tag, String msg, Throwable tr) {
+ if (isLoggable(tag, android.util.Log.ERROR)) {
+ android.util.Log.e(tag.toString(), msg, tr);
+ }
+ }
+
+ public static void i(Tag tag, String msg) {
+ if (isLoggable(tag, android.util.Log.INFO)) {
+ android.util.Log.i(tag.toString(), msg);
+ }
+ }
+
+ public static void i(Tag tag, String msg, Throwable tr) {
+ if (isLoggable(tag, android.util.Log.INFO)) {
+ android.util.Log.i(tag.toString(), msg, tr);
+ }
+ }
+
+ public static void v(Tag tag, String msg) {
+ if (isLoggable(tag, android.util.Log.VERBOSE)) {
+ android.util.Log.v(tag.toString(), msg);
+ }
+ }
+
+ public static void v(Tag tag, String msg, Throwable tr) {
+ if (isLoggable(tag, android.util.Log.VERBOSE)) {
+ android.util.Log.v(tag.toString(), msg, tr);
+ }
+ }
+
+ public static void w(Tag tag, String msg) {
+ if (isLoggable(tag, android.util.Log.WARN)) {
+ android.util.Log.w(tag.toString(), msg);
+ }
+ }
+
+ public static void w(Tag tag, String msg, Throwable tr) {
+ if (isLoggable(tag, android.util.Log.WARN)) {
+ android.util.Log.w(tag.toString(), msg, tr);
+ }
+ }
+
+ private static boolean isLoggable(Tag tag, int level) {
+ try {
+ if (LogHelper.getOverrideLevel() != 0) {
+ // Override system log level and output. VERBOSE is smaller than
+ // ERROR, so the comparison checks that the override value is smaller
+ // than the desired output level. This applies to all tags.
+ return LogHelper.getOverrideLevel() <= level;
+ } else {
+ // The prefix can be used as an override tag to see all camera logs
+ return android.util.Log.isLoggable(CAMERA_LOGTAG_PREFIX, level)
+ || android.util.Log.isLoggable(tag.toString(), level);
+ }
+ } catch (IllegalArgumentException ex) {
+ e(TAG, "Tag too long:" + tag);
+ return false;
+ }
+ }
+}
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/debug/LogHelper.java b/camera2/portability/src/com/android/ex/camera2/portability/debug/LogHelper.java
new file mode 100644
index 0000000..3ec0260
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/debug/LogHelper.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 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.debug;
+
+import android.content.Context;
+
+public class LogHelper {
+ public static void initialize(Context context) {
+ // Do nothing.
+ }
+
+ /**
+ * Return a valid log level from {@link android.util.Log} to override
+ * the system log level. Return 0 to instead defer to system log level.
+ */
+ public static int getOverrideLevel() {
+ return 0;
+ }
+} \ No newline at end of file
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/util/SystemProperties.java b/camera2/portability/src/com/android/ex/camera2/portability/util/SystemProperties.java
new file mode 100644
index 0000000..81493f3
--- /dev/null
+++ b/camera2/portability/src/com/android/ex/camera2/portability/util/SystemProperties.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 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.util;
+
+import com.android.ex.camera2.portability.debug.Log;
+
+import java.lang.reflect.Method;
+
+/**
+ * Mirrors hidden class {@link android.os.SystemProperties} (available since API Level 1).
+ */
+public final class SystemProperties {
+ private static final Log.Tag TAG = new Log.Tag("SysProps");
+
+ /**
+ * Gets system properties set by <code>adb shell setprop <em>key</em> <em>value</em></code>
+ *
+ * @param key the property key.
+ * @param defaultValue the value to return if the property is undefined or empty (this parameter
+ * may be {@code null}).
+ * @return the system property value or the default value.
+ */
+ public static String get(String key, String defaultValue) {
+ try {
+ final Class<?> systemProperties = Class.forName("android.os.SystemProperties");
+ final Method get = systemProperties.getMethod("get", String.class, String.class);
+ return (String) get.invoke(null, key, defaultValue);
+ } catch (Exception e) {
+ // This should never happen
+ Log.e(TAG, "Exception while getting system property: ", e);
+ return defaultValue;
+ }
+ }
+
+ private SystemProperties() {
+ }
+}
diff --git a/camera2/portability/tests/Android.mk b/camera2/portability/tests/Android.mk
new file mode 100644
index 0000000..f0b24e0
--- /dev/null
+++ b/camera2/portability/tests/Android.mk
@@ -0,0 +1,25 @@
+# Copyright (C) 2014 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := android-ex-camera2-portability-tests
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under,src) $(call all-java-files-under,../../utils/tests)
+LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2-portability android-ex-camera2-utils \
+ android-support-test mockito-target
+
+include $(BUILD_PACKAGE)
diff --git a/camera2/portability/tests/AndroidManifest.xml b/camera2/portability/tests/AndroidManifest.xml
new file mode 100644
index 0000000..65cf709
--- /dev/null
+++ b/camera2/portability/tests/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright (C) 2014 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.ex.camera2.portability.tests">
+ <uses-permission android:name="android.permission.CAMERA" />
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.ex.camera2.portability.tests" />
+</manifest>
diff --git a/camera2/portability/tests/src/com/android/ex/camera2/portability/Camera2PortabilityTest.java b/camera2/portability/tests/src/com/android/ex/camera2/portability/Camera2PortabilityTest.java
new file mode 100644
index 0000000..034fac7
--- /dev/null
+++ b/camera2/portability/tests/src/com/android/ex/camera2/portability/Camera2PortabilityTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2014 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 static android.hardware.camera2.CaptureRequest.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.support.test.InjectContext;
+
+import com.android.ex.camera2.portability.CameraCapabilities.FlashMode;
+import com.android.ex.camera2.portability.CameraCapabilities.FocusMode;
+import com.android.ex.camera2.portability.CameraCapabilities.SceneMode;
+import com.android.ex.camera2.portability.CameraCapabilities.Stringifier;
+import com.android.ex.camera2.portability.CameraCapabilities.WhiteBalance;
+import com.android.ex.camera2.utils.Camera2DeviceTester;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+
+public class Camera2PortabilityTest extends Camera2DeviceTester {
+ @Test
+ public void cameraCapabilitiesStringifier() {
+ Stringifier strfy = new Stringifier();
+ for(FocusMode val : FocusMode.values()) {
+ assertEquals(val, strfy.focusModeFromString(strfy.stringify(val)));
+ }
+ for(FlashMode val : FlashMode.values()) {
+ assertEquals(val, strfy.flashModeFromString(strfy.stringify(val)));
+ }
+ for(SceneMode val : SceneMode.values()) {
+ assertEquals(val, strfy.sceneModeFromString(strfy.stringify(val)));
+ }
+ for(WhiteBalance val : WhiteBalance.values()) {
+ assertEquals(val, strfy.whiteBalanceFromString(strfy.stringify(val)));
+ }
+ }
+
+ @Test
+ public void cameraCapabilitiesStringifierNull() {
+ Stringifier strfy = new Stringifier();
+ assertEquals(strfy.focusModeFromString(null), FocusMode.AUTO);
+ assertEquals(strfy.flashModeFromString(null), FlashMode.NO_FLASH);
+ assertEquals(strfy.sceneModeFromString(null), SceneMode.NO_SCENE_MODE);
+ assertEquals(strfy.whiteBalanceFromString(null), WhiteBalance.AUTO);
+ }
+
+ @Test
+ public void cameraCapabilitiesStringifierInvalid() {
+ Stringifier strfy = new Stringifier();
+ assertEquals(strfy.focusModeFromString("crap"), FocusMode.AUTO);
+ assertEquals(strfy.flashModeFromString("crap"), FlashMode.NO_FLASH);
+ assertEquals(strfy.sceneModeFromString("crap"), SceneMode.NO_SCENE_MODE);
+ assertEquals(strfy.whiteBalanceFromString("crap"), WhiteBalance.AUTO);
+ }
+
+ private CameraCharacteristics buildFrameworkCharacteristics() throws CameraAccessException {
+ CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+ String id = manager.getCameraIdList()[0];
+ return manager.getCameraCharacteristics(id);
+ }
+
+ private void camera2SettingsCheckSingleOption(AndroidCamera2Settings setts,
+ Key<?> apiKey, int apiVal) {
+ assertEquals(apiVal, setts.getRequestSettings().get(apiKey));
+ }
+
+ @Test
+ public void camera2SettingsSetOptionsAndGetRequestSettings() throws CameraAccessException {
+ AndroidCamera2Settings set = new AndroidCamera2Settings(
+ mCamera, CameraDevice.TEMPLATE_PREVIEW, null, null, null);
+
+ // Focus modes
+ set.setFocusMode(FocusMode.AUTO);
+ camera2SettingsCheckSingleOption(set, CONTROL_AF_MODE, CONTROL_AF_MODE_AUTO);
+ set.setFocusMode(FocusMode.CONTINUOUS_PICTURE);
+ camera2SettingsCheckSingleOption(set, CONTROL_AF_MODE, CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ set.setFocusMode(FocusMode.CONTINUOUS_VIDEO);
+ camera2SettingsCheckSingleOption(set, CONTROL_AF_MODE, CONTROL_AF_MODE_CONTINUOUS_VIDEO);
+ set.setFocusMode(FocusMode.EXTENDED_DOF);
+ camera2SettingsCheckSingleOption(set, CONTROL_AF_MODE, CONTROL_AF_MODE_EDOF);
+ set.setFocusMode(FocusMode.FIXED);
+ camera2SettingsCheckSingleOption(set, CONTROL_AF_MODE, CONTROL_AF_MODE_OFF);
+ set.setFocusMode(FocusMode.MACRO);
+ camera2SettingsCheckSingleOption(set, CONTROL_AF_MODE, CONTROL_AF_MODE_MACRO);
+
+ // Scene modes
+ set.setSceneMode(SceneMode.AUTO);
+ camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_DISABLED);
+ set.setSceneMode(SceneMode.ACTION);
+ camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_ACTION);
+ set.setSceneMode(SceneMode.BARCODE);
+ camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_BARCODE);
+ set.setSceneMode(SceneMode.BEACH);
+ camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_BEACH);
+ set.setSceneMode(SceneMode.CANDLELIGHT);
+ camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_CANDLELIGHT);
+ set.setSceneMode(SceneMode.FIREWORKS);
+ camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_FIREWORKS);
+ set.setSceneMode(SceneMode.LANDSCAPE);
+ camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_LANDSCAPE);
+ set.setSceneMode(SceneMode.NIGHT);
+ camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_NIGHT);
+ set.setSceneMode(SceneMode.PARTY);
+ camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_PARTY);
+ set.setSceneMode(SceneMode.PORTRAIT);
+ camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_PORTRAIT);
+ set.setSceneMode(SceneMode.SNOW);
+ camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_SNOW);
+ set.setSceneMode(SceneMode.SPORTS);
+ camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_SPORTS);
+ set.setSceneMode(SceneMode.STEADYPHOTO);
+ camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_STEADYPHOTO);
+ set.setSceneMode(SceneMode.SUNSET);
+ camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_SUNSET);
+ set.setSceneMode(SceneMode.THEATRE);
+ camera2SettingsCheckSingleOption(set, CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_THEATRE);
+
+ // White balances
+ set.setWhiteBalance(WhiteBalance.AUTO);
+ camera2SettingsCheckSingleOption(set, CONTROL_AWB_MODE, CONTROL_AWB_MODE_AUTO);
+ set.setWhiteBalance(WhiteBalance.CLOUDY_DAYLIGHT);
+ camera2SettingsCheckSingleOption(set, CONTROL_AWB_MODE, CONTROL_AWB_MODE_CLOUDY_DAYLIGHT);
+ set.setWhiteBalance(WhiteBalance.DAYLIGHT);
+ camera2SettingsCheckSingleOption(set, CONTROL_AWB_MODE, CONTROL_AWB_MODE_DAYLIGHT);
+ set.setWhiteBalance(WhiteBalance.FLUORESCENT);
+ camera2SettingsCheckSingleOption(set, CONTROL_AWB_MODE, CONTROL_AWB_MODE_FLUORESCENT);
+ set.setWhiteBalance(WhiteBalance.INCANDESCENT);
+ camera2SettingsCheckSingleOption(set, CONTROL_AWB_MODE, CONTROL_AWB_MODE_INCANDESCENT);
+ set.setWhiteBalance(WhiteBalance.SHADE);
+ camera2SettingsCheckSingleOption(set, CONTROL_AWB_MODE, CONTROL_AWB_MODE_SHADE);
+ set.setWhiteBalance(WhiteBalance.TWILIGHT);
+ camera2SettingsCheckSingleOption(set, CONTROL_AWB_MODE, CONTROL_AWB_MODE_TWILIGHT);
+ set.setWhiteBalance(WhiteBalance.WARM_FLUORESCENT);
+ camera2SettingsCheckSingleOption(set, CONTROL_AWB_MODE, CONTROL_AWB_MODE_WARM_FLUORESCENT);
+ }
+
+ // TODO: Add a test checking whether stringification matches API 1 representation
+
+ @Test
+ public void camera2CapabilitiesFocusModeFromInt() throws CameraAccessException {
+ CameraCharacteristics chars = buildFrameworkCharacteristics();
+ AndroidCamera2Capabilities intstr = new AndroidCamera2Capabilities(chars);
+
+ // Focus modes
+ assertEquals(intstr.focusModeFromInt(CONTROL_AF_MODE_AUTO), FocusMode.AUTO);
+ assertEquals(intstr.focusModeFromInt(CONTROL_AF_MODE_CONTINUOUS_PICTURE),
+ FocusMode.CONTINUOUS_PICTURE);
+ assertEquals(intstr.focusModeFromInt(CONTROL_AF_MODE_CONTINUOUS_VIDEO),
+ FocusMode.CONTINUOUS_VIDEO);
+ assertEquals(intstr.focusModeFromInt(CONTROL_AF_MODE_EDOF), FocusMode.EXTENDED_DOF);
+ assertEquals(intstr.focusModeFromInt(CONTROL_AF_MODE_OFF), FocusMode.FIXED);
+ assertEquals(intstr.focusModeFromInt(CONTROL_AF_MODE_MACRO), FocusMode.MACRO);
+
+ // Scene modes
+ assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_DISABLED), SceneMode.AUTO);
+ assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_ACTION), SceneMode.ACTION);
+ assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_BARCODE), SceneMode.BARCODE);
+ assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_BEACH), SceneMode.BEACH);
+ assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_CANDLELIGHT), SceneMode.CANDLELIGHT);
+ assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_FIREWORKS), SceneMode.FIREWORKS);
+ assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_LANDSCAPE), SceneMode.LANDSCAPE);
+ assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_NIGHT), SceneMode.NIGHT);
+ assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_PARTY), SceneMode.PARTY);
+ assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_PORTRAIT), SceneMode.PORTRAIT);
+ assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_SNOW), SceneMode.SNOW);
+ assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_SPORTS), SceneMode.SPORTS);
+ assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_STEADYPHOTO),
+ SceneMode.STEADYPHOTO);
+ assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_SUNSET), SceneMode.SUNSET);
+ assertEquals(intstr.sceneModeFromInt(CONTROL_SCENE_MODE_THEATRE), SceneMode.THEATRE);
+
+ // White balances
+ assertEquals(intstr.whiteBalanceFromInt(CONTROL_AWB_MODE_AUTO), WhiteBalance.AUTO);
+ assertEquals(intstr.whiteBalanceFromInt(CONTROL_AWB_MODE_CLOUDY_DAYLIGHT),
+ WhiteBalance.CLOUDY_DAYLIGHT);
+ assertEquals(intstr.whiteBalanceFromInt(CONTROL_AWB_MODE_DAYLIGHT), WhiteBalance.DAYLIGHT);
+ assertEquals(intstr.whiteBalanceFromInt(CONTROL_AWB_MODE_FLUORESCENT),
+ WhiteBalance.FLUORESCENT);
+ assertEquals(intstr.whiteBalanceFromInt(CONTROL_AWB_MODE_INCANDESCENT),
+ WhiteBalance.INCANDESCENT);
+ assertEquals(intstr.whiteBalanceFromInt(CONTROL_AWB_MODE_SHADE), WhiteBalance.SHADE);
+ assertEquals(intstr.whiteBalanceFromInt(CONTROL_AWB_MODE_TWILIGHT), WhiteBalance.TWILIGHT);
+ assertEquals(intstr.whiteBalanceFromInt(CONTROL_AWB_MODE_WARM_FLUORESCENT),
+ WhiteBalance.WARM_FLUORESCENT);
+ }
+}
diff --git a/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java b/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java
index de036e1..02dbbba 100644
--- a/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java
+++ b/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java
@@ -275,26 +275,6 @@ public class BlockingCameraManager {
}
@Override
- public void onUnconfigured(CameraDevice camera) {
- if (mProxy != null) mProxy.onUnconfigured(camera);
- }
-
- @Override
- public void onIdle(CameraDevice camera) {
- if (mProxy != null) mProxy.onIdle(camera);
- }
-
- @Override
- public void onActive(CameraDevice camera) {
- if (mProxy != null) mProxy.onActive(camera);
- }
-
- @Override
- public void onBusy(CameraDevice camera) {
- if (mProxy != null) mProxy.onBusy(camera);
- }
-
- @Override
public void onClosed(CameraDevice camera) {
if (mProxy != null) mProxy.onClosed(camera);
}
diff --git a/camera2/public/src/com/android/ex/camera2/blocking/BlockingCaptureListener.java b/camera2/public/src/com/android/ex/camera2/blocking/BlockingCaptureListener.java
new file mode 100644
index 0000000..eae85d1
--- /dev/null
+++ b/camera2/public/src/com/android/ex/camera2/blocking/BlockingCaptureListener.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2014 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.blocking;
+
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.util.Log;
+
+import com.android.ex.camera2.utils.StateChangeListener;
+import com.android.ex.camera2.utils.StateWaiter;
+
+/**
+ * A camera capture listener that implements blocking operations on state changes for a
+ * particular capture request.
+ *
+ * <p>Provides a waiter that can be used to block until the next unobserved state of the
+ * requested type arrives.</p>
+ *
+ * <p>Pass-through all StateListener changes to the proxy.</p>
+ *
+ * @see #getStateWaiter
+ */
+public class BlockingCaptureListener extends CameraCaptureSession.CaptureListener {
+
+ /**
+ * {@link #onCaptureStarted} has been called.
+ */
+ public static final int CAPTURE_STARTED = 0;
+
+ /**
+ * {@link #onCaptureProgressed} has been
+ * called.
+ */
+ public static final int CAPTURE_PROGRESSED = 1;
+
+ /**
+ * {@link #onCaptureCompleted} has
+ * been called.
+ */
+ public static final int CAPTURE_COMPLETED = 2;
+
+ /**
+ * {@link #onCaptureFailed} has been
+ * called.
+ */
+ public static final int CAPTURE_FAILED = 3;
+
+ /**
+ * {@link #onCaptureSequenceCompleted} has been called.
+ */
+ public static final int CAPTURE_SEQUENCE_COMPLETED = 4;
+
+ /**
+ * {@link #onCaptureSequenceAborted} has been called.
+ */
+ public static final int CAPTURE_SEQUENCE_ABORTED = 5;
+
+ private static final String[] sStateNames = {
+ "CAPTURE_STARTED",
+ "CAPTURE_PROGRESSED",
+ "CAPTURE_COMPLETED",
+ "CAPTURE_FAILED",
+ "CAPTURE_SEQUENCE_COMPLETED",
+ "CAPTURE_SEQUENCE_ABORTED"
+ };
+
+ private static final String TAG = "BlockingCaptureListener";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private final CameraCaptureSession.CaptureListener mProxy;
+
+ private final StateWaiter mStateWaiter = new StateWaiter(sStateNames);
+ private final StateChangeListener mStateChangeListener = mStateWaiter.getListener();
+
+ /**
+ * Create a blocking capture listener without forwarding the capture listener invocations
+ * to another capture listener.
+ */
+ public BlockingCaptureListener() {
+ mProxy = null;
+ }
+
+ /**
+ * Create a blocking capture listener; forward original listener invocations
+ * into {@code listener}.
+ *
+ * @param listener a non-{@code null} listener to forward invocations into
+ *
+ * @throws NullPointerException if {@code listener} was {@code null}
+ */
+ public BlockingCaptureListener(CameraCaptureSession.CaptureListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("listener must not be null");
+ }
+ mProxy = listener;
+ }
+
+ /**
+ * Acquire the state waiter; can be used to block until a set of state transitions have
+ * been reached.
+ *
+ * <p>Only one thread should wait at a time.</p>
+ */
+ public StateWaiter getStateWaiter() {
+ return mStateWaiter;
+ }
+
+ @Override
+ public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
+ long timestamp) {
+ if (mProxy != null) mProxy.onCaptureStarted(session, request, timestamp);
+ mStateChangeListener.onStateChanged(CAPTURE_STARTED);
+ }
+
+ @Override
+ public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
+ CaptureResult partialResult) {
+ if (mProxy != null) mProxy.onCaptureProgressed(session, request, partialResult);
+ mStateChangeListener.onStateChanged(CAPTURE_PROGRESSED);
+ }
+
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
+ TotalCaptureResult result) {
+ if (mProxy != null) mProxy.onCaptureCompleted(session, request, result);
+ mStateChangeListener.onStateChanged(CAPTURE_COMPLETED);
+ }
+
+ @Override
+ public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request,
+ CaptureFailure failure) {
+ if (mProxy != null) mProxy.onCaptureFailed(session, request, failure);
+ mStateChangeListener.onStateChanged(CAPTURE_FAILED);
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId,
+ long frameNumber) {
+ if (mProxy != null) mProxy.onCaptureSequenceCompleted(session, sequenceId, frameNumber);
+ mStateChangeListener.onStateChanged(CAPTURE_SEQUENCE_COMPLETED);
+ }
+
+ @Override
+ public void onCaptureSequenceAborted(CameraCaptureSession session, int sequenceId) {
+ if (mProxy != null) mProxy.onCaptureSequenceAborted(session, sequenceId);
+ mStateChangeListener.onStateChanged(CAPTURE_SEQUENCE_ABORTED);
+ }
+}
diff --git a/camera2/public/src/com/android/ex/camera2/blocking/BlockingSessionListener.java b/camera2/public/src/com/android/ex/camera2/blocking/BlockingSessionListener.java
new file mode 100644
index 0000000..26bb652
--- /dev/null
+++ b/camera2/public/src/com/android/ex/camera2/blocking/BlockingSessionListener.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2014 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.blocking;
+
+import android.hardware.camera2.CameraCaptureSession;
+import android.os.ConditionVariable;
+import android.util.Log;
+
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+import com.android.ex.camera2.utils.StateChangeListener;
+import com.android.ex.camera2.utils.StateWaiter;
+
+import java.util.ArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+
+/**
+ * A camera session listener that implements blocking operations on session state changes.
+ *
+ * <p>Provides a waiter that can be used to block until the next unobserved state of the
+ * requested type arrives.</p>
+ *
+ * <p>Pass-through all StateListener changes to the proxy.</p>
+ *
+ * @see #getStateWaiter
+ */
+public class BlockingSessionListener extends CameraCaptureSession.StateListener {
+ /**
+ * Session is configured, ready for captures
+ */
+ public static final int SESSION_CONFIGURED = 0;
+
+ /**
+ * Session has failed to configure, can't do any captures
+ */
+ public static final int SESSION_CONFIGURE_FAILED = 1;
+
+ /**
+ * Session is ready
+ */
+ public static final int SESSION_READY = 2;
+
+ /**
+ * Session is active (transitory)
+ */
+ public static final int SESSION_ACTIVE = 3;
+
+ /**
+ * Session is closed
+ */
+ public static final int SESSION_CLOSED = 4;
+
+ private final int NUM_STATES = 5;
+
+ /*
+ * Private fields
+ */
+ private static final String TAG = "BlockingSessionListener";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private final CameraCaptureSession.StateListener mProxy;
+ private final SessionFuture mSessionFuture = new SessionFuture();
+
+ private final StateWaiter mStateWaiter = new StateWaiter(sStateNames);
+ private final StateChangeListener mStateChangeListener = mStateWaiter.getListener();
+
+ private static final String[] sStateNames = {
+ "SESSION_CONFIGURED",
+ "SESSION_CONFIGURE_FAILED",
+ "SESSION_READY",
+ "SESSION_ACTIVE",
+ "SESSION_CLOSED"
+ };
+
+ /**
+ * Create a blocking session listener without forwarding the session listener invocations
+ * to another session listener.
+ */
+ public BlockingSessionListener() {
+ mProxy = null;
+ }
+
+ /**
+ * Create a blocking session listener; forward original listener invocations
+ * into {@code listener}.
+ *
+ * @param listener a non-{@code null} listener to forward invocations into
+ *
+ * @throws NullPointerException if {@code listener} was {@code null}
+ */
+ public BlockingSessionListener(CameraCaptureSession.StateListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("listener must not be null");
+ }
+ mProxy = listener;
+ }
+
+ /**
+ * Acquire the state waiter; can be used to block until a set of state transitions have
+ * been reached.
+ *
+ * <p>Only one thread should wait at a time.</p>
+ */
+ public StateWaiter getStateWaiter() {
+ return mStateWaiter;
+ }
+
+ /**
+ * Return session if already have it; otherwise wait until any of the session listener
+ * invocations fire and the session is available.
+ *
+ * <p>Does not consume any of the states from the state waiter.</p>
+ *
+ * @param timeoutMs how many milliseconds to wait for
+ * @return a non-{@code null} {@link CameraCaptureSession} instance
+ *
+ * @throws TimeoutRuntimeException if waiting for more than {@long timeoutMs}
+ */
+ public CameraCaptureSession waitAndGetSession(long timeoutMs) {
+ try {
+ return mSessionFuture.get(timeoutMs, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ throw new TimeoutRuntimeException(
+ String.format("Failed to get session after %s milliseconds", timeoutMs), e);
+ }
+ }
+
+ /*
+ * CameraCaptureSession.StateListener implementation
+ */
+
+ @Override
+ public void onActive(CameraCaptureSession session) {
+ mSessionFuture.setSession(session);
+ if (mProxy != null) mProxy.onActive(session);
+ mStateChangeListener.onStateChanged(SESSION_ACTIVE);
+ }
+
+ @Override
+ public void onClosed(CameraCaptureSession session) {
+ mSessionFuture.setSession(session);
+ if (mProxy != null) mProxy.onClosed(session);
+ mStateChangeListener.onStateChanged(SESSION_CLOSED);
+ }
+
+ @Override
+ public void onConfigured(CameraCaptureSession session) {
+ mSessionFuture.setSession(session);
+ if (mProxy != null) mProxy.onConfigured(session);
+ mStateChangeListener.onStateChanged(SESSION_CONFIGURED);
+ }
+
+ @Override
+ public void onConfigureFailed(CameraCaptureSession session) {
+ mSessionFuture.setSession(session);
+ if (mProxy != null) mProxy.onConfigureFailed(session);
+ mStateChangeListener.onStateChanged(SESSION_CONFIGURE_FAILED);
+ }
+
+ @Override
+ public void onReady(CameraCaptureSession session) {
+ mSessionFuture.setSession(session);
+ if (mProxy != null) mProxy.onReady(session);
+ mStateChangeListener.onStateChanged(SESSION_READY);
+ }
+
+ private static class SessionFuture implements Future<CameraCaptureSession> {
+ private volatile CameraCaptureSession mSession;
+ ConditionVariable mCondVar = new ConditionVariable(/*opened*/false);
+
+ public void setSession(CameraCaptureSession session) {
+ mSession = session;
+ mCondVar.open();
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return false; // don't allow canceling this task
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false; // can never cancel this task
+ }
+
+ @Override
+ public boolean isDone() {
+ return mSession != null;
+ }
+
+ @Override
+ public CameraCaptureSession get() {
+ mCondVar.block();
+ return mSession;
+ }
+
+ @Override
+ public CameraCaptureSession get(long timeout, TimeUnit unit) throws TimeoutException {
+ long timeoutMs = unit.convert(timeout, TimeUnit.MILLISECONDS);
+ if (!mCondVar.block(timeoutMs)) {
+ throw new TimeoutException(
+ "Failed to receive session after " + timeout + " " + unit);
+ }
+
+ if (mSession == null) {
+ throw new AssertionError();
+ }
+ return mSession;
+ }
+
+ }
+}
diff --git a/camera2/public/src/com/android/ex/camera2/blocking/BlockingStateListener.java b/camera2/public/src/com/android/ex/camera2/blocking/BlockingStateListener.java
index 619ba0a..02c2ba3 100644
--- a/camera2/public/src/com/android/ex/camera2/blocking/BlockingStateListener.java
+++ b/camera2/public/src/com/android/ex/camera2/blocking/BlockingStateListener.java
@@ -64,10 +64,6 @@ public class BlockingStateListener extends CameraDevice.StateListener {
private static final String[] mStateNames = {
"STATE_UNINITIALIZED",
"STATE_OPENED",
- "STATE_UNCONFIGURED",
- "STATE_IDLE",
- "STATE_ACTIVE",
- "STATE_BUSY",
"STATE_CLOSED",
"STATE_DISCONNECTED",
"STATE_ERROR"
@@ -84,44 +80,24 @@ public class BlockingStateListener extends CameraDevice.StateListener {
public static final int STATE_OPENED = 0;
/**
- * Device is unconfigured
- */
- public static final int STATE_UNCONFIGURED = 1;
-
- /**
- * Device is idle
- */
- public static final int STATE_IDLE = 2;
-
- /**
- * Device is active (transitory)
- */
- public static final int STATE_ACTIVE = 3;
-
- /**
- * Device is busy (transitory)
- */
- public static final int STATE_BUSY = 4;
-
- /**
* Device is closed
*/
- public static final int STATE_CLOSED = 5;
+ public static final int STATE_CLOSED = 1;
/**
* Device is disconnected
*/
- public static final int STATE_DISCONNECTED = 6;
+ public static final int STATE_DISCONNECTED = 2;
/**
* Device has encountered a fatal error
*/
- public static final int STATE_ERROR = 7;
+ public static final int STATE_ERROR = 3;
/**
* Total number of reachable states
*/
- private static int NUM_STATES = 8;
+ private static int NUM_STATES = 4;
public BlockingStateListener() {
mProxy = null;
@@ -133,50 +109,26 @@ public class BlockingStateListener extends CameraDevice.StateListener {
@Override
public void onOpened(CameraDevice camera) {
- setCurrentState(STATE_OPENED);
if (mProxy != null) mProxy.onOpened(camera);
+ setCurrentState(STATE_OPENED);
}
@Override
public void onDisconnected(CameraDevice camera) {
- setCurrentState(STATE_DISCONNECTED);
if (mProxy != null) mProxy.onDisconnected(camera);
+ setCurrentState(STATE_DISCONNECTED);
}
@Override
public void onError(CameraDevice camera, int error) {
- setCurrentState(STATE_ERROR);
if (mProxy != null) mProxy.onError(camera, error);
- }
-
- @Override
- public void onUnconfigured(CameraDevice camera) {
- setCurrentState(STATE_UNCONFIGURED);
- if (mProxy != null) mProxy.onUnconfigured(camera);
- }
-
- @Override
- public void onIdle(CameraDevice camera) {
- setCurrentState(STATE_IDLE);
- if (mProxy != null) mProxy.onIdle(camera);
- }
-
- @Override
- public void onActive(CameraDevice camera) {
- setCurrentState(STATE_ACTIVE);
- if (mProxy != null) mProxy.onActive(camera);
- }
-
- @Override
- public void onBusy(CameraDevice camera) {
- setCurrentState(STATE_BUSY);
- if (mProxy != null) mProxy.onBusy(camera);
+ setCurrentState(STATE_ERROR);
}
@Override
public void onClosed(CameraDevice camera) {
- setCurrentState(STATE_CLOSED);
if (mProxy != null) mProxy.onClosed(camera);
+ setCurrentState(STATE_CLOSED);
}
/**
diff --git a/camera2/public/src/com/android/ex/camera2/pos/AutoFocusStateMachine.java b/camera2/public/src/com/android/ex/camera2/pos/AutoFocusStateMachine.java
index 500fb12..9fa2af2 100644
--- a/camera2/public/src/com/android/ex/camera2/pos/AutoFocusStateMachine.java
+++ b/camera2/public/src/com/android/ex/camera2/pos/AutoFocusStateMachine.java
@@ -16,7 +16,7 @@
package com.android.ex.camera2.pos;
import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraMetadata.Key;
+import android.hardware.camera2.CaptureResult.Key;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.util.Log;
diff --git a/camera2/public/src/com/android/ex/camera2/utils/StateChangeListener.java b/camera2/public/src/com/android/ex/camera2/utils/StateChangeListener.java
new file mode 100644
index 0000000..48cca17
--- /dev/null
+++ b/camera2/public/src/com/android/ex/camera2/utils/StateChangeListener.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2014 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.utils;
+
+public interface StateChangeListener {
+ public void onStateChanged(int state);
+}
diff --git a/camera2/public/src/com/android/ex/camera2/utils/StateWaiter.java b/camera2/public/src/com/android/ex/camera2/utils/StateWaiter.java
new file mode 100644
index 0000000..ec57829
--- /dev/null
+++ b/camera2/public/src/com/android/ex/camera2/utils/StateWaiter.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2014 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.utils;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Block until a specific state change occurs.
+ *
+ * <p>Provides wait calls that block until the next unobserved state of the
+ * requested type arrives. Unobserved states are states that have occurred since
+ * the last wait, or that will be received from the camera device in the
+ * future.</p>
+ *
+ * <p>Thread interruptions are not supported; interrupting a thread that is either
+ * waiting with {@link #waitForState} / {@link #waitForAnyOfStates} or is currently in
+ * {@link StateChangeListener#onStateChanged} (provided by {@link #getListener}) will result in an
+ * {@link UnsupportedOperationException} being raised on that thread.</p>
+ */
+public final class StateWaiter {
+
+ private static final String TAG = "StateWaiter";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private final String[] mStateNames;
+ private final int mStateCount;
+ private final StateChangeListener mListener;
+
+ /** Guard waitForState, waitForAnyState to only have one waiter */
+ private final AtomicBoolean mWaiting = new AtomicBoolean(false);
+
+ private final LinkedBlockingQueue<Integer> mQueuedStates = new LinkedBlockingQueue<>();
+
+ /**
+ * Create a new state waiter.
+ *
+ * <p>All {@code state}/{@code states} arguments used in other methods must be
+ * in the range of {@code [0, stateNames.length - 1]}.</p>
+ *
+ * @param stateNames an array of string names, used to mark the range of the valid states
+ */
+ public StateWaiter(String[] stateNames) {
+ mStateCount = stateNames.length;
+ mStateNames = new String[mStateCount];
+ System.arraycopy(stateNames, /*srcPos*/0, mStateNames, /*dstPos*/0, mStateCount);
+
+ mListener = new StateChangeListener() {
+ @Override
+ public void onStateChanged(int state) {
+ queueStateTransition(checkStateInRange(state));
+ }
+ };
+ }
+
+ public StateChangeListener getListener() {
+ return mListener;
+ }
+
+ /**
+ * Wait until the desired state is observed, checking all state
+ * transitions since the last time a state was waited on.
+ *
+ * <p>Any intermediate state transitions that is not {@code state} are ignored.</p>
+ *
+ * <p>Note: Only one waiter allowed at a time!</p>
+ *
+ * @param desired state to observe a transition to
+ * @param timeoutMs how long to wait in milliseconds
+ *
+ * @throws IllegalArgumentException if {@code state} was out of range
+ * @throws TimeoutRuntimeException if the desired state is not observed before timeout.
+ * @throws IllegalStateException if another thread is already waiting for a state transition
+ */
+ public void waitForState(int state, long timeoutMs) {
+ Integer[] stateArray = { checkStateInRange(state) };
+
+ waitForAnyOfStates(Arrays.asList(stateArray), timeoutMs);
+ }
+
+ /**
+ * Wait until the one of the desired {@code states} is observed, checking all
+ * state transitions since the last time a state was waited on.
+ *
+ * <p>Any intermediate state transitions that are not in {@code states} are ignored.</p>
+ *
+ * <p>Note: Only one waiter allowed at a time!</p>
+ *
+ * @param states Set of desired states to observe a transition to.
+ * @param timeoutMs how long to wait in milliseconds
+ *
+ * @return the state reached
+ *
+ * @throws IllegalArgumentException if {@code state} was out of range
+ * @throws TimeoutRuntimeException if none of the states is observed before timeout.
+ * @throws IllegalStateException if another thread is already waiting for a state transition
+ */
+ public int waitForAnyOfStates(Collection<Integer> states, final long timeoutMs) {
+ checkStateCollectionInRange(states);
+
+ // Acquire exclusive waiting privileges
+ if (mWaiting.getAndSet(true)) {
+ throw new IllegalStateException("Only one waiter allowed at a time");
+ }
+
+ Integer nextState = null;
+ try {
+ if (VERBOSE) {
+ StringBuilder s = new StringBuilder("Waiting for state(s) ");
+ appendStateNames(s, states);
+ Log.v(TAG, s.toString());
+ }
+
+ long timeoutLeft = timeoutMs;
+ long startMs = SystemClock.elapsedRealtime();
+ while ((nextState = mQueuedStates.poll(timeoutLeft, TimeUnit.MILLISECONDS)) != null) {
+ if (VERBOSE) {
+ Log.v(TAG, " Saw transition to " + getStateName(nextState));
+ }
+
+ if (states.contains(nextState)) {
+ break;
+ }
+
+ long endMs = SystemClock.elapsedRealtime();
+ timeoutLeft -= (endMs - startMs);
+ startMs = endMs;
+ }
+ } catch (InterruptedException e) {
+ throw new UnsupportedOperationException("Does not support interrupts on waits", e);
+ } finally {
+ // Release exclusive waiting privileges
+ mWaiting.set(false);
+ }
+
+ if (!states.contains(nextState)) {
+ StringBuilder s = new StringBuilder("Timed out after ");
+ s.append(timeoutMs);
+ s.append(" ms waiting for state(s) ");
+ appendStateNames(s, states);
+
+ throw new TimeoutRuntimeException(s.toString());
+ }
+
+ return nextState;
+ }
+
+ /**
+ * Convert state integer to a String
+ */
+ public String getStateName(int state) {
+ return mStateNames[checkStateInRange(state)];
+ }
+
+ /**
+ * Append all states to string
+ */
+ public void appendStateNames(StringBuilder s, Collection<Integer> states) {
+ checkStateCollectionInRange(states);
+
+ boolean start = true;
+ for (Integer state : states) {
+ if (!start) {
+ s.append(" ");
+ }
+
+ s.append(getStateName(state));
+ start = false;
+ }
+ }
+
+ private void queueStateTransition(int state) {
+ if (VERBOSE) Log.v(TAG, "setCurrentState - state now " + getStateName(state));
+
+ try {
+ mQueuedStates.put(state);
+ } catch(InterruptedException e) {
+ throw new UnsupportedOperationException("Unable to set current state", e);
+ }
+ }
+
+ private int checkStateInRange(int state) {
+ if (state < 0 || state >= mStateCount) {
+ throw new IllegalArgumentException("State out of range " + state);
+ }
+
+ return state;
+ }
+
+ private Collection<Integer> checkStateCollectionInRange(Collection<Integer> states) {
+ for (int state : states) {
+ checkStateInRange(state);
+ }
+
+ return states;
+ }
+
+}
diff --git a/camera2/utils/Android.mk b/camera2/utils/Android.mk
new file mode 100644
index 0000000..36595e1
--- /dev/null
+++ b/camera2/utils/Android.mk
@@ -0,0 +1,17 @@
+# Copyright 2014 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(LOCAL_PATH)/utils.mk \
+ $(call all-subdir-makefiles)
diff --git a/camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureListenerForwarder.java b/camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureListenerForwarder.java
new file mode 100644
index 0000000..35b1c6d
--- /dev/null
+++ b/camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureListenerForwarder.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 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.utils;
+
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCaptureSession.CaptureListener;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.os.Handler;
+
+/**
+ * Proxy that forwards all updates to another {@link CaptureListener}, invoking
+ * its callbacks on a separate {@link Handler}.
+ */
+public class Camera2CaptureListenerForwarder extends CaptureListener {
+ private CaptureListener mListener;
+ private Handler mHandler;
+
+ public Camera2CaptureListenerForwarder(CaptureListener listener, Handler handler) {
+ mListener = listener;
+ mHandler = handler;
+ }
+
+ @Override
+ public void onCaptureCompleted(final CameraCaptureSession session, final CaptureRequest request,
+ final TotalCaptureResult result) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onCaptureCompleted(session, request, result);
+ }});
+ }
+
+ @Override
+ public void onCaptureFailed(final CameraCaptureSession session, final CaptureRequest request,
+ final CaptureFailure failure) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onCaptureFailed(session, request, failure);
+ }});
+ }
+
+ @Override
+ public void onCaptureProgressed(final CameraCaptureSession session,
+ final CaptureRequest request,
+ final CaptureResult partialResult) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onCaptureProgressed(session, request, partialResult);
+ }});
+ }
+
+ @Override
+ public void onCaptureSequenceAborted(final CameraCaptureSession session, final int sequenceId) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onCaptureSequenceAborted(session, sequenceId);
+ }});
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted(final CameraCaptureSession session, final int sequenceId,
+ final long frameNumber) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onCaptureSequenceCompleted(session, sequenceId, frameNumber);
+ }});
+ }
+
+ @Override
+ public void onCaptureStarted(final CameraCaptureSession session, final CaptureRequest request,
+ final long timestamp) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onCaptureStarted(session, request, timestamp);
+ }});
+ }
+}
diff --git a/camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureListenerSplitter.java b/camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureListenerSplitter.java
new file mode 100644
index 0000000..a13dc04
--- /dev/null
+++ b/camera2/utils/src/com/android/ex/camera2/utils/Camera2CaptureListenerSplitter.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2014 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.utils;
+
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCaptureSession.CaptureListener;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Junction that allows notifying multiple {@link CaptureListener}s whenever
+ * the {@link CameraCaptureSession} posts a capture-related update.
+ */
+public class Camera2CaptureListenerSplitter extends CaptureListener {
+ private final List<CaptureListener> mRecipients = new LinkedList<>();
+
+ /**
+ * @param recipients The listeners to notify. Any {@code null} passed here
+ * will be completely ignored.
+ */
+ public Camera2CaptureListenerSplitter(CaptureListener... recipients) {
+ for (CaptureListener listener : recipients) {
+ if (listener != null) {
+ mRecipients.add(listener);
+ }
+ }
+ }
+
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
+ TotalCaptureResult result) {
+ for (CaptureListener target : mRecipients) {
+ target.onCaptureCompleted(session, request, result);
+ }
+ }
+
+ @Override
+ public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request,
+ CaptureFailure failure) {
+ for (CaptureListener target : mRecipients) {
+ target.onCaptureFailed(session, request, failure);
+ }
+ }
+
+ @Override
+ public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
+ CaptureResult partialResult) {
+ for (CaptureListener target : mRecipients) {
+ target.onCaptureProgressed(session, request, partialResult);
+ }
+ }
+
+ @Override
+ public void onCaptureSequenceAborted(CameraCaptureSession session, int sequenceId) {
+ for (CaptureListener target : mRecipients) {
+ target.onCaptureSequenceAborted(session, sequenceId);
+ }
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId,
+ long frameNumber) {
+ for (CaptureListener target : mRecipients) {
+ target.onCaptureSequenceCompleted(session, sequenceId, frameNumber);
+ }
+ }
+
+ @Override
+ public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
+ long timestamp) {
+ for (CaptureListener target : mRecipients) {
+ target.onCaptureStarted(session, request, timestamp);
+ }
+ }
+}
diff --git a/camera2/utils/src/com/android/ex/camera2/utils/Camera2RequestSettingsSet.java b/camera2/utils/src/com/android/ex/camera2/utils/Camera2RequestSettingsSet.java
new file mode 100644
index 0000000..fac8f1a
--- /dev/null
+++ b/camera2/utils/src/com/android/ex/camera2/utils/Camera2RequestSettingsSet.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2014 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.utils;
+
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureRequest.Builder;
+import android.hardware.camera2.CaptureRequest.Key;
+import android.view.Surface;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A set of settings to be used when filing a {@link CaptureRequest}.
+ */
+public class Camera2RequestSettingsSet {
+ private final Map<Key<?>, Object> mDictionary;
+ private long mRevision;
+
+ /**
+ * Create a new instance with no settings defined.
+ *
+ * <p>Creating a request from this object without first specifying any
+ * properties on it is equivalent to just creating a request directly
+ * from the template of choice. Its revision identifier is initially
+ * {@code 0}, and will remain thus until its first modification.</p>
+ */
+ public Camera2RequestSettingsSet() {
+ mDictionary = new HashMap<>();
+ mRevision = 0;
+ }
+
+ /**
+ * Perform a deep copy of the defined settings and revision number.
+ *
+ * @param other The reference instance.
+ *
+ * @throws NullPointerException If {@code other} is {@code null}.
+ */
+ public Camera2RequestSettingsSet(Camera2RequestSettingsSet other) {
+ if (other == null) {
+ throw new NullPointerException("Tried to copy null Camera2RequestSettingsSet");
+ }
+
+ mDictionary = new HashMap<>(other.mDictionary);
+ mRevision = other.mRevision;
+ }
+
+ /**
+ * Specify a setting, potentially overriding the template's default choice.
+ *
+ * <p>Providing a {@code null} {@code value} will indicate a forced use of
+ * the template's selection for that {@code key}; the difference here is
+ * that this information will be propagated with unions as documented in
+ * {@link #union}. This method increments the revision identifier if the new
+ * choice is different than the existing selection.</p>
+ *
+ * @param key Which setting to alter.
+ * @param value The new selection for that setting, or {@code null} to force
+ * the use of the template's default selection for this field.
+ * @return Whether the settings were updated, which only occurs if the
+ * {@code value} is different from any already stored.
+ *
+ * @throws NullPointerException If {@code key} is {@code null}.
+ */
+ public <T> boolean set(Key<T> key, T value) {
+ if (key == null) {
+ throw new NullPointerException("Received a null key");
+ }
+
+ Object currentValue = get(key);
+ // Only save the value if it's different from the one we already have
+ if (!mDictionary.containsKey(key) || !Objects.equals(value, currentValue)) {
+ mDictionary.put(key, value);
+ ++mRevision;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Unsets a setting, preventing it from being propagated with unions or from
+ * overriding the default when creating a capture request.
+ *
+ * <p>This method increments the revision identifier if a selection had
+ * previously been made for that parameter.</p>
+ *
+ * @param key Which setting to reset.
+ * @return Whether the settings were updated, which only occurs if the
+ * specified setting already had a value or was forced to default.
+ *
+ * @throws NullPointerException If {@code key} is {@code null}.
+ */
+ public boolean unset(Key<?> key) {
+ if (key == null) {
+ throw new NullPointerException("Received a null key");
+ }
+
+ if (mDictionary.containsKey(key)) {
+ mDictionary.remove(key);
+ ++mRevision;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Interrogate the current specialization of a setting.
+ *
+ * @param key Which setting to check.
+ * @return The current selection for that setting, or {@code null} if the
+ * setting is unset or forced to the template-defined default.
+ *
+ * @throws NullPointerException If {@code key} is {@code null}.
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T get(Key<T> key) {
+ if (key == null) {
+ throw new NullPointerException("Received a null key");
+ }
+ return (T) mDictionary.get(key);
+ }
+
+ /**
+ * Query this instance for whether it prefers a particular choice for the
+ * given request parameter.
+ *
+ * <p>This method can be used to detect whether a particular field is forced
+ * to its default value or simply unset. While {@link #get} will return
+ * {@code null} in both these cases, this method will return {@code true}
+ * and {@code false}, respectively.</p>
+
+ * @param key Which setting to look for.
+ * @return Whether that setting has a value that will propagate with unions.
+ *
+ * @throws NullPointerException If {@code key} is {@code null}.
+ */
+ public boolean contains(Key<?> key) {
+ if (key == null) {
+ throw new NullPointerException("Received a null key");
+ }
+ return mDictionary.containsKey(key);
+ }
+
+ /**
+ * Get this set of settings's revision identifier, which can be compared
+ * against cached past values to determine whether it has been modified.
+ * <p>Distinct revisions across the same object do not necessarily indicate
+ * that the object's key/value pairs have changed at all, but the same
+ * revision on the same object does imply that they've stayed the same.</p>
+ *
+ * @return The number of modifications made since the beginning of this
+ * object's heritage.
+ */
+ public long getRevision() {
+ return mRevision;
+ }
+
+ /**
+ * Add all settings choices defined by {@code moreSettings} to this object.
+ *
+ * <p>For any settings defined in both, the choice stored in the argument
+ * to this method take precedence. Unset settings are not propagated, but
+ * those forced to default as described in {@link set} are also forced to
+ * default in {@code this} set. Invoking this method increments {@code this}
+ * object's revision counter, but leaves the argument's unchanged.</p>
+ *
+ * @param moreSettings The source of the additional settings ({@code null}
+ * is allowed here).
+ * @return Whether these settings were updated, which can only fail if the
+ * target itself is also given as the argument.
+ */
+ public boolean union(Camera2RequestSettingsSet moreSettings) {
+ if (moreSettings == null || moreSettings == this) {
+ return false;
+ }
+
+ mDictionary.putAll(moreSettings.mDictionary);
+ ++mRevision;
+ return true;
+ }
+
+ /**
+ * Create a {@link CaptureRequest} specialized for the specified
+ * {@link CameraDevice} and targeting the given {@link Surface}s.
+ *
+ * @param camera The camera from which to capture.
+ * @param template A {@link CaptureRequest} template defined in
+ * {@link CameraDevice}.
+ * @param targets The location(s) to draw the resulting image onto.
+ * @return The request, ready to be passed to the camera framework.
+ *
+ * @throws CameraAccessException Upon an underlying framework API failure.
+ * @throws NullPointerException If any argument is {@code null}.
+ */
+ public CaptureRequest createRequest(CameraDevice camera, int template, Surface... targets)
+ throws CameraAccessException {
+ if (camera == null) {
+ throw new NullPointerException("Tried to create request using null CameraDevice");
+ }
+
+ Builder reqBuilder = camera.createCaptureRequest(template);
+ for (Key<?> key : mDictionary.keySet()) {
+ setRequestFieldIfNonNull(reqBuilder, key);
+ }
+ for (Surface target : targets) {
+ if (target == null) {
+ throw new NullPointerException("Tried to add null Surface as request target");
+ }
+ reqBuilder.addTarget(target);
+ }
+ return reqBuilder.build();
+ }
+
+ private <T> void setRequestFieldIfNonNull(Builder requestBuilder, Key<T> key) {
+ T value = get(key);
+ if (value != null) {
+ requestBuilder.set(key, value);
+ }
+ }
+}
diff --git a/camera2/utils/tests/Android.mk b/camera2/utils/tests/Android.mk
new file mode 100644
index 0000000..6b3a569
--- /dev/null
+++ b/camera2/utils/tests/Android.mk
@@ -0,0 +1,24 @@
+# Copyright (C) 2014 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := android-ex-camera2-utils-tests
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2-utils android-support-test mockito-target
+
+include $(BUILD_PACKAGE)
diff --git a/camera2/utils/tests/AndroidManifest.xml b/camera2/utils/tests/AndroidManifest.xml
new file mode 100644
index 0000000..2f3cc08
--- /dev/null
+++ b/camera2/utils/tests/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright (C) 2014 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.ex.camera2.utils.tests">
+ <uses-permission android:name="android.permission.CAMERA" />
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.ex.camera2.utils.tests" />
+</manifest>
diff --git a/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2DeviceTester.java b/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2DeviceTester.java
new file mode 100644
index 0000000..4db6dfb
--- /dev/null
+++ b/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2DeviceTester.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 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.utils;
+
+import android.content.Context;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.support.test.InjectContext;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+/**
+ * Subclasses of this have an {@code mCamera} instance variable representing the first camera.
+ */
+public class Camera2DeviceTester {
+ private static HandlerThread sThread;
+
+ private static Handler sHandler;
+
+ @BeforeClass
+ public static void setupBackgroundHandler() {
+ sThread = new HandlerThread("CameraFramework");
+ sThread.start();
+ sHandler = new Handler(sThread.getLooper());
+ }
+
+ @AfterClass
+ public static void teardownBackgroundHandler() throws Exception {
+ sThread.quitSafely();
+ sThread.join();
+ }
+
+ @InjectContext
+ public Context mContext;
+
+ private class DeviceCapturer extends CameraDevice.StateListener {
+ private CameraDevice mCamera;
+
+ public CameraDevice captureCameraDevice() throws Exception {
+ CameraManager manager =
+ (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+ String id = manager.getCameraIdList()[0];
+ synchronized (this) {
+ manager.openCamera(id, this, sHandler);
+ wait();
+ }
+ return mCamera;
+ }
+
+ @Override
+ public synchronized void onOpened(CameraDevice camera) {
+ mCamera = camera;
+ notify();
+ }
+
+ @Override
+ public void onDisconnected(CameraDevice camera) {}
+
+ @Override
+ public void onError(CameraDevice camera, int error) {}
+ }
+
+ protected CameraDevice mCamera;
+
+ @Before
+ public void obtainCameraCaptureRequestBuilderFactory() throws Exception {
+ mCamera = new DeviceCapturer().captureCameraDevice();
+ }
+
+ @After
+ public void releaseCameraCaptureRequestBuilderFactory() {
+ mCamera.close();
+ }
+}
diff --git a/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2UtilsTest.java b/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2UtilsTest.java
new file mode 100644
index 0000000..bb23e37
--- /dev/null
+++ b/camera2/utils/tests/src/com/android/ex/camera2/utils/Camera2UtilsTest.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2014 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.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.camera2.CameraCaptureSession.CaptureListener;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureRequest.Key;
+import android.view.Surface;
+
+import org.junit.Test;
+
+public class Camera2UtilsTest extends Camera2DeviceTester {
+ private void captureListenerSplitterAllCallbacksReceived(CaptureListener splitter,
+ CaptureListener... terminals) {
+ splitter.onCaptureCompleted(null, null, null);
+ for (CaptureListener each : terminals) {
+ verify(each).onCaptureCompleted(null, null, null);
+ }
+ splitter.onCaptureFailed(null, null, null);
+ for (CaptureListener each : terminals) {
+ verify(each).onCaptureFailed(null, null, null);
+ }
+ splitter.onCaptureProgressed(null, null, null);
+ for (CaptureListener each : terminals) {
+ verify(each).onCaptureProgressed(null, null, null);
+ }
+ splitter.onCaptureSequenceAborted(null, 0);
+ for (CaptureListener each : terminals) {
+ verify(each).onCaptureSequenceAborted(null, 0);
+ }
+ splitter.onCaptureSequenceCompleted(null, 0, 0L);
+ for (CaptureListener each : terminals) {
+ verify(each).onCaptureSequenceCompleted(null, 0, 0L);
+ }
+ splitter.onCaptureStarted(null, null, 0L);
+ for (CaptureListener each : terminals) {
+ verify(each).onCaptureStarted(null, null, 0L);
+ }
+ }
+
+ @Test
+ public void captureListenerSplitter() {
+ CaptureListener firstBackingListener = mock(CaptureListener.class);
+ CaptureListener secondBackingListener = mock(CaptureListener.class);
+ captureListenerSplitterAllCallbacksReceived(
+ new Camera2CaptureListenerSplitter(firstBackingListener, secondBackingListener),
+ firstBackingListener, secondBackingListener);
+ }
+
+ @Test
+ public void captureListenerSplitterEmpty() {
+ captureListenerSplitterAllCallbacksReceived(new Camera2CaptureListenerSplitter());
+ }
+
+ @Test
+ public void captureListenerSplitterNoNpe() {
+ captureListenerSplitterAllCallbacksReceived(
+ new Camera2CaptureListenerSplitter((CaptureListener) null));
+ }
+
+ @Test
+ public void captureListenerSplitterMultipleNulls() {
+ captureListenerSplitterAllCallbacksReceived(
+ new Camera2CaptureListenerSplitter(null, null, null));
+ }
+
+ @Test
+ public void captureListenerSplitterValidAndNull() {
+ CaptureListener onlyRealBackingListener = mock(CaptureListener.class);
+ captureListenerSplitterAllCallbacksReceived(
+ new Camera2CaptureListenerSplitter(null, onlyRealBackingListener),
+ onlyRealBackingListener);
+ }
+
+ private <T> void requestSettingsSetAndForget(Camera2RequestSettingsSet s, Key<T> k, T v) {
+ s.set(k, v);
+ assertEquals(v, s.get(k));
+ }
+
+ @Test
+ public void requestSettingsSet() {
+ Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
+ // Try a boolean
+ requestSettingsSetAndForget(setUp, CaptureRequest.CONTROL_AE_LOCK, false);
+ requestSettingsSetAndForget(setUp, CaptureRequest.CONTROL_AE_LOCK, true);
+ // Try an int
+ requestSettingsSetAndForget(setUp, CaptureRequest.CONTROL_AE_MODE, 1);
+ requestSettingsSetAndForget(setUp, CaptureRequest.CONTROL_AE_MODE, -1);
+ requestSettingsSetAndForget(setUp, CaptureRequest.CONTROL_AE_MODE, 0);
+ // Try an int[]
+ requestSettingsSetAndForget(setUp, CaptureRequest.SENSOR_TEST_PATTERN_DATA, new int[] {1});
+ requestSettingsSetAndForget(setUp, CaptureRequest.SENSOR_TEST_PATTERN_DATA,
+ new int[] {2, 2});
+ }
+
+ @Test
+ public void requestSettingsSetNullValue() {
+ Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
+ requestSettingsSetAndForget(setUp, CaptureRequest.SENSOR_TEST_PATTERN_DATA, new int[] {1});
+ requestSettingsSetAndForget(setUp, CaptureRequest.SENSOR_TEST_PATTERN_DATA, null);
+ requestSettingsSetAndForget(setUp, CaptureRequest.SENSOR_TEST_PATTERN_DATA,
+ new int[] {2, 2});
+ }
+
+ @Test
+ public void requestSettingsSetUnsetAndContains() {
+ Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
+ assertFalse(setUp.contains(CaptureRequest.CONTROL_AE_LOCK));
+ setUp.set(CaptureRequest.CONTROL_AE_LOCK, false);
+ assertTrue(setUp.contains(CaptureRequest.CONTROL_AE_LOCK));
+ setUp.set(CaptureRequest.CONTROL_AE_LOCK, null);
+ assertTrue(setUp.contains(CaptureRequest.CONTROL_AE_LOCK));
+ setUp.unset(CaptureRequest.CONTROL_AE_LOCK);
+ assertFalse(setUp.contains(CaptureRequest.CONTROL_AE_LOCK));
+
+ setUp.set(CaptureRequest.CONTROL_AE_LOCK, null);
+ assertTrue(setUp.contains(CaptureRequest.CONTROL_AE_LOCK));
+ setUp.set(CaptureRequest.CONTROL_AE_LOCK, false);
+ assertTrue(setUp.contains(CaptureRequest.CONTROL_AE_LOCK));
+ setUp.unset(CaptureRequest.CONTROL_AE_LOCK);
+ assertFalse(setUp.contains(CaptureRequest.CONTROL_AE_LOCK));
+ }
+
+ @Test
+ public void requestSettingsSetStartsWithoutChanges() {
+ Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
+ assertEquals(0, setUp.getRevision());
+ }
+
+ private <T> void requestSettingsSetAndAssertChanged(Camera2RequestSettingsSet settings,
+ Key<T> key, T value,
+ boolean shouldHaveChanged) {
+ long revision = settings.getRevision();
+ assertEquals(shouldHaveChanged, settings.set(key, value));
+ assertEquals(shouldHaveChanged ? revision + 1 : revision, settings.getRevision());
+ }
+
+ @Test
+ public void requestSettingsSetChangesReportedCorrectly() {
+ Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
+ requestSettingsSetAndAssertChanged(setUp, CaptureRequest.CONTROL_AE_LOCK, false, true);
+ requestSettingsSetAndAssertChanged(setUp, CaptureRequest.CONTROL_AE_LOCK, false, false);
+ requestSettingsSetAndAssertChanged(setUp, CaptureRequest.CONTROL_AE_LOCK, true, true);
+ }
+
+ @Test
+ public void requestSettingsSetDetectsNoopChanges() {
+ Camera2RequestSettingsSet s = new Camera2RequestSettingsSet();
+ int[] one = {1}, two = {2};
+
+ requestSettingsSetAndAssertChanged(s, CaptureRequest.SENSOR_TEST_PATTERN_DATA, one, true);
+ requestSettingsSetAndAssertChanged(s, CaptureRequest.SENSOR_TEST_PATTERN_DATA, one, false);
+
+ requestSettingsSetAndAssertChanged(s, CaptureRequest.SENSOR_TEST_PATTERN_DATA, null, true);
+ requestSettingsSetAndAssertChanged(s, CaptureRequest.SENSOR_TEST_PATTERN_DATA, null, false);
+
+ requestSettingsSetAndAssertChanged(s, CaptureRequest.SENSOR_TEST_PATTERN_DATA, two, true);
+ requestSettingsSetAndAssertChanged(s, CaptureRequest.SENSOR_TEST_PATTERN_DATA, two, false);
+ }
+
+ private <T> void requestSettingsUnsetAndAssertChanged(Camera2RequestSettingsSet settings,
+ Key<T> key, boolean shouldHaveChanged) {
+ long revision = settings.getRevision();
+ assertEquals(shouldHaveChanged, settings.unset(key));
+ assertEquals(shouldHaveChanged ? revision + 1 : revision, settings.getRevision());
+ }
+
+ @Test
+ public void requestSettingsSetUnsetMakesChangesAndDetectsNoops() {
+ Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
+ requestSettingsUnsetAndAssertChanged(setUp, CaptureRequest.CONTROL_AE_LOCK, false);
+
+ requestSettingsSetAndAssertChanged(setUp, CaptureRequest.CONTROL_AE_LOCK, false, true);
+ requestSettingsUnsetAndAssertChanged(setUp, CaptureRequest.CONTROL_AE_LOCK, true);
+
+ requestSettingsSetAndAssertChanged(setUp, CaptureRequest.CONTROL_AE_LOCK, false, true);
+ requestSettingsSetAndAssertChanged(setUp, CaptureRequest.CONTROL_AE_LOCK, false, false);
+ requestSettingsUnsetAndAssertChanged(setUp, CaptureRequest.CONTROL_AE_LOCK, true);
+ requestSettingsUnsetAndAssertChanged(setUp, CaptureRequest.CONTROL_AE_LOCK, false);
+
+ requestSettingsSetAndAssertChanged(setUp, CaptureRequest.CONTROL_AE_LOCK, false, true);
+ requestSettingsSetAndAssertChanged(setUp, CaptureRequest.CONTROL_AE_LOCK, true, true);
+ requestSettingsUnsetAndAssertChanged(setUp, CaptureRequest.CONTROL_AE_LOCK, true);
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void requestSettingsSetNullArgToCopyConstructor() {
+ Camera2RequestSettingsSet flop = new Camera2RequestSettingsSet(null);
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void requestSettingsSetNullArgToSetKey() {
+ Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
+ setUp.set(null, null);
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void requestSettingsSetNullArgToUnset() {
+ Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
+ setUp.unset(null);
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void requestSettingsSetNullArgToContains() {
+ Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
+ setUp.contains(null);
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void requestSettingsSetNullArgToGet() {
+ Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
+ setUp.get(null);
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void requestSettingsSetNullArgToCreateRequest0() throws Exception {
+ Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
+ setUp.createRequest(null, 0);
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void requestSettingsSetNullArgToCreateRequest2() throws Exception {
+ Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
+ setUp.createRequest(mCamera, 0, (Surface) null);
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void requestSettingsSetNullArgToCreateRequest02() throws Exception {
+ Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
+ setUp.createRequest(null, 0, (Surface) null);
+ }
+
+ @Test
+ public void requestSettingsSetNullArgToUnion() {
+ Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
+ assertFalse(setUp.union(null));
+ assertEquals(0, setUp.getRevision());
+ }
+
+ @Test
+ public void requestSettingsSetSelfArgToUnion() {
+ Camera2RequestSettingsSet setUp = new Camera2RequestSettingsSet();
+ assertFalse(setUp.union(setUp));
+ assertEquals(0, setUp.getRevision());
+ }
+
+ @Test
+ public void requestSettingsSetCopyConstructor() {
+ Camera2RequestSettingsSet original = new Camera2RequestSettingsSet();
+ Camera2RequestSettingsSet unchanged = new Camera2RequestSettingsSet(original);
+
+ requestSettingsSetAndForget(original, CaptureRequest.CONTROL_AE_LOCK, true);
+ Camera2RequestSettingsSet changed = new Camera2RequestSettingsSet(original);
+ assertEquals(true, changed.get(CaptureRequest.CONTROL_AE_LOCK));
+ }
+
+ @Test
+ public void requestSettingsSetCopyConstructorPreservesChangedStatus() {
+ Camera2RequestSettingsSet original = new Camera2RequestSettingsSet();
+ Camera2RequestSettingsSet unchanged = new Camera2RequestSettingsSet(original);
+ assertEquals(original.getRevision(), unchanged.getRevision());
+
+ requestSettingsSetAndAssertChanged(original, CaptureRequest.CONTROL_AE_LOCK, true, true);
+ Camera2RequestSettingsSet changed = new Camera2RequestSettingsSet(original);
+ assertEquals(original.getRevision(), changed.getRevision());
+ assertNotSame(original.getRevision(), unchanged.getRevision());
+ }
+
+ @Test
+ public void requestSettingsSetCopyConstructorPerformsDeepCopy() {
+ Camera2RequestSettingsSet original = new Camera2RequestSettingsSet();
+ requestSettingsSetAndForget(original, CaptureRequest.CONTROL_AE_LOCK, true);
+
+ Camera2RequestSettingsSet changed = new Camera2RequestSettingsSet(original);
+ requestSettingsSetAndForget(changed, CaptureRequest.CONTROL_AE_LOCK, false);
+ assertEquals(true, original.get(CaptureRequest.CONTROL_AE_LOCK));
+ }
+
+ @Test
+ public void requestSettingsSetNullMeansDefault() throws Exception {
+ Camera2RequestSettingsSet s = new Camera2RequestSettingsSet();
+ CaptureRequest r1 = s.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW);
+ assertEquals((Object) CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW,
+ r1.get(CaptureRequest.CONTROL_CAPTURE_INTENT));
+
+ requestSettingsSetAndForget(s, CaptureRequest.CONTROL_CAPTURE_INTENT,
+ CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
+ CaptureRequest r2 = s.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW);
+ assertEquals((Object) CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE,
+ r2.get(CaptureRequest.CONTROL_CAPTURE_INTENT));
+
+ requestSettingsSetAndForget(s, CaptureRequest.CONTROL_CAPTURE_INTENT, null);
+ CaptureRequest r3 = s.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW);
+ assertEquals((Object) CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW,
+ r3.get(CaptureRequest.CONTROL_CAPTURE_INTENT));
+
+ s.unset(CaptureRequest.CONTROL_CAPTURE_INTENT);
+ CaptureRequest r4 = s.createRequest(mCamera, CameraDevice.TEMPLATE_PREVIEW);
+ assertEquals((Object) CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW,
+ r4.get(CaptureRequest.CONTROL_CAPTURE_INTENT));
+ }
+
+ @Test
+ public void requestSettingsSetNullPreservedByUnions() {
+ Camera2RequestSettingsSet master = new Camera2RequestSettingsSet();
+ requestSettingsSetAndForget(master, CaptureRequest.CONTROL_CAPTURE_INTENT,
+ CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW);
+
+ Camera2RequestSettingsSet slave = new Camera2RequestSettingsSet();
+ master.union(slave);
+ assertEquals((Object) CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW,
+ master.get(CaptureRequest.CONTROL_CAPTURE_INTENT));
+
+ requestSettingsSetAndForget(slave, CaptureRequest.CONTROL_CAPTURE_INTENT, null);
+ master.union(slave);
+ assertEquals(null, master.get(CaptureRequest.CONTROL_CAPTURE_INTENT));
+
+ requestSettingsSetAndForget(slave, CaptureRequest.CONTROL_CAPTURE_INTENT,
+ CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
+ master.union(slave);
+ assertEquals((Object) CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE,
+ master.get(CaptureRequest.CONTROL_CAPTURE_INTENT));
+
+ slave.unset(CaptureRequest.CONTROL_CAPTURE_INTENT);
+ master.union(slave);
+ assertEquals((Object) CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE,
+ master.get(CaptureRequest.CONTROL_CAPTURE_INTENT));
+ }
+
+ @Test
+ public void requestSettingsSetNullChangesRecorded() throws Exception {
+ Camera2RequestSettingsSet s = new Camera2RequestSettingsSet();
+ requestSettingsSetAndAssertChanged(s, CaptureRequest.CONTROL_CAPTURE_INTENT, null, true);
+ requestSettingsSetAndAssertChanged(s, CaptureRequest.CONTROL_CAPTURE_INTENT,
+ CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW, true);
+ requestSettingsSetAndAssertChanged(s, CaptureRequest.CONTROL_CAPTURE_INTENT, null, true);
+ requestSettingsSetAndAssertChanged(s, CaptureRequest.CONTROL_CAPTURE_INTENT, null, false);
+ }
+
+ @Test
+ public void requestSettingsSetUnionChangesRecorded() {
+ Camera2RequestSettingsSet[] sets = { new Camera2RequestSettingsSet(),
+ new Camera2RequestSettingsSet() };
+ sets[0].union(sets[1]);
+ assertEquals(1, sets[0].getRevision());
+ assertEquals(0, sets[1].getRevision());
+ }
+
+ private <T> void requestSettingsSetsCheckPairOfProperties(Camera2RequestSettingsSet firstSet,
+ Camera2RequestSettingsSet secondSet,
+ Key<T> firstKey,
+ Key<T> secondKey,
+ T expectedFirstSetFirstValue,
+ T expectedFirstSetSecondValue,
+ T expectedSecondSetFirstValue,
+ T expectedSecondSetSecondValue) {
+ assertEquals(expectedFirstSetFirstValue, firstSet.get(firstKey));
+ assertEquals(expectedFirstSetSecondValue, firstSet.get(secondKey));
+ assertEquals(expectedSecondSetFirstValue, secondSet.get(firstKey));
+ assertEquals(expectedSecondSetSecondValue, secondSet.get(secondKey));
+ }
+
+ @Test
+ public void requestSettingsSetUnionChangesReflected() {
+ Camera2RequestSettingsSet[] sets = { new Camera2RequestSettingsSet(),
+ new Camera2RequestSettingsSet() };
+
+ sets[0].set(CaptureRequest.CONTROL_AE_LOCK, true);
+ sets[1].set(CaptureRequest.CONTROL_AWB_LOCK, true);
+ sets[0].union(sets[1]);
+ sets[1].set(CaptureRequest.CONTROL_AE_LOCK, false);
+ requestSettingsSetsCheckPairOfProperties(sets[0], sets[1],
+ CaptureRequest.CONTROL_AE_LOCK, CaptureRequest.CONTROL_AWB_LOCK,
+ true, true, false, true);
+
+ sets[0].union(sets[1]);
+ requestSettingsSetsCheckPairOfProperties(sets[0], sets[1],
+ CaptureRequest.CONTROL_AE_LOCK, CaptureRequest.CONTROL_AWB_LOCK,
+ false, true, false, true);
+
+ sets[1].set(CaptureRequest.CONTROL_AE_LOCK, false);
+ sets[1].set(CaptureRequest.CONTROL_AWB_LOCK, false);
+ sets[0].union(sets[1]);
+ requestSettingsSetsCheckPairOfProperties(sets[0], sets[1],
+ CaptureRequest.CONTROL_AE_LOCK, CaptureRequest.CONTROL_AWB_LOCK,
+ false, false, false, false);
+ }
+}
diff --git a/camera2/utils/utils.mk b/camera2/utils/utils.mk
new file mode 100644
index 0000000..a193582
--- /dev/null
+++ b/camera2/utils/utils.mk
@@ -0,0 +1,23 @@
+# Copyright 2014 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android-ex-camera2-utils
+LOCAL_MODULE_TAGS := optional
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)