summaryrefslogtreecommitdiffstats
path: root/src/com/android/camera/PhotoModule.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/camera/PhotoModule.java')
-rw-r--r--src/com/android/camera/PhotoModule.java2006
1 files changed, 2006 insertions, 0 deletions
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
new file mode 100644
index 000000000..c65a49ef9
--- /dev/null
+++ b/src/com/android/camera/PhotoModule.java
@@ -0,0 +1,2006 @@
+/*
+ * 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.camera;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences.Editor;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.Size;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.location.Location;
+import android.media.CameraProfile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.SystemClock;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.OrientationEventListener;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.camera.CameraManager.CameraAFCallback;
+import com.android.camera.CameraManager.CameraAFMoveCallback;
+import com.android.camera.CameraManager.CameraPictureCallback;
+import com.android.camera.CameraManager.CameraProxy;
+import com.android.camera.CameraManager.CameraShutterCallback;
+import com.android.camera.ui.CountDownView.OnCountDownFinishedListener;
+import com.android.camera.ui.PopupManager;
+import com.android.camera.ui.RotateTextToast;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.gallery3d.exif.ExifTag;
+import com.android.gallery3d.exif.Rational;
+import com.android.gallery3d.filtershow.crop.CropActivity;
+import com.android.gallery3d.filtershow.crop.CropExtras;
+import com.android.gallery3d.util.UsageStatistics;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Formatter;
+import java.util.List;
+
+public class PhotoModule
+ implements CameraModule,
+ PhotoController,
+ FocusOverlayManager.Listener,
+ CameraPreference.OnPreferenceChangedListener,
+ ShutterButton.OnShutterButtonListener,
+ MediaSaveService.Listener,
+ OnCountDownFinishedListener,
+ SensorEventListener {
+
+ private static final String TAG = "CAM_PhotoModule";
+
+ // We number the request code from 1000 to avoid collision with Gallery.
+ private static final int REQUEST_CROP = 1000;
+
+ private static final int SETUP_PREVIEW = 1;
+ private static final int FIRST_TIME_INIT = 2;
+ private static final int CLEAR_SCREEN_DELAY = 3;
+ private static final int SET_CAMERA_PARAMETERS_WHEN_IDLE = 4;
+ private static final int CHECK_DISPLAY_ROTATION = 5;
+ private static final int SHOW_TAP_TO_FOCUS_TOAST = 6;
+ private static final int SWITCH_CAMERA = 7;
+ private static final int SWITCH_CAMERA_START_ANIMATION = 8;
+ private static final int CAMERA_OPEN_DONE = 9;
+ private static final int START_PREVIEW_DONE = 10;
+ private static final int OPEN_CAMERA_FAIL = 11;
+ private static final int CAMERA_DISABLED = 12;
+ private static final int CAPTURE_ANIMATION_DONE = 13;
+
+ // The subset of parameters we need to update in setCameraParameters().
+ private static final int UPDATE_PARAM_INITIALIZE = 1;
+ private static final int UPDATE_PARAM_ZOOM = 2;
+ private static final int UPDATE_PARAM_PREFERENCE = 4;
+ private static final int UPDATE_PARAM_ALL = -1;
+
+ // This is the timeout to keep the camera in onPause for the first time
+ // after screen on if the activity is started from secure lock screen.
+ private static final int KEEP_CAMERA_TIMEOUT = 1000; // ms
+
+ // copied from Camera hierarchy
+ private CameraActivity mActivity;
+ private CameraProxy mCameraDevice;
+ private int mCameraId;
+ private Parameters mParameters;
+ private boolean mPaused;
+
+ private PhotoUI mUI;
+
+ // The activity is going to switch to the specified camera id. This is
+ // needed because texture copy is done in GL thread. -1 means camera is not
+ // switching.
+ protected int mPendingSwitchCameraId = -1;
+ private boolean mOpenCameraFail;
+ private boolean mCameraDisabled;
+
+ // When setCameraParametersWhenIdle() is called, we accumulate the subsets
+ // needed to be updated in mUpdateSet.
+ private int mUpdateSet;
+
+ private static final int SCREEN_DELAY = 2 * 60 * 1000;
+
+ private int mZoomValue; // The current zoom value.
+
+ private Parameters mInitialParams;
+ private boolean mFocusAreaSupported;
+ private boolean mMeteringAreaSupported;
+ private boolean mAeLockSupported;
+ private boolean mAwbLockSupported;
+ private boolean mContinousFocusSupported;
+
+ // The degrees of the device rotated clockwise from its natural orientation.
+ private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
+ private ComboPreferences mPreferences;
+
+ private static final String sTempCropFilename = "crop-temp";
+
+ private ContentProviderClient mMediaProviderClient;
+ private boolean mFaceDetectionStarted = false;
+
+ // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
+ private String mCropValue;
+ private Uri mSaveUri;
+
+ // We use a queue to generated names of the images to be used later
+ // when the image is ready to be saved.
+ private NamedImages mNamedImages;
+
+ private Runnable mDoSnapRunnable = new Runnable() {
+ @Override
+ public void run() {
+ onShutterButtonClick();
+ }
+ };
+
+ private Runnable mFlashRunnable = new Runnable() {
+ @Override
+ public void run() {
+ animateFlash();
+ }
+ };
+
+ private final StringBuilder mBuilder = new StringBuilder();
+ private final Formatter mFormatter = new Formatter(mBuilder);
+ private final Object[] mFormatterArgs = new Object[1];
+
+ /**
+ * An unpublished intent flag requesting to return as soon as capturing
+ * is completed.
+ *
+ * TODO: consider publishing by moving into MediaStore.
+ */
+ private static final String EXTRA_QUICK_CAPTURE =
+ "android.intent.extra.quickCapture";
+
+ // The display rotation in degrees. This is only valid when mCameraState is
+ // not PREVIEW_STOPPED.
+ private int mDisplayRotation;
+ // The value for android.hardware.Camera.setDisplayOrientation.
+ private int mCameraDisplayOrientation;
+ // The value for UI components like indicators.
+ private int mDisplayOrientation;
+ // The value for android.hardware.Camera.Parameters.setRotation.
+ private int mJpegRotation;
+ private boolean mFirstTimeInitialized;
+ private boolean mIsImageCaptureIntent;
+
+ private int mCameraState = PREVIEW_STOPPED;
+ private boolean mSnapshotOnIdle = false;
+
+ private ContentResolver mContentResolver;
+
+ private LocationManager mLocationManager;
+
+ private final PostViewPictureCallback mPostViewPictureCallback =
+ new PostViewPictureCallback();
+ private final RawPictureCallback mRawPictureCallback =
+ new RawPictureCallback();
+ private final AutoFocusCallback mAutoFocusCallback =
+ new AutoFocusCallback();
+ private final Object mAutoFocusMoveCallback =
+ ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
+ ? new AutoFocusMoveCallback()
+ : null;
+
+ private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
+
+ private long mFocusStartTime;
+ private long mShutterCallbackTime;
+ private long mPostViewPictureCallbackTime;
+ private long mRawPictureCallbackTime;
+ private long mJpegPictureCallbackTime;
+ private long mOnResumeTime;
+ private byte[] mJpegImageData;
+
+ // These latency time are for the CameraLatency test.
+ public long mAutoFocusTime;
+ public long mShutterLag;
+ public long mShutterToPictureDisplayedTime;
+ public long mPictureDisplayedToJpegCallbackTime;
+ public long mJpegCallbackFinishTime;
+ public long mCaptureStartTime;
+
+ // This handles everything about focus.
+ private FocusOverlayManager mFocusManager;
+
+ private String mSceneMode;
+
+ private final Handler mHandler = new MainHandler();
+ private PreferenceGroup mPreferenceGroup;
+
+ private boolean mQuickCapture;
+ private SensorManager mSensorManager;
+ private float[] mGData = new float[3];
+ private float[] mMData = new float[3];
+ private float[] mR = new float[16];
+ private int mHeading = -1;
+
+ CameraStartUpThread mCameraStartUpThread;
+ ConditionVariable mStartPreviewPrerequisiteReady = new ConditionVariable();
+
+ private MediaSaveService.OnMediaSavedListener mOnMediaSavedListener =
+ new MediaSaveService.OnMediaSavedListener() {
+ @Override
+ public void onMediaSaved(Uri uri) {
+ if (uri != null) {
+ mActivity.notifyNewMedia(uri);
+ }
+ }
+ };
+
+ // The purpose is not to block the main thread in onCreate and onResume.
+ private class CameraStartUpThread extends Thread {
+ private volatile boolean mCancelled;
+
+ public void cancel() {
+ mCancelled = true;
+ interrupt();
+ }
+
+ public boolean isCanceled() {
+ return mCancelled;
+ }
+
+ @Override
+ public void run() {
+ try {
+ // We need to check whether the activity is paused before long
+ // operations to ensure that onPause() can be done ASAP.
+ if (mCancelled) return;
+ mCameraDevice = Util.openCamera(mActivity, mCameraId);
+ mParameters = mCameraDevice.getParameters();
+ // Wait until all the initialization needed by startPreview are
+ // done.
+ mStartPreviewPrerequisiteReady.block();
+
+ initializeCapabilities();
+ if (mFocusManager == null) initializeFocusManager();
+ if (mCancelled) return;
+ setCameraParameters(UPDATE_PARAM_ALL);
+ mHandler.sendEmptyMessage(CAMERA_OPEN_DONE);
+ if (mCancelled) return;
+ startPreview();
+ mHandler.sendEmptyMessage(START_PREVIEW_DONE);
+ mOnResumeTime = SystemClock.uptimeMillis();
+ mHandler.sendEmptyMessage(CHECK_DISPLAY_ROTATION);
+ } catch (CameraHardwareException e) {
+ mHandler.sendEmptyMessage(OPEN_CAMERA_FAIL);
+ } catch (CameraDisabledException e) {
+ mHandler.sendEmptyMessage(CAMERA_DISABLED);
+ }
+ }
+ }
+
+ /**
+ * This Handler is used to post message back onto the main thread of the
+ * application
+ */
+ private class MainHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SETUP_PREVIEW: {
+ setupPreview();
+ break;
+ }
+
+ case CLEAR_SCREEN_DELAY: {
+ mActivity.getWindow().clearFlags(
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ break;
+ }
+
+ case FIRST_TIME_INIT: {
+ initializeFirstTime();
+ break;
+ }
+
+ case SET_CAMERA_PARAMETERS_WHEN_IDLE: {
+ setCameraParametersWhenIdle(0);
+ break;
+ }
+
+ case CHECK_DISPLAY_ROTATION: {
+ // Set the display orientation if display rotation has changed.
+ // Sometimes this happens when the device is held upside
+ // down and camera app is opened. Rotation animation will
+ // take some time and the rotation value we have got may be
+ // wrong. Framework does not have a callback for this now.
+ if (Util.getDisplayRotation(mActivity) != mDisplayRotation) {
+ setDisplayOrientation();
+ }
+ if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
+ mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
+ }
+ break;
+ }
+
+ case SHOW_TAP_TO_FOCUS_TOAST: {
+ showTapToFocusToast();
+ break;
+ }
+
+ case SWITCH_CAMERA: {
+ switchCamera();
+ break;
+ }
+
+ case SWITCH_CAMERA_START_ANIMATION: {
+ // TODO: Need to revisit
+ // ((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
+ break;
+ }
+
+ case CAMERA_OPEN_DONE: {
+ onCameraOpened();
+ break;
+ }
+
+ case START_PREVIEW_DONE: {
+ onPreviewStarted();
+ break;
+ }
+
+ case OPEN_CAMERA_FAIL: {
+ mCameraStartUpThread = null;
+ mOpenCameraFail = true;
+ Util.showErrorAndFinish(mActivity,
+ R.string.cannot_connect_camera);
+ break;
+ }
+
+ case CAMERA_DISABLED: {
+ mCameraStartUpThread = null;
+ mCameraDisabled = true;
+ Util.showErrorAndFinish(mActivity,
+ R.string.camera_disabled);
+ break;
+ }
+ case CAPTURE_ANIMATION_DONE: {
+ mUI.enablePreviewThumb(false);
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void init(CameraActivity activity, View parent) {
+ mActivity = activity;
+ mUI = new PhotoUI(activity, this, parent);
+ mPreferences = new ComboPreferences(mActivity);
+ CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
+ mCameraId = getPreferredCameraId(mPreferences);
+
+ mContentResolver = mActivity.getContentResolver();
+
+ // To reduce startup time, open the camera and start the preview in
+ // another thread.
+ mCameraStartUpThread = new CameraStartUpThread();
+ mCameraStartUpThread.start();
+
+ // Surface texture is from camera screen nail and startPreview needs it.
+ // This must be done before startPreview.
+ mIsImageCaptureIntent = isImageCaptureIntent();
+
+ mPreferences.setLocalId(mActivity, mCameraId);
+ CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+ // we need to reset exposure for the preview
+ resetExposureCompensation();
+ // Starting the preview needs preferences, camera screen nail, and
+ // focus area indicator.
+ mStartPreviewPrerequisiteReady.open();
+
+ initializeControlByIntent();
+ mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
+ mLocationManager = new LocationManager(mActivity, mUI);
+ mSensorManager = (SensorManager)(mActivity.getSystemService(Context.SENSOR_SERVICE));
+ }
+
+ private void initializeControlByIntent() {
+ mUI.initializeControlByIntent();
+ if (mIsImageCaptureIntent) {
+ setupCaptureParams();
+ }
+ }
+
+ private void onPreviewStarted() {
+ mCameraStartUpThread = null;
+ setCameraState(IDLE);
+ startFaceDetection();
+ locationFirstRun();
+ }
+
+ // Prompt the user to pick to record location for the very first run of
+ // camera only
+ private void locationFirstRun() {
+ if (RecordLocationPreference.isSet(mPreferences)) {
+ return;
+ }
+ if (mActivity.isSecureCamera()) return;
+ // Check if the back camera exists
+ int backCameraId = CameraHolder.instance().getBackCameraId();
+ if (backCameraId == -1) {
+ // If there is no back camera, do not show the prompt.
+ return;
+ }
+ mUI.showLocationDialog();
+ }
+
+ public void enableRecordingLocation(boolean enable) {
+ setLocationPreference(enable ? RecordLocationPreference.VALUE_ON
+ : RecordLocationPreference.VALUE_OFF);
+ }
+
+ private void setLocationPreference(String value) {
+ mPreferences.edit()
+ .putString(CameraSettings.KEY_RECORD_LOCATION, value)
+ .apply();
+ // TODO: Fix this to use the actual onSharedPreferencesChanged listener
+ // instead of invoking manually
+ onSharedPreferenceChanged();
+ }
+
+ private void onCameraOpened() {
+ View root = mUI.getRootView();
+ // These depend on camera parameters.
+
+ int width = root.getWidth();
+ int height = root.getHeight();
+ mFocusManager.setPreviewSize(width, height);
+ openCameraCommon();
+ }
+
+ private void switchCamera() {
+ if (mPaused) return;
+
+ Log.v(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
+ mCameraId = mPendingSwitchCameraId;
+ mPendingSwitchCameraId = -1;
+ setCameraId(mCameraId);
+
+ // from onPause
+ closeCamera();
+ mUI.collapseCameraControls();
+ mUI.clearFaces();
+ if (mFocusManager != null) mFocusManager.removeMessages();
+
+ // Restart the camera and initialize the UI. From onCreate.
+ mPreferences.setLocalId(mActivity, mCameraId);
+ CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+ try {
+ mCameraDevice = Util.openCamera(mActivity, mCameraId);
+ mParameters = mCameraDevice.getParameters();
+ } catch (CameraHardwareException e) {
+ Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
+ return;
+ } catch (CameraDisabledException e) {
+ Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
+ return;
+ }
+ initializeCapabilities();
+ CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+ boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
+ mFocusManager.setMirror(mirror);
+ mFocusManager.setParameters(mInitialParams);
+ setupPreview();
+
+ // reset zoom value index
+ mZoomValue = 0;
+ openCameraCommon();
+
+ if (ApiHelper.HAS_SURFACE_TEXTURE) {
+ // Start switch camera animation. Post a message because
+ // onFrameAvailable from the old camera may already exist.
+ mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
+ }
+ }
+
+ protected void setCameraId(int cameraId) {
+ ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
+ pref.setValue("" + cameraId);
+ }
+
+ // either open a new camera or switch cameras
+ private void openCameraCommon() {
+ loadCameraPreferences();
+
+ mUI.onCameraOpened(mPreferenceGroup, mPreferences, mParameters, this);
+ updateSceneMode();
+ showTapToFocusToastIfNeeded();
+
+
+ }
+
+ public void onScreenSizeChanged(int width, int height, int previewWidth, int previewHeight) {
+ if (mFocusManager != null) mFocusManager.setPreviewSize(width, height);
+ }
+
+ private void resetExposureCompensation() {
+ String value = mPreferences.getString(CameraSettings.KEY_EXPOSURE,
+ CameraSettings.EXPOSURE_DEFAULT_VALUE);
+ if (!CameraSettings.EXPOSURE_DEFAULT_VALUE.equals(value)) {
+ Editor editor = mPreferences.edit();
+ editor.putString(CameraSettings.KEY_EXPOSURE, "0");
+ editor.apply();
+ }
+ }
+
+ private void keepMediaProviderInstance() {
+ // We want to keep a reference to MediaProvider in camera's lifecycle.
+ // TODO: Utilize mMediaProviderClient instance to replace
+ // ContentResolver calls.
+ if (mMediaProviderClient == null) {
+ mMediaProviderClient = mContentResolver
+ .acquireContentProviderClient(MediaStore.AUTHORITY);
+ }
+ }
+
+ // Snapshots can only be taken after this is called. It should be called
+ // once only. We could have done these things in onCreate() but we want to
+ // make preview screen appear as soon as possible.
+ private void initializeFirstTime() {
+ if (mFirstTimeInitialized) return;
+
+ // Initialize location service.
+ boolean recordLocation = RecordLocationPreference.get(
+ mPreferences, mContentResolver);
+ mLocationManager.recordLocation(recordLocation);
+
+ keepMediaProviderInstance();
+
+ mUI.initializeFirstTime();
+ MediaSaveService s = mActivity.getMediaSaveService();
+ // We set the listener only when both service and shutterbutton
+ // are initialized.
+ if (s != null) {
+ s.setListener(this);
+ }
+
+ mNamedImages = new NamedImages();
+
+ mFirstTimeInitialized = true;
+ addIdleHandler();
+
+ mActivity.updateStorageSpaceAndHint();
+ }
+
+ // If the activity is paused and resumed, this method will be called in
+ // onResume.
+ private void initializeSecondTime() {
+ // Start location update if needed.
+ boolean recordLocation = RecordLocationPreference.get(
+ mPreferences, mContentResolver);
+ mLocationManager.recordLocation(recordLocation);
+ MediaSaveService s = mActivity.getMediaSaveService();
+ if (s != null) {
+ s.setListener(this);
+ }
+ mNamedImages = new NamedImages();
+ mUI.initializeSecondTime(mParameters);
+ keepMediaProviderInstance();
+ }
+
+ @Override
+ public void onSurfaceCreated(SurfaceHolder holder) {
+ // Do not access the camera if camera start up thread is not finished.
+ if (mCameraDevice == null || mCameraStartUpThread != null)
+ return;
+
+ mCameraDevice.setPreviewDisplay(holder);
+ // This happens when onConfigurationChanged arrives, surface has been
+ // destroyed, and there is no onFullScreenChanged.
+ if (mCameraState == PREVIEW_STOPPED) {
+ setupPreview();
+ }
+ }
+
+ private void showTapToFocusToastIfNeeded() {
+ // Show the tap to focus toast if this is the first start.
+ if (mFocusAreaSupported &&
+ mPreferences.getBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, true)) {
+ // Delay the toast for one second to wait for orientation.
+ mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_FOCUS_TOAST, 1000);
+ }
+ }
+
+ private void addIdleHandler() {
+ MessageQueue queue = Looper.myQueue();
+ queue.addIdleHandler(new MessageQueue.IdleHandler() {
+ @Override
+ public boolean queueIdle() {
+ Storage.ensureOSXCompatible();
+ return false;
+ }
+ });
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+ @Override
+ public void startFaceDetection() {
+ if (!ApiHelper.HAS_FACE_DETECTION) return;
+ if (mFaceDetectionStarted) return;
+ if (mParameters.getMaxNumDetectedFaces() > 0) {
+ mFaceDetectionStarted = true;
+ CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+ mUI.onStartFaceDetection(mDisplayOrientation,
+ (info.facing == CameraInfo.CAMERA_FACING_FRONT));
+ mCameraDevice.setFaceDetectionCallback(mHandler, mUI);
+ mCameraDevice.startFaceDetection();
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+ @Override
+ public void stopFaceDetection() {
+ if (!ApiHelper.HAS_FACE_DETECTION) return;
+ if (!mFaceDetectionStarted) return;
+ if (mParameters.getMaxNumDetectedFaces() > 0) {
+ mFaceDetectionStarted = false;
+ mCameraDevice.setFaceDetectionCallback(null, null);
+ mCameraDevice.stopFaceDetection();
+ mUI.clearFaces();
+ }
+ }
+
+ private final class ShutterCallback
+ implements CameraShutterCallback {
+
+ private boolean mAnimateFlash;
+
+ public ShutterCallback(boolean animateFlash) {
+ mAnimateFlash = animateFlash;
+ }
+
+ @Override
+ public void onShutter(CameraProxy camera) {
+ mShutterCallbackTime = System.currentTimeMillis();
+ mShutterLag = mShutterCallbackTime - mCaptureStartTime;
+ Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
+ if (mAnimateFlash) {
+ mActivity.runOnUiThread(mFlashRunnable);
+ }
+ }
+ }
+
+ private final class PostViewPictureCallback
+ implements CameraPictureCallback {
+ @Override
+ public void onPictureTaken(byte [] data, CameraProxy camera) {
+ mPostViewPictureCallbackTime = System.currentTimeMillis();
+ Log.v(TAG, "mShutterToPostViewCallbackTime = "
+ + (mPostViewPictureCallbackTime - mShutterCallbackTime)
+ + "ms");
+ }
+ }
+
+ private final class RawPictureCallback
+ implements CameraPictureCallback {
+ @Override
+ public void onPictureTaken(byte [] rawData, CameraProxy camera) {
+ mRawPictureCallbackTime = System.currentTimeMillis();
+ Log.v(TAG, "mShutterToRawCallbackTime = "
+ + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
+ }
+ }
+
+ private final class JpegPictureCallback
+ implements CameraPictureCallback {
+ Location mLocation;
+
+ public JpegPictureCallback(Location loc) {
+ mLocation = loc;
+ }
+
+ @Override
+ public void onPictureTaken(final byte [] jpegData, CameraProxy camera) {
+ if (mPaused) {
+ return;
+ }
+ //TODO: We should show the picture taken rather than frozen preview here
+ if (mIsImageCaptureIntent) {
+ stopPreview();
+ }
+ if (mSceneMode == Util.SCENE_MODE_HDR) {
+ mUI.showSwitcher();
+ mUI.setSwipingEnabled(true);
+ }
+
+ mJpegPictureCallbackTime = System.currentTimeMillis();
+ // If postview callback has arrived, the captured image is displayed
+ // in postview callback. If not, the captured image is displayed in
+ // raw picture callback.
+ if (mPostViewPictureCallbackTime != 0) {
+ mShutterToPictureDisplayedTime =
+ mPostViewPictureCallbackTime - mShutterCallbackTime;
+ mPictureDisplayedToJpegCallbackTime =
+ mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
+ } else {
+ mShutterToPictureDisplayedTime =
+ mRawPictureCallbackTime - mShutterCallbackTime;
+ mPictureDisplayedToJpegCallbackTime =
+ mJpegPictureCallbackTime - mRawPictureCallbackTime;
+ }
+ Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
+ + mPictureDisplayedToJpegCallbackTime + "ms");
+
+ /*TODO:
+ // Only animate when in full screen capture mode
+ // i.e. If monkey/a user swipes to the gallery during picture taking,
+ // don't show animation
+ if (ApiHelper.HAS_SURFACE_TEXTURE && !mIsImageCaptureIntent
+ && mActivity.mShowCameraAppView) {
+ // Finish capture animation
+ mHandler.removeMessages(CAPTURE_ANIMATION_DONE);
+ ((CameraScreenNail) mActivity.mCameraScreenNail).animateSlide();
+ mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE,
+ CaptureAnimManager.getAnimationDuration());
+ } */
+ mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden.
+ if (!mIsImageCaptureIntent) {
+ if (ApiHelper.CAN_START_PREVIEW_IN_JPEG_CALLBACK) {
+ setupPreview();
+ } else {
+ // Camera HAL of some devices have a bug. Starting preview
+ // immediately after taking a picture will fail. Wait some
+ // time before starting the preview.
+ mHandler.sendEmptyMessageDelayed(SETUP_PREVIEW, 300);
+ }
+ }
+
+ if (!mIsImageCaptureIntent) {
+ // Calculate the width and the height of the jpeg.
+ Size s = mParameters.getPictureSize();
+ ExifInterface exif = Exif.getExif(jpegData);
+ int orientation = Exif.getOrientation(exif);
+ int width, height;
+ if ((mJpegRotation + orientation) % 180 == 0) {
+ width = s.width;
+ height = s.height;
+ } else {
+ width = s.height;
+ height = s.width;
+ }
+ String title = mNamedImages.getTitle();
+ long date = mNamedImages.getDate();
+ if (title == null) {
+ Log.e(TAG, "Unbalanced name/data pair");
+ } else {
+ if (date == -1) date = mCaptureStartTime;
+ if (mHeading >= 0) {
+ // heading direction has been updated by the sensor.
+ ExifTag directionRefTag = exif.buildTag(
+ ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
+ ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION);
+ ExifTag directionTag = exif.buildTag(
+ ExifInterface.TAG_GPS_IMG_DIRECTION,
+ new Rational(mHeading, 1));
+ exif.setTag(directionRefTag);
+ exif.setTag(directionTag);
+ }
+ mActivity.getMediaSaveService().addImage(
+ jpegData, title, date, mLocation, width, height,
+ orientation, exif, mOnMediaSavedListener, mContentResolver);
+ }
+ } else {
+ mJpegImageData = jpegData;
+ if (!mQuickCapture) {
+ mUI.showPostCaptureAlert();
+ } else {
+ onCaptureDone();
+ }
+ }
+
+ // Check this in advance of each shot so we don't add to shutter
+ // latency. It's true that someone else could write to the SD card in
+ // the mean time and fill it, but that could have happened between the
+ // shutter press and saving the JPEG too.
+ mActivity.updateStorageSpaceAndHint();
+
+ long now = System.currentTimeMillis();
+ mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
+ Log.v(TAG, "mJpegCallbackFinishTime = "
+ + mJpegCallbackFinishTime + "ms");
+ mJpegPictureCallbackTime = 0;
+ }
+ }
+
+ private final class AutoFocusCallback implements CameraAFCallback {
+ @Override
+ public void onAutoFocus(
+ boolean focused, CameraProxy camera) {
+ if (mPaused) return;
+
+ mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
+ Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
+ setCameraState(IDLE);
+ mFocusManager.onAutoFocus(focused, mUI.isShutterPressed());
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ private final class AutoFocusMoveCallback
+ implements CameraAFMoveCallback {
+ @Override
+ public void onAutoFocusMoving(
+ boolean moving, CameraProxy camera) {
+ mFocusManager.onAutoFocusMoving(moving);
+ }
+ }
+
+ private static class NamedImages {
+ private ArrayList<NamedEntity> mQueue;
+ private boolean mStop;
+ private NamedEntity mNamedEntity;
+
+ public NamedImages() {
+ mQueue = new ArrayList<NamedEntity>();
+ }
+
+ public void nameNewImage(ContentResolver resolver, long date) {
+ NamedEntity r = new NamedEntity();
+ r.title = Util.createJpegName(date);
+ r.date = date;
+ mQueue.add(r);
+ }
+
+ public String getTitle() {
+ if (mQueue.isEmpty()) {
+ mNamedEntity = null;
+ return null;
+ }
+ mNamedEntity = mQueue.get(0);
+ mQueue.remove(0);
+
+ return mNamedEntity.title;
+ }
+
+ // Must be called after getTitle().
+ public long getDate() {
+ if (mNamedEntity == null) return -1;
+ return mNamedEntity.date;
+ }
+
+ private static class NamedEntity {
+ String title;
+ long date;
+ }
+ }
+
+ private void setCameraState(int state) {
+ mCameraState = state;
+ switch (state) {
+ case PhotoController.PREVIEW_STOPPED:
+ case PhotoController.SNAPSHOT_IN_PROGRESS:
+ case PhotoController.SWITCHING_CAMERA:
+ mUI.enableGestures(false);
+ break;
+ case PhotoController.IDLE:
+ mUI.enableGestures(true);
+ break;
+ }
+ }
+
+ private void animateFlash() {
+ // Only animate when in full screen capture mode
+ // i.e. If monkey/a user swipes to the gallery during picture taking,
+ // don't show animation
+ if (!mIsImageCaptureIntent) {
+ mUI.animateFlash();
+
+ // TODO: mUI.enablePreviewThumb(true);
+ // mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE,
+ // CaptureAnimManager.getAnimationDuration());
+ }
+ }
+
+ @Override
+ public boolean capture() {
+ // If we are already in the middle of taking a snapshot or the image save request
+ // is full then ignore.
+ if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
+ || mCameraState == SWITCHING_CAMERA
+ || mActivity.getMediaSaveService().isQueueFull()) {
+ return false;
+ }
+ mCaptureStartTime = System.currentTimeMillis();
+ mPostViewPictureCallbackTime = 0;
+ mJpegImageData = null;
+
+ final boolean animateBefore = (mSceneMode == Util.SCENE_MODE_HDR);
+
+ if (animateBefore) {
+ animateFlash();
+ }
+
+ // Set rotation and gps data.
+ int orientation;
+ // We need to be consistent with the framework orientation (i.e. the
+ // orientation of the UI.) when the auto-rotate screen setting is on.
+ if (mActivity.isAutoRotateScreen()) {
+ orientation = (360 - mDisplayRotation) % 360;
+ } else {
+ orientation = mOrientation;
+ }
+ mJpegRotation = Util.getJpegRotation(mCameraId, orientation);
+ mParameters.setRotation(mJpegRotation);
+ Location loc = mLocationManager.getCurrentLocation();
+ Util.setGpsParameters(mParameters, loc);
+ mCameraDevice.setParameters(mParameters);
+
+ mCameraDevice.takePicture(mHandler,
+ new ShutterCallback(!animateBefore),
+ mRawPictureCallback, mPostViewPictureCallback,
+ new JpegPictureCallback(loc));
+
+ mNamedImages.nameNewImage(mContentResolver, mCaptureStartTime);
+
+ mFaceDetectionStarted = false;
+ setCameraState(SNAPSHOT_IN_PROGRESS);
+ UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+ UsageStatistics.ACTION_CAPTURE_DONE, "Photo");
+ return true;
+ }
+
+ @Override
+ public void setFocusParameters() {
+ setCameraParameters(UPDATE_PARAM_PREFERENCE);
+ }
+
+ private int getPreferredCameraId(ComboPreferences preferences) {
+ int intentCameraId = Util.getCameraFacingIntentExtras(mActivity);
+ if (intentCameraId != -1) {
+ // Testing purpose. Launch a specific camera through the intent
+ // extras.
+ return intentCameraId;
+ } else {
+ return CameraSettings.readPreferredCameraId(preferences);
+ }
+ }
+
+ private void updateSceneMode() {
+ // If scene mode is set, we cannot set flash mode, white balance, and
+ // focus mode, instead, we read it from driver
+ if (!Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
+ overrideCameraSettings(mParameters.getFlashMode(),
+ mParameters.getWhiteBalance(), mParameters.getFocusMode());
+ } else {
+ overrideCameraSettings(null, null, null);
+ }
+ }
+
+ private void overrideCameraSettings(final String flashMode,
+ final String whiteBalance, final String focusMode) {
+ mUI.overrideSettings(
+ CameraSettings.KEY_FLASH_MODE, flashMode,
+ CameraSettings.KEY_WHITE_BALANCE, whiteBalance,
+ CameraSettings.KEY_FOCUS_MODE, focusMode);
+ }
+
+ private void loadCameraPreferences() {
+ CameraSettings settings = new CameraSettings(mActivity, mInitialParams,
+ mCameraId, CameraHolder.instance().getCameraInfo());
+ mPreferenceGroup = settings.getPreferenceGroup(R.xml.camera_preferences);
+ }
+
+ @Override
+ public void onOrientationChanged(int orientation) {
+ // We keep the last known orientation. So if the user first orient
+ // the camera then point the camera to floor or sky, we still have
+ // the correct orientation.
+ if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
+ mOrientation = Util.roundOrientation(orientation, mOrientation);
+
+ // Show the toast after getting the first orientation changed.
+ if (mHandler.hasMessages(SHOW_TAP_TO_FOCUS_TOAST)) {
+ mHandler.removeMessages(SHOW_TAP_TO_FOCUS_TOAST);
+ showTapToFocusToast();
+ }
+ }
+
+ @Override
+ public void onStop() {
+ if (mMediaProviderClient != null) {
+ mMediaProviderClient.release();
+ mMediaProviderClient = null;
+ }
+ }
+
+ @Override
+ public void onCaptureCancelled() {
+ mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent());
+ mActivity.finish();
+ }
+
+ @Override
+ public void onCaptureRetake() {
+ if (mPaused)
+ return;
+ mUI.hidePostCaptureAlert();
+ setupPreview();
+ }
+
+ @Override
+ public void onCaptureDone() {
+ if (mPaused) {
+ return;
+ }
+
+ byte[] data = mJpegImageData;
+
+ if (mCropValue == null) {
+ // First handle the no crop case -- just return the value. If the
+ // caller specifies a "save uri" then write the data to its
+ // stream. Otherwise, pass back a scaled down version of the bitmap
+ // directly in the extras.
+ if (mSaveUri != null) {
+ OutputStream outputStream = null;
+ try {
+ outputStream = mContentResolver.openOutputStream(mSaveUri);
+ outputStream.write(data);
+ outputStream.close();
+
+ mActivity.setResultEx(Activity.RESULT_OK);
+ mActivity.finish();
+ } catch (IOException ex) {
+ // ignore exception
+ } finally {
+ Util.closeSilently(outputStream);
+ }
+ } else {
+ ExifInterface exif = Exif.getExif(data);
+ int orientation = Exif.getOrientation(exif);
+ Bitmap bitmap = Util.makeBitmap(data, 50 * 1024);
+ bitmap = Util.rotate(bitmap, orientation);
+ mActivity.setResultEx(Activity.RESULT_OK,
+ new Intent("inline-data").putExtra("data", bitmap));
+ mActivity.finish();
+ }
+ } else {
+ // Save the image to a temp file and invoke the cropper
+ Uri tempUri = null;
+ FileOutputStream tempStream = null;
+ try {
+ File path = mActivity.getFileStreamPath(sTempCropFilename);
+ path.delete();
+ tempStream = mActivity.openFileOutput(sTempCropFilename, 0);
+ tempStream.write(data);
+ tempStream.close();
+ tempUri = Uri.fromFile(path);
+ } catch (FileNotFoundException ex) {
+ mActivity.setResultEx(Activity.RESULT_CANCELED);
+ mActivity.finish();
+ return;
+ } catch (IOException ex) {
+ mActivity.setResultEx(Activity.RESULT_CANCELED);
+ mActivity.finish();
+ return;
+ } finally {
+ Util.closeSilently(tempStream);
+ }
+
+ Bundle newExtras = new Bundle();
+ if (mCropValue.equals("circle")) {
+ newExtras.putString("circleCrop", "true");
+ }
+ if (mSaveUri != null) {
+ newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
+ } else {
+ newExtras.putBoolean(CropExtras.KEY_RETURN_DATA, true);
+ }
+ if (mActivity.isSecureCamera()) {
+ newExtras.putBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, true);
+ }
+
+ Intent cropIntent = new Intent(CropActivity.CROP_ACTION);
+
+ cropIntent.setData(tempUri);
+ cropIntent.putExtras(newExtras);
+
+ mActivity.startActivityForResult(cropIntent, REQUEST_CROP);
+ }
+ }
+
+ @Override
+ public void onShutterButtonFocus(boolean pressed) {
+ if (mPaused || mUI.collapseCameraControls()
+ || (mCameraState == SNAPSHOT_IN_PROGRESS)
+ || (mCameraState == PREVIEW_STOPPED)) return;
+
+ // Do not do focus if there is not enough storage.
+ if (pressed && !canTakePicture()) return;
+
+ if (pressed) {
+ mFocusManager.onShutterDown();
+ } else {
+ // for countdown mode, we need to postpone the shutter release
+ // i.e. lock the focus during countdown.
+ if (!mUI.isCountingDown()) {
+ mFocusManager.onShutterUp();
+ }
+ }
+ }
+
+ @Override
+ public void onShutterButtonClick() {
+ if (mPaused || mUI.collapseCameraControls()
+ || (mCameraState == SWITCHING_CAMERA)
+ || (mCameraState == PREVIEW_STOPPED)) return;
+
+ // Do not take the picture if there is not enough storage.
+ if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) {
+ Log.i(TAG, "Not enough space or storage not ready. remaining="
+ + mActivity.getStorageSpace());
+ return;
+ }
+ Log.v(TAG, "onShutterButtonClick: mCameraState=" + mCameraState);
+
+ if (mSceneMode == Util.SCENE_MODE_HDR) {
+ mUI.hideSwitcher();
+ mUI.setSwipingEnabled(false);
+ }
+ // If the user wants to do a snapshot while the previous one is still
+ // in progress, remember the fact and do it after we finish the previous
+ // one and re-start the preview. Snapshot in progress also includes the
+ // state that autofocus is focusing and a picture will be taken when
+ // focus callback arrives.
+ if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)
+ && !mIsImageCaptureIntent) {
+ mSnapshotOnIdle = true;
+ return;
+ }
+
+ String timer = mPreferences.getString(
+ CameraSettings.KEY_TIMER,
+ mActivity.getString(R.string.pref_camera_timer_default));
+ boolean playSound = mPreferences.getString(CameraSettings.KEY_TIMER_SOUND_EFFECTS,
+ mActivity.getString(R.string.pref_camera_timer_sound_default))
+ .equals(mActivity.getString(R.string.setting_on_value));
+
+ int seconds = Integer.parseInt(timer);
+ // When shutter button is pressed, check whether the previous countdown is
+ // finished. If not, cancel the previous countdown and start a new one.
+ if (mUI.isCountingDown()) {
+ mUI.cancelCountDown();
+ }
+ if (seconds > 0) {
+ mUI.startCountDown(seconds, playSound);
+ } else {
+ mSnapshotOnIdle = false;
+ mFocusManager.doSnap();
+ }
+ }
+
+ @Override
+ public void installIntentFilter() {
+ }
+
+ @Override
+ public boolean updateStorageHintOnResume() {
+ return mFirstTimeInitialized;
+ }
+
+ @Override
+ public void updateCameraAppView() {
+ }
+
+ @Override
+ public void onResumeBeforeSuper() {
+ mPaused = false;
+ }
+
+ @Override
+ public void onResumeAfterSuper() {
+ if (mOpenCameraFail || mCameraDisabled) return;
+
+ mJpegPictureCallbackTime = 0;
+ mZoomValue = 0;
+ // Start the preview if it is not started.
+ if (mCameraState == PREVIEW_STOPPED && mCameraStartUpThread == null) {
+ resetExposureCompensation();
+ mCameraStartUpThread = new CameraStartUpThread();
+ mCameraStartUpThread.start();
+ }
+
+ // If first time initialization is not finished, put it in the
+ // message queue.
+ if (!mFirstTimeInitialized) {
+ mHandler.sendEmptyMessage(FIRST_TIME_INIT);
+ } else {
+ initializeSecondTime();
+ }
+ keepScreenOnAwhile();
+
+ // Dismiss open menu if exists.
+ PopupManager.getInstance(mActivity).notifyShowPopup(null);
+ UsageStatistics.onContentViewChanged(
+ UsageStatistics.COMPONENT_CAMERA, "PhotoModule");
+
+ Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ if (gsensor != null) {
+ mSensorManager.registerListener(this, gsensor, SensorManager.SENSOR_DELAY_NORMAL);
+ }
+
+ Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
+ if (msensor != null) {
+ mSensorManager.registerListener(this, msensor, SensorManager.SENSOR_DELAY_NORMAL);
+ }
+ }
+
+ void waitCameraStartUpThread() {
+ try {
+ if (mCameraStartUpThread != null) {
+ mCameraStartUpThread.cancel();
+ mCameraStartUpThread.join();
+ mCameraStartUpThread = null;
+ setCameraState(IDLE);
+ }
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+
+ @Override
+ public void onPauseBeforeSuper() {
+ mPaused = true;
+ Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ if (gsensor != null) {
+ mSensorManager.unregisterListener(this, gsensor);
+ }
+
+ Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
+ if (msensor != null) {
+ mSensorManager.unregisterListener(this, msensor);
+ }
+ }
+
+ @Override
+ public void onPauseAfterSuper() {
+ // Wait the camera start up thread to finish.
+ waitCameraStartUpThread();
+
+ // When camera is started from secure lock screen for the first time
+ // after screen on, the activity gets onCreate->onResume->onPause->onResume.
+ // To reduce the latency, keep the camera for a short time so it does
+ // not need to be opened again.
+ if (mCameraDevice != null && mActivity.isSecureCamera()
+ && CameraActivity.isFirstStartAfterScreenOn()) {
+ CameraActivity.resetFirstStartAfterScreenOn();
+ CameraHolder.instance().keep(KEEP_CAMERA_TIMEOUT);
+ }
+ // Reset the focus first. Camera CTS does not guarantee that
+ // cancelAutoFocus is allowed after preview stops.
+ if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
+ mCameraDevice.cancelAutoFocus();
+ }
+ stopPreview();
+
+ mNamedImages = null;
+
+ if (mLocationManager != null) mLocationManager.recordLocation(false);
+
+ // If we are in an image capture intent and has taken
+ // a picture, we just clear it in onPause.
+ mJpegImageData = null;
+
+ // Remove the messages in the event queue.
+ mHandler.removeMessages(SETUP_PREVIEW);
+ mHandler.removeMessages(FIRST_TIME_INIT);
+ mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
+ mHandler.removeMessages(SWITCH_CAMERA);
+ mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
+ mHandler.removeMessages(CAMERA_OPEN_DONE);
+ mHandler.removeMessages(START_PREVIEW_DONE);
+ mHandler.removeMessages(OPEN_CAMERA_FAIL);
+ mHandler.removeMessages(CAMERA_DISABLED);
+
+ closeCamera();
+
+ resetScreenOn();
+ mUI.onPause();
+
+ mPendingSwitchCameraId = -1;
+ if (mFocusManager != null) mFocusManager.removeMessages();
+ MediaSaveService s = mActivity.getMediaSaveService();
+ if (s != null) {
+ s.setListener(null);
+ }
+ }
+
+ /**
+ * The focus manager is the first UI related element to get initialized,
+ * and it requires the RenderOverlay, so initialize it here
+ */
+ private void initializeFocusManager() {
+ // Create FocusManager object. startPreview needs it.
+ // if mFocusManager not null, reuse it
+ // otherwise create a new instance
+ if (mFocusManager != null) {
+ mFocusManager.removeMessages();
+ } else {
+ CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+ boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
+ String[] defaultFocusModes = mActivity.getResources().getStringArray(
+ R.array.pref_camera_focusmode_default_array);
+ mFocusManager = new FocusOverlayManager(mPreferences, defaultFocusModes,
+ mInitialParams, this, mirror,
+ mActivity.getMainLooper(), mUI);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ Log.v(TAG, "onConfigurationChanged");
+ setDisplayOrientation();
+ }
+
+ @Override
+ public void updateCameraOrientation() {
+ if (mDisplayRotation != Util.getDisplayRotation(mActivity)) {
+ setDisplayOrientation();
+ }
+ }
+
+ @Override
+ public void onActivityResult(
+ int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case REQUEST_CROP: {
+ Intent intent = new Intent();
+ if (data != null) {
+ Bundle extras = data.getExtras();
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ }
+ mActivity.setResultEx(resultCode, intent);
+ mActivity.finish();
+
+ File path = mActivity.getFileStreamPath(sTempCropFilename);
+ path.delete();
+
+ break;
+ }
+ }
+ }
+
+ private boolean canTakePicture() {
+ return isCameraIdle() && (mActivity.getStorageSpace() > Storage.LOW_STORAGE_THRESHOLD);
+ }
+
+ @Override
+ public void autoFocus() {
+ mFocusStartTime = System.currentTimeMillis();
+ mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
+ setCameraState(FOCUSING);
+ }
+
+ @Override
+ public void cancelAutoFocus() {
+ mCameraDevice.cancelAutoFocus();
+ setCameraState(IDLE);
+ setCameraParameters(UPDATE_PARAM_PREFERENCE);
+ }
+
+ // Preview area is touched. Handle touch focus.
+ @Override
+ public void onSingleTapUp(View view, int x, int y) {
+ if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
+ || mCameraState == SNAPSHOT_IN_PROGRESS
+ || mCameraState == SWITCHING_CAMERA
+ || mCameraState == PREVIEW_STOPPED) {
+ return;
+ }
+
+ // Do not trigger touch focus if popup window is opened.
+ if (mUI.removeTopLevelPopup()) return;
+
+ // Check if metering area or focus area is supported.
+ if (!mFocusAreaSupported && !mMeteringAreaSupported) return;
+ mFocusManager.onSingleTapUp(x, y);
+ }
+
+ @Override
+ public boolean onBackPressed() {
+ return mUI.onBackPressed();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_FOCUS:
+ if (/*TODO: mActivity.isInCameraApp() &&*/ mFirstTimeInitialized) {
+ if (event.getRepeatCount() == 0) {
+ onShutterButtonFocus(true);
+ }
+ return true;
+ }
+ return false;
+ case KeyEvent.KEYCODE_CAMERA:
+ if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
+ onShutterButtonClick();
+ }
+ return true;
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ // If we get a dpad center event without any focused view, move
+ // the focus to the shutter button and press it.
+ if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
+ // Start auto-focus immediately to reduce shutter lag. After
+ // the shutter button gets the focus, onShutterButtonFocus()
+ // will be called again but it is fine.
+ if (mUI.removeTopLevelPopup()) return true;
+ onShutterButtonFocus(true);
+ mUI.pressShutterButton();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ if (/*mActivity.isInCameraApp() && */ mFirstTimeInitialized) {
+ onShutterButtonClick();
+ return true;
+ }
+ return false;
+ case KeyEvent.KEYCODE_FOCUS:
+ if (mFirstTimeInitialized) {
+ onShutterButtonFocus(false);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+ private void closeCamera() {
+ if (mCameraDevice != null) {
+ mCameraDevice.setZoomChangeListener(null);
+ if(ApiHelper.HAS_FACE_DETECTION) {
+ mCameraDevice.setFaceDetectionCallback(null, null);
+ }
+ mCameraDevice.setErrorCallback(null);
+ CameraHolder.instance().release();
+ mFaceDetectionStarted = false;
+ mCameraDevice = null;
+ setCameraState(PREVIEW_STOPPED);
+ mFocusManager.onCameraReleased();
+ }
+ }
+
+ private void setDisplayOrientation() {
+ mDisplayRotation = Util.getDisplayRotation(mActivity);
+ mDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
+ mCameraDisplayOrientation = mDisplayOrientation;
+ mUI.setDisplayOrientation(mDisplayOrientation);
+ if (mFocusManager != null) {
+ mFocusManager.setDisplayOrientation(mDisplayOrientation);
+ }
+ // Change the camera display orientation
+ if (mCameraDevice != null) {
+ mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
+ }
+ }
+
+ // Only called by UI thread.
+ private void setupPreview() {
+ mFocusManager.resetTouchFocus();
+ startPreview();
+ setCameraState(IDLE);
+ startFaceDetection();
+ }
+
+ // This can be called by UI Thread or CameraStartUpThread. So this should
+ // not modify the views.
+ private void startPreview() {
+ mCameraDevice.setErrorCallback(mErrorCallback);
+
+ // ICS camera frameworks has a bug. Face detection state is not cleared
+ // after taking a picture. Stop the preview to work around it. The bug
+ // was fixed in JB.
+ if (mCameraState != PREVIEW_STOPPED) stopPreview();
+
+ setDisplayOrientation();
+
+ if (!mSnapshotOnIdle) {
+ // If the focus mode is continuous autofocus, call cancelAutoFocus to
+ // resume it because it may have been paused by autoFocus call.
+ if (Util.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusManager.getFocusMode())) {
+ mCameraDevice.cancelAutoFocus();
+ }
+ mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
+ }
+ setCameraParameters(UPDATE_PARAM_ALL);
+ // Let UI set its expected aspect ratio
+ mUI.setPreviewSize(mParameters.getPreviewSize());
+ Object st = mUI.getSurfaceTexture();
+ if (st != null) {
+ mCameraDevice.setPreviewTexture((SurfaceTexture) st);
+ }
+
+ Log.v(TAG, "startPreview");
+ mCameraDevice.startPreview();
+ mFocusManager.onPreviewStarted();
+
+ if (mSnapshotOnIdle) {
+ mHandler.post(mDoSnapRunnable);
+ }
+ }
+
+ @Override
+ public void stopPreview() {
+ if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
+ Log.v(TAG, "stopPreview");
+ mCameraDevice.stopPreview();
+ mFaceDetectionStarted = false;
+ }
+ setCameraState(PREVIEW_STOPPED);
+ if (mFocusManager != null) mFocusManager.onPreviewStopped();
+ }
+
+ @SuppressWarnings("deprecation")
+ private void updateCameraParametersInitialize() {
+ // Reset preview frame rate to the maximum because it may be lowered by
+ // video camera application.
+ int[] fpsRange = Util.getMaxPreviewFpsRange(mParameters);
+ if (fpsRange.length > 0) {
+ mParameters.setPreviewFpsRange(
+ fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX],
+ fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]);
+ }
+
+ mParameters.set(Util.RECORDING_HINT, Util.FALSE);
+
+ // Disable video stabilization. Convenience methods not available in API
+ // level <= 14
+ String vstabSupported = mParameters.get("video-stabilization-supported");
+ if ("true".equals(vstabSupported)) {
+ mParameters.set("video-stabilization", "false");
+ }
+ }
+
+ private void updateCameraParametersZoom() {
+ // Set zoom.
+ if (mParameters.isZoomSupported()) {
+ mParameters.setZoom(mZoomValue);
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ private void setAutoExposureLockIfSupported() {
+ if (mAeLockSupported) {
+ mParameters.setAutoExposureLock(mFocusManager.getAeAwbLock());
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ private void setAutoWhiteBalanceLockIfSupported() {
+ if (mAwbLockSupported) {
+ mParameters.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+ private void setFocusAreasIfSupported() {
+ if (mFocusAreaSupported) {
+ mParameters.setFocusAreas(mFocusManager.getFocusAreas());
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+ private void setMeteringAreasIfSupported() {
+ if (mMeteringAreaSupported) {
+ // Use the same area for focus and metering.
+ mParameters.setMeteringAreas(mFocusManager.getMeteringAreas());
+ }
+ }
+
+ private void updateCameraParametersPreference() {
+ setAutoExposureLockIfSupported();
+ setAutoWhiteBalanceLockIfSupported();
+ setFocusAreasIfSupported();
+ setMeteringAreasIfSupported();
+
+ // Set picture size.
+ String pictureSize = mPreferences.getString(
+ CameraSettings.KEY_PICTURE_SIZE, null);
+ if (pictureSize == null) {
+ CameraSettings.initialCameraPictureSize(mActivity, mParameters);
+ } else {
+ List<Size> supported = mParameters.getSupportedPictureSizes();
+ CameraSettings.setCameraPictureSize(
+ pictureSize, supported, mParameters);
+ }
+ Size size = mParameters.getPictureSize();
+
+ // Set a preview size that is closest to the viewfinder height and has
+ // the right aspect ratio.
+ List<Size> sizes = mParameters.getSupportedPreviewSizes();
+ Size optimalSize = Util.getOptimalPreviewSize(mActivity, sizes,
+ (double) size.width / size.height);
+ Size original = mParameters.getPreviewSize();
+ if (!original.equals(optimalSize)) {
+ mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
+
+ // Zoom related settings will be changed for different preview
+ // sizes, so set and read the parameters to get latest values
+ if (mHandler.getLooper() == Looper.myLooper()) {
+ // On UI thread only, not when camera starts up
+ setupPreview();
+ } else {
+ mCameraDevice.setParameters(mParameters);
+ }
+ mParameters = mCameraDevice.getParameters();
+ }
+ Log.v(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height);
+
+ // Since changing scene mode may change supported values, set scene mode
+ // first. HDR is a scene mode. To promote it in UI, it is stored in a
+ // separate preference.
+ String hdr = mPreferences.getString(CameraSettings.KEY_CAMERA_HDR,
+ mActivity.getString(R.string.pref_camera_hdr_default));
+ if (mActivity.getString(R.string.setting_on_value).equals(hdr)) {
+ mSceneMode = Util.SCENE_MODE_HDR;
+ } else {
+ mSceneMode = mPreferences.getString(
+ CameraSettings.KEY_SCENE_MODE,
+ mActivity.getString(R.string.pref_camera_scenemode_default));
+ }
+ if (Util.isSupported(mSceneMode, mParameters.getSupportedSceneModes())) {
+ if (!mParameters.getSceneMode().equals(mSceneMode)) {
+ mParameters.setSceneMode(mSceneMode);
+
+ // Setting scene mode will change the settings of flash mode,
+ // white balance, and focus mode. Here we read back the
+ // parameters, so we can know those settings.
+ mCameraDevice.setParameters(mParameters);
+ mParameters = mCameraDevice.getParameters();
+ }
+ } else {
+ mSceneMode = mParameters.getSceneMode();
+ if (mSceneMode == null) {
+ mSceneMode = Parameters.SCENE_MODE_AUTO;
+ }
+ }
+
+ // Set JPEG quality.
+ int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
+ CameraProfile.QUALITY_HIGH);
+ mParameters.setJpegQuality(jpegQuality);
+
+ // For the following settings, we need to check if the settings are
+ // still supported by latest driver, if not, ignore the settings.
+
+ // Set exposure compensation
+ int value = CameraSettings.readExposure(mPreferences);
+ int max = mParameters.getMaxExposureCompensation();
+ int min = mParameters.getMinExposureCompensation();
+ if (value >= min && value <= max) {
+ mParameters.setExposureCompensation(value);
+ } else {
+ Log.w(TAG, "invalid exposure range: " + value);
+ }
+
+ if (Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
+ // Set flash mode.
+ String flashMode = mPreferences.getString(
+ CameraSettings.KEY_FLASH_MODE,
+ mActivity.getString(R.string.pref_camera_flashmode_default));
+ List<String> supportedFlash = mParameters.getSupportedFlashModes();
+ if (Util.isSupported(flashMode, supportedFlash)) {
+ mParameters.setFlashMode(flashMode);
+ } else {
+ flashMode = mParameters.getFlashMode();
+ if (flashMode == null) {
+ flashMode = mActivity.getString(
+ R.string.pref_camera_flashmode_no_flash);
+ }
+ }
+
+ // Set white balance parameter.
+ String whiteBalance = mPreferences.getString(
+ CameraSettings.KEY_WHITE_BALANCE,
+ mActivity.getString(R.string.pref_camera_whitebalance_default));
+ if (Util.isSupported(whiteBalance,
+ mParameters.getSupportedWhiteBalance())) {
+ mParameters.setWhiteBalance(whiteBalance);
+ } else {
+ whiteBalance = mParameters.getWhiteBalance();
+ if (whiteBalance == null) {
+ whiteBalance = Parameters.WHITE_BALANCE_AUTO;
+ }
+ }
+
+ // Set focus mode.
+ mFocusManager.overrideFocusMode(null);
+ mParameters.setFocusMode(mFocusManager.getFocusMode());
+ } else {
+ mFocusManager.overrideFocusMode(mParameters.getFocusMode());
+ }
+
+ if (mContinousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
+ updateAutoFocusMoveCallback();
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ private void updateAutoFocusMoveCallback() {
+ if (mParameters.getFocusMode().equals(Util.FOCUS_MODE_CONTINUOUS_PICTURE)) {
+ mCameraDevice.setAutoFocusMoveCallback(mHandler,
+ (CameraManager.CameraAFMoveCallback) mAutoFocusMoveCallback);
+ } else {
+ mCameraDevice.setAutoFocusMoveCallback(null, null);
+ }
+ }
+
+ // We separate the parameters into several subsets, so we can update only
+ // the subsets actually need updating. The PREFERENCE set needs extra
+ // locking because the preference can be changed from GLThread as well.
+ private void setCameraParameters(int updateSet) {
+ if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
+ updateCameraParametersInitialize();
+ }
+
+ if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
+ updateCameraParametersZoom();
+ }
+
+ if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
+ updateCameraParametersPreference();
+ }
+
+ mCameraDevice.setParameters(mParameters);
+ }
+
+ // If the Camera is idle, update the parameters immediately, otherwise
+ // accumulate them in mUpdateSet and update later.
+ private void setCameraParametersWhenIdle(int additionalUpdateSet) {
+ mUpdateSet |= additionalUpdateSet;
+ if (mCameraDevice == null) {
+ // We will update all the parameters when we open the device, so
+ // we don't need to do anything now.
+ mUpdateSet = 0;
+ return;
+ } else if (isCameraIdle()) {
+ setCameraParameters(mUpdateSet);
+ updateSceneMode();
+ mUpdateSet = 0;
+ } else {
+ if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
+ mHandler.sendEmptyMessageDelayed(
+ SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
+ }
+ }
+ }
+
+ public boolean isCameraIdle() {
+ return (mCameraState == IDLE) ||
+ (mCameraState == PREVIEW_STOPPED) ||
+ ((mFocusManager != null) && mFocusManager.isFocusCompleted()
+ && (mCameraState != SWITCHING_CAMERA));
+ }
+
+ public boolean isImageCaptureIntent() {
+ String action = mActivity.getIntent().getAction();
+ return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
+ || CameraActivity.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
+ }
+
+ private void setupCaptureParams() {
+ Bundle myExtras = mActivity.getIntent().getExtras();
+ if (myExtras != null) {
+ mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
+ mCropValue = myExtras.getString("crop");
+ }
+ }
+
+ @Override
+ public void onSharedPreferenceChanged() {
+ // ignore the events after "onPause()"
+ if (mPaused) return;
+
+ boolean recordLocation = RecordLocationPreference.get(
+ mPreferences, mContentResolver);
+ mLocationManager.recordLocation(recordLocation);
+
+ setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);
+ mUI.updateOnScreenIndicators(mParameters, mPreferenceGroup, mPreferences);
+ }
+
+ @Override
+ public void onCameraPickerClicked(int cameraId) {
+ if (mPaused || mPendingSwitchCameraId != -1) return;
+
+ mPendingSwitchCameraId = cameraId;
+
+ Log.v(TAG, "Start to switch camera. cameraId=" + cameraId);
+ // We need to keep a preview frame for the animation before
+ // releasing the camera. This will trigger onPreviewTextureCopied.
+ //TODO: Need to animate the camera switch
+ switchCamera();
+ }
+
+ // Preview texture has been copied. Now camera can be released and the
+ // animation can be started.
+ @Override
+ public void onPreviewTextureCopied() {
+ mHandler.sendEmptyMessage(SWITCH_CAMERA);
+ }
+
+ @Override
+ public void onCaptureTextureCopied() {
+ }
+
+ @Override
+ public void onUserInteraction() {
+ if (!mActivity.isFinishing()) keepScreenOnAwhile();
+ }
+
+ private void resetScreenOn() {
+ mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+ mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ private void keepScreenOnAwhile() {
+ mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+ mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
+ }
+
+ @Override
+ public void onOverriddenPreferencesClicked() {
+ if (mPaused) return;
+ mUI.showPreferencesToast();
+ }
+
+ private void showTapToFocusToast() {
+ // TODO: Use a toast?
+ new RotateTextToast(mActivity, R.string.tap_to_focus, 0).show();
+ // Clear the preference.
+ Editor editor = mPreferences.edit();
+ editor.putBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, false);
+ editor.apply();
+ }
+
+ private void initializeCapabilities() {
+ mInitialParams = mCameraDevice.getParameters();
+ mFocusAreaSupported = Util.isFocusAreaSupported(mInitialParams);
+ mMeteringAreaSupported = Util.isMeteringAreaSupported(mInitialParams);
+ mAeLockSupported = Util.isAutoExposureLockSupported(mInitialParams);
+ mAwbLockSupported = Util.isAutoWhiteBalanceLockSupported(mInitialParams);
+ mContinousFocusSupported = mInitialParams.getSupportedFocusModes().contains(
+ Util.FOCUS_MODE_CONTINUOUS_PICTURE);
+ }
+
+ @Override
+ public void onCountDownFinished() {
+ mSnapshotOnIdle = false;
+ mFocusManager.doSnap();
+ mFocusManager.onShutterUp();
+ }
+
+ @Override
+ public void onShowSwitcherPopup() {
+ mUI.onShowSwitcherPopup();
+ }
+
+ @Override
+ public int onZoomChanged(int index) {
+ // Not useful to change zoom value when the activity is paused.
+ if (mPaused) return index;
+ mZoomValue = index;
+ if (mParameters == null || mCameraDevice == null) return index;
+ // Set zoom parameters asynchronously
+ mParameters.setZoom(mZoomValue);
+ mCameraDevice.setParameters(mParameters);
+ Parameters p = mCameraDevice.getParameters();
+ if (p != null) return p.getZoom();
+ return index;
+ }
+
+ @Override
+ public int getCameraState() {
+ return mCameraState;
+ }
+
+ @Override
+ public void onQueueStatus(boolean full) {
+ mUI.enableShutter(!full);
+ }
+
+ @Override
+ public void onMediaSaveServiceConnected(MediaSaveService s) {
+ // We set the listener only when both service and shutterbutton
+ // are initialized.
+ if (mFirstTimeInitialized) {
+ s.setListener(this);
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ int type = event.sensor.getType();
+ float[] data;
+ if (type == Sensor.TYPE_ACCELEROMETER) {
+ data = mGData;
+ } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
+ data = mMData;
+ } else {
+ // we should not be here.
+ return;
+ }
+ for (int i = 0; i < 3 ; i++) {
+ data[i] = event.values[i];
+ }
+ float[] orientation = new float[3];
+ SensorManager.getRotationMatrix(mR, null, mGData, mMData);
+ SensorManager.getOrientation(mR, orientation);
+ mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
+ if (mHeading < 0) {
+ mHeading += 360;
+ }
+ }
+
+ @Override
+ public void onSwitchMode(boolean toCamera) {
+ mUI.onSwitchMode(toCamera);
+ }
+
+/* Below is no longer needed, except to get rid of compile error
+ * TODO: Remove these
+ */
+
+ // TODO: Delete this function after old camera code is removed
+ @Override
+ public void onRestorePreferencesClicked() {}
+
+}