blob: 66762fdd250823d67c4b1e81ecf3095df778ee1b [file] [log] [blame]
/*
* 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.os.Message;
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 = 3500;
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);
}});
}
}
/**
* 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) {
try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
getCameraHandler().obtainMessage(CameraActions.OPEN_CAMERA, cameraId, 0,
CameraOpenCallbackForward.getNewInstance(handler, callback)).sendToTarget();
}
});
} catch (final RuntimeException ex) {
getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* 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) {
try {
if (synced) {
// Don't bother to wait since camera is in bad state.
if (getCameraState().isInvalid()) {
return;
}
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();
}});
}
} catch (final RuntimeException ex) {
getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* Sets a callback for handling camera api runtime exceptions on
* a handler.
*/
public abstract void setCameraExceptionHandler(CameraExceptionHandler exceptionHandler);
/**
* 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();
/**
* @return The state machine tracking the camera API's current status.
*/
protected abstract CameraStateHolder getCameraState();
/**
* @return The exception handler.
*/
protected abstract CameraExceptionHandler getCameraExceptionHandler();
/**
* 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 abstract static 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();
/**
* @return The camera agent which creates this proxy.
*/
public abstract CameraAgent getAgent();
/**
* 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) {
try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
getCameraHandler().obtainMessage(CameraActions.RECONNECT, getCameraId(), 0,
CameraOpenCallbackForward.getNewInstance(handler, cb)).sendToTarget();
}});
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* Unlocks the camera device.
*
* @see android.hardware.Camera#unlock()
*/
public void unlock() {
// Don't bother to wait since camera is in bad state.
if (getCameraState().isInvalid()) {
return;
}
final WaitDoneBundle bundle = new WaitDoneBundle();
try {
getDispatchThread().runJobSync(new Runnable() {
@Override
public void run() {
getCameraHandler().sendEmptyMessage(CameraActions.UNLOCK);
getCameraHandler().post(bundle.mUnlockRunnable);
}
}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "camera unlock");
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* Locks the camera device.
* @see android.hardware.Camera#lock()
*/
public void lock() {
try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
getCameraHandler().sendEmptyMessage(CameraActions.LOCK);
}});
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* Sets the {@link android.graphics.SurfaceTexture} for preview.
*
* <p>Note that, once this operation has been performed, it is no longer
* possible to change the preview or photo sizes in the
* {@link CameraSettings} instance for this camera, and the mutators for
* these fields are allowed to ignore all further invocations until the
* preview is stopped with {@link #stopPreview}.</p>
*
* @param surfaceTexture The {@link SurfaceTexture} for preview.
*
* @see CameraSettings#setPhotoSize
* @see CameraSettings#setPreviewSize
*/
// XXX: Despite the above documentation about locking the sizes, the API
// 1 implementation doesn't currently enforce this at all, although the
// Camera class warns that preview sizes shouldn't be changed while a
// preview is running. Furthermore, the API 2 implementation doesn't yet
// unlock the sizes when stopPreview() is invoked (see related FIXME on
// the STOP_PREVIEW case in its handler; in the meantime, changing API 2
// sizes would require closing and reopening the camera.
public void setPreviewTexture(final SurfaceTexture surfaceTexture) {
try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
getCameraHandler()
.obtainMessage(CameraActions.SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture)
.sendToTarget();
}});
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* Blocks until a {@link android.graphics.SurfaceTexture} has been set
* for preview.
*
* <p>Note that, once this operation has been performed, it is no longer
* possible to change the preview or photo sizes in the
* {@link CameraSettings} instance for this camera, and the mutators for
* these fields are allowed to ignore all further invocations.</p>
*
* @param surfaceTexture The {@link SurfaceTexture} for preview.
*
* @see CameraSettings#setPhotoSize
* @see CameraSettings#setPreviewSize
*/
public void setPreviewTextureSync(final SurfaceTexture surfaceTexture) {
// Don't bother to wait since camera is in bad state.
if (getCameraState().isInvalid()) {
return;
}
final WaitDoneBundle bundle = new WaitDoneBundle();
try {
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");
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* Sets the {@link android.view.SurfaceHolder} for preview.
*
* @param surfaceHolder The {@link SurfaceHolder} for preview.
*/
public void setPreviewDisplay(final SurfaceHolder surfaceHolder) {
try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
getCameraHandler()
.obtainMessage(CameraActions.SET_PREVIEW_DISPLAY_ASYNC, surfaceHolder)
.sendToTarget();
}});
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* Starts the camera preview.
*/
public void startPreview() {
try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
getCameraHandler()
.obtainMessage(CameraActions.START_PREVIEW_ASYNC, null).sendToTarget();
}});
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* Starts the camera preview and executes a callback on a handler once
* the preview starts.
*/
public void startPreviewWithCallback(final Handler h, final CameraStartPreviewCallback cb) {
try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
getCameraHandler().obtainMessage(CameraActions.START_PREVIEW_ASYNC,
CameraStartPreviewCallbackForward.getNewInstance(h, cb))
.sendToTarget();
}});
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* 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() {
// Don't bother to wait since camera is in bad state.
if (getCameraState().isInvalid()) {
return;
}
final WaitDoneBundle bundle = new WaitDoneBundle();
try {
getDispatchThread().runJobSync(new Runnable() {
@Override
public void run() {
getCameraHandler().obtainMessage(CameraActions.STOP_PREVIEW, bundle)
.sendToTarget();
}}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "stop preview");
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* 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) {
try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
getCameraHandler()
.obtainMessage(CameraActions.ADD_CALLBACK_BUFFER, callbackBuffer)
.sendToTarget();
}
});
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* 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.
*
* <p>This action has the highest priority and will get processed before anything
* else that is pending. Moreover, any pending auto-focuses that haven't yet
* began will also be ignored.</p>
*/
public void cancelAutoFocus() {
// Do not use the dispatch thread since we want to avoid a wait-cycle
// between applySettingsHelper which waits until the state is not FOCUSING.
// cancelAutoFocus should get executed asap, set the state back to idle.
getCameraHandler().sendMessageAtFrontOfQueue(
getCameraHandler().obtainMessage(CameraActions.CANCEL_AUTO_FOCUS));
getCameraHandler().sendEmptyMessage(CameraActions.CANCEL_AUTO_FOCUS_FINISH);
}
/**
* 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) {
try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
getCameraHandler()
.obtainMessage(CameraActions.SET_DISPLAY_ORIENTATION, degrees,
capture ? 1 : 0)
.sendToTarget();
}});
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
public void setJpegOrientation(final int degrees) {
try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
getCameraHandler()
.obtainMessage(CameraActions.SET_JPEG_ORIENTATION, degrees, 0)
.sendToTarget();
}});
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* 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() {
try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
getCameraHandler().sendEmptyMessage(CameraActions.START_FACE_DETECTION);
}});
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* Stops the face detection.
*/
public void stopFaceDetection() {
try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
getCameraHandler().sendEmptyMessage(CameraActions.STOP_FACE_DETECTION);
}});
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* 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 argument in applySettings()");
return false;
}
if (!getCapabilities().supports(settings)) {
Log.w(TAG, "Unsupported settings in applySettings()");
return false;
}
final CameraSettings copyOfSettings = settings.copy();
try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
CameraStateHolder cameraState = getCameraState();
// Don't bother to wait since camera is in bad state.
if (cameraState.isInvalid()) {
return;
}
cameraState.waitForStates(statesToAwait);
getCameraHandler().obtainMessage(CameraActions.APPLY_SETTINGS, copyOfSettings)
.sendToTarget();
}});
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
return true;
}
/**
* Applies the settings to the camera device.
*
* <p>If the camera is either focusing or capturing; settings applications
* will be (asynchronously) deferred until those operations complete.</p>
*
* @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() {
try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
getCameraHandler().sendEmptyMessage(CameraActions.REFRESH_PARAMETERS);
}});
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* 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) {
try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
getCameraHandler()
.obtainMessage(CameraActions.ENABLE_SHUTTER_SOUND, (enable ? 1 : 0), 0)
.sendToTarget();
}});
} catch (final RuntimeException ex) {
getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
/**
* 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();
}
}};
}
/**
* Notify all synchronous waiters waiting on message completion with {@link #mWaitLock}.
*
* <p>This assumes that the message was sent with {@code this} as the {@code Message#obj}.
* Otherwise the message is ignored.</p>
*/
/*package*/ static void unblockSyncWaiters(Message msg) {
if (msg == null) return;
if (msg.obj instanceof WaitDoneBundle) {
WaitDoneBundle bundle = (WaitDoneBundle)msg.obj;
bundle.mUnlockRunnable.run();
}
}
}
}