PhotoModule.java revision 367c7c82a22ec007771059beec4aeff3ef96310e
1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera;
18
19import android.annotation.TargetApi;
20import android.app.Activity;
21import android.content.ContentProviderClient;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.SharedPreferences.Editor;
26import android.content.res.Configuration;
27import android.graphics.Bitmap;
28import android.graphics.SurfaceTexture;
29import android.hardware.Camera.CameraInfo;
30import android.hardware.Camera.Parameters;
31import android.hardware.Camera.Size;
32import android.hardware.Sensor;
33import android.hardware.SensorEvent;
34import android.hardware.SensorEventListener;
35import android.hardware.SensorManager;
36import android.location.Location;
37import android.media.CameraProfile;
38import android.net.Uri;
39import android.os.Build;
40import android.os.Bundle;
41import android.os.Handler;
42import android.os.Looper;
43import android.os.Message;
44import android.os.MessageQueue;
45import android.os.SystemClock;
46import android.provider.MediaStore;
47import android.util.Log;
48import android.view.KeyEvent;
49import android.view.OrientationEventListener;
50import android.view.View;
51import android.view.WindowManager;
52
53import com.android.camera.CameraManager.CameraAFCallback;
54import com.android.camera.CameraManager.CameraAFMoveCallback;
55import com.android.camera.CameraManager.CameraPictureCallback;
56import com.android.camera.CameraManager.CameraProxy;
57import com.android.camera.CameraManager.CameraShutterCallback;
58import com.android.camera.PhotoModule.NamedImages.NamedEntity;
59import com.android.camera.exif.ExifInterface;
60import com.android.camera.exif.ExifTag;
61import com.android.camera.exif.Rational;
62import com.android.camera.ui.CountDownView.OnCountDownFinishedListener;
63import com.android.camera.ui.ModuleSwitcher;
64import com.android.camera.ui.RotateTextToast;
65import com.android.camera.util.ApiHelper;
66import com.android.camera.util.CameraUtil;
67import com.android.camera.util.GcamHelper;
68import com.android.camera.util.UsageStatistics;
69import com.android.camera2.R;
70
71import java.io.File;
72import java.io.FileNotFoundException;
73import java.io.FileOutputStream;
74import java.io.IOException;
75import java.io.OutputStream;
76import java.util.List;
77import java.util.Vector;
78
79public class PhotoModule
80        implements CameraModule,
81        PhotoController,
82        FocusOverlayManager.Listener,
83        CameraPreference.OnPreferenceChangedListener,
84        ShutterButton.OnShutterButtonListener,
85        MediaSaveService.Listener,
86        OnCountDownFinishedListener,
87        SensorEventListener {
88
89    private static final String TAG = "CAM_PhotoModule";
90
91    // We number the request code from 1000 to avoid collision with Gallery.
92    private static final int REQUEST_CROP = 1000;
93
94    private static final int SETUP_PREVIEW = 1;
95    private static final int FIRST_TIME_INIT = 2;
96    private static final int CLEAR_SCREEN_DELAY = 3;
97    private static final int SET_CAMERA_PARAMETERS_WHEN_IDLE = 4;
98    private static final int SHOW_TAP_TO_FOCUS_TOAST = 5;
99    private static final int SWITCH_CAMERA = 6;
100    private static final int SWITCH_CAMERA_START_ANIMATION = 7;
101    private static final int CAMERA_OPEN_DONE = 8;
102    private static final int OPEN_CAMERA_FAIL = 9;
103    private static final int CAMERA_DISABLED = 10;
104    private static final int SWITCH_TO_GCAM_MODULE = 11;
105
106    // The subset of parameters we need to update in setCameraParameters().
107    private static final int UPDATE_PARAM_INITIALIZE = 1;
108    private static final int UPDATE_PARAM_ZOOM = 2;
109    private static final int UPDATE_PARAM_PREFERENCE = 4;
110    private static final int UPDATE_PARAM_ALL = -1;
111
112    // This is the timeout to keep the camera in onPause for the first time
113    // after screen on if the activity is started from secure lock screen.
114    private static final int KEEP_CAMERA_TIMEOUT = 1000; // ms
115
116    private static final String DEBUG_IMAGE_PREFIX = "DEBUG_";
117
118    // copied from Camera hierarchy
119    private CameraActivity mActivity;
120    private CameraProxy mCameraDevice;
121    private int mCameraId;
122    private Parameters mParameters;
123    private boolean mPaused;
124
125    private PhotoUI mUI;
126
127    // The activity is going to switch to the specified camera id. This is
128    // needed because texture copy is done in GL thread. -1 means camera is not
129    // switching.
130    protected int mPendingSwitchCameraId = -1;
131    private boolean mOpenCameraFail;
132    private boolean mCameraDisabled;
133
134    // When setCameraParametersWhenIdle() is called, we accumulate the subsets
135    // needed to be updated in mUpdateSet.
136    private int mUpdateSet;
137
138    private static final int SCREEN_DELAY = 2 * 60 * 1000;
139
140    private int mZoomValue;  // The current zoom value.
141
142    private Parameters mInitialParams;
143    private boolean mFocusAreaSupported;
144    private boolean mMeteringAreaSupported;
145    private boolean mAeLockSupported;
146    private boolean mAwbLockSupported;
147    private boolean mContinuousFocusSupported;
148
149    // The degrees of the device rotated clockwise from its natural orientation.
150    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
151    private ComboPreferences mPreferences;
152
153    private static final String sTempCropFilename = "crop-temp";
154
155    private ContentProviderClient mMediaProviderClient;
156    private boolean mFaceDetectionStarted = false;
157
158    // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
159    private String mCropValue;
160    private Uri mSaveUri;
161
162    private Uri mDebugUri;
163
164    // We use a queue to generated names of the images to be used later
165    // when the image is ready to be saved.
166    private NamedImages mNamedImages;
167
168    private Runnable mDoSnapRunnable = new Runnable() {
169        @Override
170        public void run() {
171            onShutterButtonClick();
172        }
173    };
174
175    /**
176     * An unpublished intent flag requesting to return as soon as capturing
177     * is completed.
178     *
179     * TODO: consider publishing by moving into MediaStore.
180     */
181    private static final String EXTRA_QUICK_CAPTURE =
182            "android.intent.extra.quickCapture";
183
184    // The display rotation in degrees. This is only valid when mCameraState is
185    // not PREVIEW_STOPPED.
186    private int mDisplayRotation;
187    // The value for android.hardware.Camera.setDisplayOrientation.
188    private int mCameraDisplayOrientation;
189    // The value for UI components like indicators.
190    private int mDisplayOrientation;
191    // The value for android.hardware.Camera.Parameters.setRotation.
192    private int mJpegRotation;
193    // Indicates whether we are using front camera
194    private boolean mMirror;
195    private boolean mFirstTimeInitialized;
196    private boolean mIsImageCaptureIntent;
197
198    private int mCameraState = PREVIEW_STOPPED;
199    private boolean mSnapshotOnIdle = false;
200
201    private ContentResolver mContentResolver;
202
203    private LocationManager mLocationManager;
204
205    private final PostViewPictureCallback mPostViewPictureCallback =
206            new PostViewPictureCallback();
207    private final RawPictureCallback mRawPictureCallback =
208            new RawPictureCallback();
209    private final AutoFocusCallback mAutoFocusCallback =
210            new AutoFocusCallback();
211    private final Object mAutoFocusMoveCallback =
212            ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
213                    ? new AutoFocusMoveCallback()
214                    : null;
215
216    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
217
218    private long mFocusStartTime;
219    private long mShutterCallbackTime;
220    private long mPostViewPictureCallbackTime;
221    private long mRawPictureCallbackTime;
222    private long mJpegPictureCallbackTime;
223    private long mOnResumeTime;
224    private byte[] mJpegImageData;
225
226    // These latency time are for the CameraLatency test.
227    public long mAutoFocusTime;
228    public long mShutterLag;
229    public long mShutterToPictureDisplayedTime;
230    public long mPictureDisplayedToJpegCallbackTime;
231    public long mJpegCallbackFinishTime;
232    public long mCaptureStartTime;
233
234    // This handles everything about focus.
235    private FocusOverlayManager mFocusManager;
236
237    private String mSceneMode;
238
239    private final Handler mHandler = new MainHandler();
240    private PreferenceGroup mPreferenceGroup;
241
242    private boolean mQuickCapture;
243    private SensorManager mSensorManager;
244    private float[] mGData = new float[3];
245    private float[] mMData = new float[3];
246    private float[] mR = new float[16];
247    private int mHeading = -1;
248
249    // True if all the parameters needed to start preview is ready.
250    private boolean mCameraPreviewParamsReady = false;
251
252    private MediaSaveService.OnMediaSavedListener mOnMediaSavedListener =
253            new MediaSaveService.OnMediaSavedListener() {
254                @Override
255                public void onMediaSaved(Uri uri) {
256                    if (uri != null) {
257                        mActivity.notifyNewMedia(uri);
258                    }
259                }
260            };
261
262    private void checkDisplayRotation() {
263        // Set the display orientation if display rotation has changed.
264        // Sometimes this happens when the device is held upside
265        // down and camera app is opened. Rotation animation will
266        // take some time and the rotation value we have got may be
267        // wrong. Framework does not have a callback for this now.
268        if (CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation) {
269            setDisplayOrientation();
270        }
271        if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
272            mHandler.postDelayed(new Runnable() {
273                @Override
274                public void run() {
275                    checkDisplayRotation();
276                }
277            }, 100);
278        }
279    }
280
281    /**
282     * This Handler is used to post message back onto the main thread of the
283     * application
284     */
285    private class MainHandler extends Handler {
286        @Override
287        public void handleMessage(Message msg) {
288            switch (msg.what) {
289                case SETUP_PREVIEW: {
290                    setupPreview();
291                    break;
292                }
293
294                case CLEAR_SCREEN_DELAY: {
295                    mActivity.getWindow().clearFlags(
296                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
297                    break;
298                }
299
300                case FIRST_TIME_INIT: {
301                    initializeFirstTime();
302                    break;
303                }
304
305                case SET_CAMERA_PARAMETERS_WHEN_IDLE: {
306                    setCameraParametersWhenIdle(0);
307                    break;
308                }
309
310                case SHOW_TAP_TO_FOCUS_TOAST: {
311                    showTapToFocusToast();
312                    break;
313                }
314
315                case SWITCH_CAMERA: {
316                    switchCamera();
317                    break;
318                }
319
320                case SWITCH_CAMERA_START_ANIMATION: {
321                    // TODO: Need to revisit
322                    // ((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
323                    break;
324                }
325
326                case CAMERA_OPEN_DONE: {
327                    onCameraOpened();
328                    break;
329                }
330
331                case OPEN_CAMERA_FAIL: {
332                    mOpenCameraFail = true;
333                    CameraUtil.showErrorAndFinish(mActivity,
334                            R.string.cannot_connect_camera);
335                    break;
336                }
337
338                case CAMERA_DISABLED: {
339                    mCameraDisabled = true;
340                    CameraUtil.showErrorAndFinish(mActivity,
341                            R.string.camera_disabled);
342                    break;
343                }
344
345                case SWITCH_TO_GCAM_MODULE: {
346                    mActivity.onModuleSelected(ModuleSwitcher.GCAM_MODULE_INDEX);
347                }
348            }
349        }
350    }
351
352
353    @Override
354    public void init(CameraActivity activity, View parent) {
355        mActivity = activity;
356        mUI = new PhotoUI(activity, this, parent);
357        mPreferences = new ComboPreferences(mActivity);
358        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
359        mCameraId = getPreferredCameraId(mPreferences);
360
361        mContentResolver = mActivity.getContentResolver();
362
363        // Surface texture is from camera screen nail and startPreview needs it.
364        // This must be done before startPreview.
365        mIsImageCaptureIntent = isImageCaptureIntent();
366
367        mPreferences.setLocalId(mActivity, mCameraId);
368        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
369        // we need to reset exposure for the preview
370        resetExposureCompensation();
371
372        initializeControlByIntent();
373        mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
374        mLocationManager = new LocationManager(mActivity, mUI);
375        mSensorManager = (SensorManager)(mActivity.getSystemService(Context.SENSOR_SERVICE));
376    }
377
378    private void initializeControlByIntent() {
379        mUI.initializeControlByIntent();
380        if (mIsImageCaptureIntent) {
381            setupCaptureParams();
382        }
383    }
384
385    private void onPreviewStarted() {
386        setCameraState(IDLE);
387        startFaceDetection();
388        locationFirstRun();
389    }
390
391    // Prompt the user to pick to record location for the very first run of
392    // camera only
393    private void locationFirstRun() {
394        if (RecordLocationPreference.isSet(mPreferences)) {
395            return;
396        }
397        if (mActivity.isSecureCamera()) return;
398        // Check if the back camera exists
399        int backCameraId = CameraHolder.instance().getBackCameraId();
400        if (backCameraId == -1) {
401            // If there is no back camera, do not show the prompt.
402            return;
403        }
404        mUI.showLocationDialog();
405    }
406
407    @Override
408    public void enableRecordingLocation(boolean enable) {
409        setLocationPreference(enable ? RecordLocationPreference.VALUE_ON
410                : RecordLocationPreference.VALUE_OFF);
411    }
412
413    @Override
414    public void onPreviewUIReady() {
415        startPreview();
416    }
417
418    @Override
419    public void onPreviewUIDestroyed() {
420        if (mCameraDevice == null) {
421            return;
422        }
423        mCameraDevice.setPreviewTexture(null);
424        stopPreview();
425    }
426
427    private void setLocationPreference(String value) {
428        mPreferences.edit()
429                .putString(CameraSettings.KEY_RECORD_LOCATION, value)
430                .apply();
431        // TODO: Fix this to use the actual onSharedPreferencesChanged listener
432        // instead of invoking manually
433        onSharedPreferenceChanged();
434    }
435
436    private void onCameraOpened() {
437        View root = mUI.getRootView();
438        // These depend on camera parameters.
439
440        int width = root.getWidth();
441        int height = root.getHeight();
442        mFocusManager.setPreviewSize(width, height);
443        openCameraCommon();
444    }
445
446    private void switchCamera() {
447        if (mPaused) return;
448
449        Log.v(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
450        mCameraId = mPendingSwitchCameraId;
451        mPendingSwitchCameraId = -1;
452        setCameraId(mCameraId);
453
454        // from onPause
455        closeCamera();
456        mUI.collapseCameraControls();
457        mUI.clearFaces();
458        if (mFocusManager != null) mFocusManager.removeMessages();
459
460        // Restart the camera and initialize the UI. From onCreate.
461        mPreferences.setLocalId(mActivity, mCameraId);
462        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
463        mCameraDevice = CameraUtil.openCamera(
464                mActivity, mCameraId, mHandler,
465                mActivity.getCameraOpenErrorCallback());
466        if (mCameraDevice == null) {
467            Log.e(TAG, "Failed to open camera:" + mCameraId + ", aborting.");
468            return;
469        }
470        mParameters = mCameraDevice.getParameters();
471        initializeCapabilities();
472        CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
473        mMirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
474        mFocusManager.setMirror(mMirror);
475        mFocusManager.setParameters(mInitialParams);
476        setupPreview();
477
478        // reset zoom value index
479        mZoomValue = 0;
480        openCameraCommon();
481
482        // Start switch camera animation. Post a message because
483        // onFrameAvailable from the old camera may already exist.
484        mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
485    }
486
487    protected void setCameraId(int cameraId) {
488        ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
489        pref.setValue("" + cameraId);
490    }
491
492    // either open a new camera or switch cameras
493    private void openCameraCommon() {
494        loadCameraPreferences();
495
496        mUI.onCameraOpened(mPreferenceGroup, mPreferences, mParameters, this);
497        if (mIsImageCaptureIntent) {
498            mUI.overrideSettings(CameraSettings.KEY_CAMERA_HDR_PLUS,
499                    mActivity.getString(R.string.setting_off_value));
500        }
501        updateSceneMode();
502        showTapToFocusToastIfNeeded();
503
504
505    }
506
507    @Override
508    public void onScreenSizeChanged(int width, int height) {
509        if (mFocusManager != null) mFocusManager.setPreviewSize(width, height);
510    }
511
512    private void resetExposureCompensation() {
513        String value = mPreferences.getString(CameraSettings.KEY_EXPOSURE,
514                CameraSettings.EXPOSURE_DEFAULT_VALUE);
515        if (!CameraSettings.EXPOSURE_DEFAULT_VALUE.equals(value)) {
516            Editor editor = mPreferences.edit();
517            editor.putString(CameraSettings.KEY_EXPOSURE, "0");
518            editor.apply();
519        }
520    }
521
522    private void keepMediaProviderInstance() {
523        // We want to keep a reference to MediaProvider in camera's lifecycle.
524        // TODO: Utilize mMediaProviderClient instance to replace
525        // ContentResolver calls.
526        if (mMediaProviderClient == null) {
527            mMediaProviderClient = mContentResolver
528                    .acquireContentProviderClient(MediaStore.AUTHORITY);
529        }
530    }
531
532    // Snapshots can only be taken after this is called. It should be called
533    // once only. We could have done these things in onCreate() but we want to
534    // make preview screen appear as soon as possible.
535    private void initializeFirstTime() {
536        if (mFirstTimeInitialized || mPaused) {
537            return;
538        }
539
540        // Initialize location service.
541        boolean recordLocation = RecordLocationPreference.get(
542                mPreferences, mContentResolver);
543        mLocationManager.recordLocation(recordLocation);
544
545        keepMediaProviderInstance();
546
547        mUI.initializeFirstTime();
548        MediaSaveService s = mActivity.getMediaSaveService();
549        // We set the listener only when both service and shutterbutton
550        // are initialized.
551        if (s != null) {
552            s.setListener(this);
553        }
554
555        mNamedImages = new NamedImages();
556
557        mFirstTimeInitialized = true;
558        addIdleHandler();
559
560        mActivity.updateStorageSpaceAndHint();
561    }
562
563    // If the activity is paused and resumed, this method will be called in
564    // onResume.
565    private void initializeSecondTime() {
566        // Start location update if needed.
567        boolean recordLocation = RecordLocationPreference.get(
568                mPreferences, mContentResolver);
569        mLocationManager.recordLocation(recordLocation);
570        MediaSaveService s = mActivity.getMediaSaveService();
571        if (s != null) {
572            s.setListener(this);
573        }
574        mNamedImages = new NamedImages();
575        mUI.initializeSecondTime(mParameters);
576        keepMediaProviderInstance();
577    }
578
579    private void showTapToFocusToastIfNeeded() {
580        // Show the tap to focus toast if this is the first start.
581        if (mFocusAreaSupported &&
582                mPreferences.getBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, true)) {
583            // Delay the toast for one second to wait for orientation.
584            mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_FOCUS_TOAST, 1000);
585        }
586    }
587
588    private void addIdleHandler() {
589        MessageQueue queue = Looper.myQueue();
590        queue.addIdleHandler(new MessageQueue.IdleHandler() {
591            @Override
592            public boolean queueIdle() {
593                Storage.ensureOSXCompatible();
594                return false;
595            }
596        });
597    }
598
599    @Override
600    public void startFaceDetection() {
601        if (mFaceDetectionStarted) return;
602        if (mParameters.getMaxNumDetectedFaces() > 0) {
603            mFaceDetectionStarted = true;
604            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
605            mUI.onStartFaceDetection(mDisplayOrientation,
606                    (info.facing == CameraInfo.CAMERA_FACING_FRONT));
607            mCameraDevice.setFaceDetectionCallback(mHandler, mUI);
608            mCameraDevice.startFaceDetection();
609        }
610    }
611
612    @Override
613    public void stopFaceDetection() {
614        if (!mFaceDetectionStarted) return;
615        if (mParameters.getMaxNumDetectedFaces() > 0) {
616            mFaceDetectionStarted = false;
617            mCameraDevice.setFaceDetectionCallback(null, null);
618            mCameraDevice.stopFaceDetection();
619            mUI.clearFaces();
620        }
621    }
622
623    private final class ShutterCallback
624            implements CameraShutterCallback {
625
626        private boolean mNeedsAnimation;
627
628        public ShutterCallback(boolean needsAnimation) {
629            mNeedsAnimation = needsAnimation;
630        }
631
632        @Override
633        public void onShutter(CameraProxy camera) {
634            mShutterCallbackTime = System.currentTimeMillis();
635            mShutterLag = mShutterCallbackTime - mCaptureStartTime;
636            Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
637            if (mNeedsAnimation) {
638                mActivity.runOnUiThread(new Runnable() {
639                    @Override
640                    public void run() {
641                        animateAfterShutter();
642                    }
643                });
644            }
645        }
646    }
647
648    private final class PostViewPictureCallback
649            implements CameraPictureCallback {
650        @Override
651        public void onPictureTaken(byte [] data, CameraProxy camera) {
652            mPostViewPictureCallbackTime = System.currentTimeMillis();
653            Log.v(TAG, "mShutterToPostViewCallbackTime = "
654                    + (mPostViewPictureCallbackTime - mShutterCallbackTime)
655                    + "ms");
656        }
657    }
658
659    private final class RawPictureCallback
660            implements CameraPictureCallback {
661        @Override
662        public void onPictureTaken(byte [] rawData, CameraProxy camera) {
663            mRawPictureCallbackTime = System.currentTimeMillis();
664            Log.v(TAG, "mShutterToRawCallbackTime = "
665                    + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
666        }
667    }
668
669    private final class JpegPictureCallback
670            implements CameraPictureCallback {
671        Location mLocation;
672
673        public JpegPictureCallback(Location loc) {
674            mLocation = loc;
675        }
676
677        @Override
678        public void onPictureTaken(final byte [] jpegData, CameraProxy camera) {
679            mUI.enableShutter(true);
680            if (mPaused) {
681                return;
682            }
683            if (mIsImageCaptureIntent) {
684                stopPreview();
685            }
686            if (mSceneMode == CameraUtil.SCENE_MODE_HDR) {
687                mUI.showSwitcher();
688                mUI.setSwipingEnabled(true);
689            }
690
691            mJpegPictureCallbackTime = System.currentTimeMillis();
692            // If postview callback has arrived, the captured image is displayed
693            // in postview callback. If not, the captured image is displayed in
694            // raw picture callback.
695            if (mPostViewPictureCallbackTime != 0) {
696                mShutterToPictureDisplayedTime =
697                        mPostViewPictureCallbackTime - mShutterCallbackTime;
698                mPictureDisplayedToJpegCallbackTime =
699                        mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
700            } else {
701                mShutterToPictureDisplayedTime =
702                        mRawPictureCallbackTime - mShutterCallbackTime;
703                mPictureDisplayedToJpegCallbackTime =
704                        mJpegPictureCallbackTime - mRawPictureCallbackTime;
705            }
706            Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
707                    + mPictureDisplayedToJpegCallbackTime + "ms");
708
709            mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden.
710            if (!mIsImageCaptureIntent) {
711                setupPreview();
712            }
713
714            ExifInterface exif = Exif.getExif(jpegData);
715            int orientation = Exif.getOrientation(exif);
716
717            if (!mIsImageCaptureIntent) {
718                // Calculate the width and the height of the jpeg.
719                Size s = mParameters.getPictureSize();
720                int width, height;
721                if ((mJpegRotation + orientation) % 180 == 0) {
722                    width = s.width;
723                    height = s.height;
724                } else {
725                    width = s.height;
726                    height = s.width;
727                }
728                NamedEntity name = mNamedImages.getNextNameEntity();
729                String title = (name == null) ? null : name.title;
730                long date = (name == null) ? -1 : name.date;
731
732                // Handle debug mode outputs
733                if (mDebugUri != null) {
734                    // If using a debug uri, save jpeg there.
735                    saveToDebugUri(jpegData);
736
737                    // Adjust the title of the debug image shown in mediastore.
738                    if (title != null) {
739                        title = DEBUG_IMAGE_PREFIX + title;
740                    }
741                }
742
743                if (title == null) {
744                    Log.e(TAG, "Unbalanced name/data pair");
745                } else {
746                    if (date == -1) date = mCaptureStartTime;
747                    if (mHeading >= 0) {
748                        // heading direction has been updated by the sensor.
749                        ExifTag directionRefTag = exif.buildTag(
750                                ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
751                                ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION);
752                        ExifTag directionTag = exif.buildTag(
753                                ExifInterface.TAG_GPS_IMG_DIRECTION,
754                                new Rational(mHeading, 1));
755                        exif.setTag(directionRefTag);
756                        exif.setTag(directionTag);
757                    }
758                    mActivity.getMediaSaveService().addImage(
759                            jpegData, title, date, mLocation, width, height,
760                            orientation, exif, mOnMediaSavedListener, mContentResolver);
761                }
762                // Animate capture with real jpeg data instead of a preview frame.
763                mUI.animateCapture(jpegData, orientation, mMirror);
764            } else {
765                mJpegImageData = jpegData;
766                if (!mQuickCapture) {
767                    mUI.showCapturedImageForReview(jpegData, orientation, mMirror);
768                } else {
769                    onCaptureDone();
770                }
771            }
772
773            // Check this in advance of each shot so we don't add to shutter
774            // latency. It's true that someone else could write to the SD card in
775            // the mean time and fill it, but that could have happened between the
776            // shutter press and saving the JPEG too.
777            mActivity.updateStorageSpaceAndHint();
778
779            long now = System.currentTimeMillis();
780            mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
781            Log.v(TAG, "mJpegCallbackFinishTime = "
782                    + mJpegCallbackFinishTime + "ms");
783            mJpegPictureCallbackTime = 0;
784        }
785    }
786
787    private final class AutoFocusCallback implements CameraAFCallback {
788        @Override
789        public void onAutoFocus(
790                boolean focused, CameraProxy camera) {
791            if (mPaused) return;
792
793            mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
794            Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
795            setCameraState(IDLE);
796            mFocusManager.onAutoFocus(focused, mUI.isShutterPressed());
797        }
798    }
799
800    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
801    private final class AutoFocusMoveCallback
802            implements CameraAFMoveCallback {
803        @Override
804        public void onAutoFocusMoving(
805                boolean moving, CameraProxy camera) {
806            mFocusManager.onAutoFocusMoving(moving);
807        }
808    }
809
810    /**
811     * This class is just a thread-safe queue for name,date holder objects.
812     */
813    public static class NamedImages {
814        private Vector<NamedEntity> mQueue;
815
816        public NamedImages() {
817            mQueue = new Vector<NamedEntity>();
818        }
819
820        public void nameNewImage(long date) {
821            NamedEntity r = new NamedEntity();
822            r.title = CameraUtil.createJpegName(date);
823            r.date = date;
824            mQueue.add(r);
825        }
826
827        public NamedEntity getNextNameEntity() {
828            synchronized(mQueue) {
829                if (!mQueue.isEmpty()) {
830                    return mQueue.remove(0);
831                }
832            }
833            return null;
834        }
835
836        public static class NamedEntity {
837            public String title;
838            public long date;
839        }
840    }
841
842    private void setCameraState(int state) {
843        mCameraState = state;
844        switch (state) {
845            case PhotoController.PREVIEW_STOPPED:
846            case PhotoController.SNAPSHOT_IN_PROGRESS:
847            case PhotoController.SWITCHING_CAMERA:
848                mUI.enableGestures(false);
849                break;
850            case PhotoController.IDLE:
851                mUI.enableGestures(true);
852                break;
853        }
854    }
855
856    private void animateAfterShutter() {
857        // Only animate when in full screen capture mode
858        // i.e. If monkey/a user swipes to the gallery during picture taking,
859        // don't show animation
860        if (!mIsImageCaptureIntent) {
861            mUI.animateFlash();
862        }
863    }
864
865    @Override
866    public boolean capture() {
867        // If we are already in the middle of taking a snapshot or the image save request
868        // is full then ignore.
869        if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
870                || mCameraState == SWITCHING_CAMERA
871                || mActivity.getMediaSaveService().isQueueFull()) {
872            return false;
873        }
874        mCaptureStartTime = System.currentTimeMillis();
875        mPostViewPictureCallbackTime = 0;
876        mJpegImageData = null;
877
878        final boolean animateBefore = (mSceneMode == CameraUtil.SCENE_MODE_HDR);
879
880        if (animateBefore) {
881            animateAfterShutter();
882        }
883
884        // Set rotation and gps data.
885        int orientation;
886        // We need to be consistent with the framework orientation (i.e. the
887        // orientation of the UI.) when the auto-rotate screen setting is on.
888        if (mActivity.isAutoRotateScreen()) {
889            orientation = (360 - mDisplayRotation) % 360;
890        } else {
891            orientation = mOrientation;
892        }
893        mJpegRotation = CameraUtil.getJpegRotation(mCameraId, orientation);
894        mParameters.setRotation(mJpegRotation);
895        Location loc = mLocationManager.getCurrentLocation();
896        CameraUtil.setGpsParameters(mParameters, loc);
897        mCameraDevice.setParameters(mParameters);
898
899        // We don't want user to press the button again while taking a
900        // multi-second HDR photo.
901        mUI.enableShutter(false);
902        mCameraDevice.takePicture(mHandler,
903                new ShutterCallback(!animateBefore),
904                mRawPictureCallback, mPostViewPictureCallback,
905                new JpegPictureCallback(loc));
906
907        mNamedImages.nameNewImage(mCaptureStartTime);
908
909        mFaceDetectionStarted = false;
910        setCameraState(SNAPSHOT_IN_PROGRESS);
911        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
912                UsageStatistics.ACTION_CAPTURE_DONE, "Photo");
913        return true;
914    }
915
916    @Override
917    public void setFocusParameters() {
918        setCameraParameters(UPDATE_PARAM_PREFERENCE);
919    }
920
921    private int getPreferredCameraId(ComboPreferences preferences) {
922        int intentCameraId = CameraUtil.getCameraFacingIntentExtras(mActivity);
923        if (intentCameraId != -1) {
924            // Testing purpose. Launch a specific camera through the intent
925            // extras.
926            return intentCameraId;
927        } else {
928            return CameraSettings.readPreferredCameraId(preferences);
929        }
930    }
931
932    private void updateSceneMode() {
933        // If scene mode is set, we cannot set flash mode, white balance, and
934        // focus mode, instead, we read it from driver
935        if (!Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
936            overrideCameraSettings(mParameters.getFlashMode(),
937                    mParameters.getWhiteBalance(), mParameters.getFocusMode());
938        } else {
939            overrideCameraSettings(null, null, null);
940        }
941    }
942
943    private void overrideCameraSettings(final String flashMode,
944                                        final String whiteBalance, final String focusMode) {
945        mUI.overrideSettings(
946                CameraSettings.KEY_FLASH_MODE, flashMode,
947                CameraSettings.KEY_WHITE_BALANCE, whiteBalance,
948                CameraSettings.KEY_FOCUS_MODE, focusMode);
949    }
950
951    private void loadCameraPreferences() {
952        CameraSettings settings = new CameraSettings(mActivity, mInitialParams,
953                mCameraId, CameraHolder.instance().getCameraInfo());
954        mPreferenceGroup = settings.getPreferenceGroup(R.xml.camera_preferences);
955    }
956
957    @Override
958    public void onOrientationChanged(int orientation) {
959        // We keep the last known orientation. So if the user first orient
960        // the camera then point the camera to floor or sky, we still have
961        // the correct orientation.
962        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
963        mOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
964
965        // Show the toast after getting the first orientation changed.
966        if (mHandler.hasMessages(SHOW_TAP_TO_FOCUS_TOAST)) {
967            mHandler.removeMessages(SHOW_TAP_TO_FOCUS_TOAST);
968            showTapToFocusToast();
969        }
970    }
971
972    @Override
973    public void onStop() {
974        if (mMediaProviderClient != null) {
975            mMediaProviderClient.release();
976            mMediaProviderClient = null;
977        }
978    }
979
980    @Override
981    public void onCaptureCancelled() {
982        mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent());
983        mActivity.finish();
984    }
985
986    @Override
987    public void onCaptureRetake() {
988        if (mPaused)
989            return;
990        mUI.hidePostCaptureAlert();
991        setupPreview();
992    }
993
994    @Override
995    public void onCaptureDone() {
996        if (mPaused) {
997            return;
998        }
999
1000        byte[] data = mJpegImageData;
1001
1002        if (mCropValue == null) {
1003            // First handle the no crop case -- just return the value.  If the
1004            // caller specifies a "save uri" then write the data to its
1005            // stream. Otherwise, pass back a scaled down version of the bitmap
1006            // directly in the extras.
1007            if (mSaveUri != null) {
1008                OutputStream outputStream = null;
1009                try {
1010                    outputStream = mContentResolver.openOutputStream(mSaveUri);
1011                    outputStream.write(data);
1012                    outputStream.close();
1013
1014                    mActivity.setResultEx(Activity.RESULT_OK);
1015                    mActivity.finish();
1016                } catch (IOException ex) {
1017                    // ignore exception
1018                } finally {
1019                    CameraUtil.closeSilently(outputStream);
1020                }
1021            } else {
1022                ExifInterface exif = Exif.getExif(data);
1023                int orientation = Exif.getOrientation(exif);
1024                Bitmap bitmap = CameraUtil.makeBitmap(data, 50 * 1024);
1025                bitmap = CameraUtil.rotate(bitmap, orientation);
1026                mActivity.setResultEx(Activity.RESULT_OK,
1027                        new Intent("inline-data").putExtra("data", bitmap));
1028                mActivity.finish();
1029            }
1030        } else {
1031            // Save the image to a temp file and invoke the cropper
1032            Uri tempUri = null;
1033            FileOutputStream tempStream = null;
1034            try {
1035                File path = mActivity.getFileStreamPath(sTempCropFilename);
1036                path.delete();
1037                tempStream = mActivity.openFileOutput(sTempCropFilename, 0);
1038                tempStream.write(data);
1039                tempStream.close();
1040                tempUri = Uri.fromFile(path);
1041            } catch (FileNotFoundException ex) {
1042                mActivity.setResultEx(Activity.RESULT_CANCELED);
1043                mActivity.finish();
1044                return;
1045            } catch (IOException ex) {
1046                mActivity.setResultEx(Activity.RESULT_CANCELED);
1047                mActivity.finish();
1048                return;
1049            } finally {
1050                CameraUtil.closeSilently(tempStream);
1051            }
1052
1053            Bundle newExtras = new Bundle();
1054            if (mCropValue.equals("circle")) {
1055                newExtras.putString("circleCrop", "true");
1056            }
1057            if (mSaveUri != null) {
1058                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
1059            } else {
1060                newExtras.putBoolean(CameraUtil.KEY_RETURN_DATA, true);
1061            }
1062            if (mActivity.isSecureCamera()) {
1063                newExtras.putBoolean(CameraUtil.KEY_SHOW_WHEN_LOCKED, true);
1064            }
1065
1066            // TODO: Share this constant.
1067            final String CROP_ACTION = "com.android.camera.action.CROP";
1068            Intent cropIntent = new Intent(CROP_ACTION);
1069
1070            cropIntent.setData(tempUri);
1071            cropIntent.putExtras(newExtras);
1072
1073            mActivity.startActivityForResult(cropIntent, REQUEST_CROP);
1074        }
1075    }
1076
1077    @Override
1078    public void onShutterButtonFocus(boolean pressed) {
1079        if (mPaused || mUI.collapseCameraControls()
1080                || (mCameraState == SNAPSHOT_IN_PROGRESS)
1081                || (mCameraState == PREVIEW_STOPPED)) return;
1082
1083        // Do not do focus if there is not enough storage.
1084        if (pressed && !canTakePicture()) return;
1085
1086        if (pressed) {
1087            mFocusManager.onShutterDown();
1088        } else {
1089            // for countdown mode, we need to postpone the shutter release
1090            // i.e. lock the focus during countdown.
1091            if (!mUI.isCountingDown()) {
1092                mFocusManager.onShutterUp();
1093            }
1094        }
1095    }
1096
1097    @Override
1098    public void onShutterButtonClick() {
1099        if (mPaused || mUI.collapseCameraControls()
1100                || (mCameraState == SWITCHING_CAMERA)
1101                || (mCameraState == PREVIEW_STOPPED)) return;
1102
1103        // Do not take the picture if there is not enough storage.
1104        if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1105            Log.i(TAG, "Not enough space or storage not ready. remaining="
1106                    + mActivity.getStorageSpaceBytes());
1107            return;
1108        }
1109        Log.v(TAG, "onShutterButtonClick: mCameraState=" + mCameraState);
1110
1111        if (mSceneMode == CameraUtil.SCENE_MODE_HDR) {
1112            mUI.hideSwitcher();
1113            mUI.setSwipingEnabled(false);
1114        }
1115        // If the user wants to do a snapshot while the previous one is still
1116        // in progress, remember the fact and do it after we finish the previous
1117        // one and re-start the preview. Snapshot in progress also includes the
1118        // state that autofocus is focusing and a picture will be taken when
1119        // focus callback arrives.
1120        if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)
1121                && !mIsImageCaptureIntent) {
1122            mSnapshotOnIdle = true;
1123            return;
1124        }
1125
1126        String timer = mPreferences.getString(
1127                CameraSettings.KEY_TIMER,
1128                mActivity.getString(R.string.pref_camera_timer_default));
1129        boolean playSound = mPreferences.getString(CameraSettings.KEY_TIMER_SOUND_EFFECTS,
1130                mActivity.getString(R.string.pref_camera_timer_sound_default))
1131                .equals(mActivity.getString(R.string.setting_on_value));
1132
1133        int seconds = Integer.parseInt(timer);
1134        // When shutter button is pressed, check whether the previous countdown is
1135        // finished. If not, cancel the previous countdown and start a new one.
1136        if (mUI.isCountingDown()) {
1137            mUI.cancelCountDown();
1138        }
1139        if (seconds > 0) {
1140            mUI.startCountDown(seconds, playSound);
1141        } else {
1142            mSnapshotOnIdle = false;
1143            mFocusManager.doSnap();
1144        }
1145    }
1146
1147    @Override
1148    public void installIntentFilter() {
1149        // Do nothing.
1150    }
1151
1152    @Override
1153    public boolean updateStorageHintOnResume() {
1154        return mFirstTimeInitialized;
1155    }
1156
1157    @Override
1158    public void onResumeBeforeSuper() {
1159        mPaused = false;
1160    }
1161
1162    /**
1163     * Opens the camera device.
1164     *
1165     * @return Whether the camera was opened successfully.
1166     */
1167    private boolean prepareCamera() {
1168        // We need to check whether the activity is paused before long
1169        // operations to ensure that onPause() can be done ASAP.
1170        mCameraDevice = CameraUtil.openCamera(
1171                mActivity, mCameraId, mHandler,
1172                mActivity.getCameraOpenErrorCallback());
1173        if (mCameraDevice == null) {
1174            Log.e(TAG, "Failed to open camera:" + mCameraId);
1175            return false;
1176        }
1177        mParameters = mCameraDevice.getParameters();
1178
1179        initializeCapabilities();
1180        if (mFocusManager == null) initializeFocusManager();
1181        setCameraParameters(UPDATE_PARAM_ALL);
1182        mHandler.sendEmptyMessage(CAMERA_OPEN_DONE);
1183        mCameraPreviewParamsReady = true;
1184        startPreview();
1185        mOnResumeTime = SystemClock.uptimeMillis();
1186        checkDisplayRotation();
1187        return true;
1188    }
1189
1190
1191    @Override
1192    public void onResumeAfterSuper() {
1193        if (mOpenCameraFail || mCameraDisabled) return;
1194
1195        mJpegPictureCallbackTime = 0;
1196        mZoomValue = 0;
1197        resetExposureCompensation();
1198        if (!prepareCamera()) {
1199            // Camera failure.
1200            return;
1201        }
1202
1203        // If first time initialization is not finished, put it in the
1204        // message queue.
1205        if (!mFirstTimeInitialized) {
1206            mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1207        } else {
1208            initializeSecondTime();
1209        }
1210        mUI.initDisplayChangeListener();
1211        keepScreenOnAwhile();
1212
1213        UsageStatistics.onContentViewChanged(
1214                UsageStatistics.COMPONENT_CAMERA, "PhotoModule");
1215
1216        Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
1217        if (gsensor != null) {
1218            mSensorManager.registerListener(this, gsensor, SensorManager.SENSOR_DELAY_NORMAL);
1219        }
1220
1221        Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
1222        if (msensor != null) {
1223            mSensorManager.registerListener(this, msensor, SensorManager.SENSOR_DELAY_NORMAL);
1224        }
1225    }
1226
1227    @Override
1228    public void onPauseBeforeSuper() {
1229        mPaused = true;
1230        Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
1231        if (gsensor != null) {
1232            mSensorManager.unregisterListener(this, gsensor);
1233        }
1234
1235        Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
1236        if (msensor != null) {
1237            mSensorManager.unregisterListener(this, msensor);
1238        }
1239    }
1240
1241    @Override
1242    public void onPauseAfterSuper() {
1243        // When camera is started from secure lock screen for the first time
1244        // after screen on, the activity gets onCreate->onResume->onPause->onResume.
1245        // To reduce the latency, keep the camera for a short time so it does
1246        // not need to be opened again.
1247        if (mCameraDevice != null && mActivity.isSecureCamera()
1248                && CameraActivity.isFirstStartAfterScreenOn()) {
1249            CameraActivity.resetFirstStartAfterScreenOn();
1250            CameraHolder.instance().keep(KEEP_CAMERA_TIMEOUT);
1251        }
1252        // Reset the focus first. Camera CTS does not guarantee that
1253        // cancelAutoFocus is allowed after preview stops.
1254        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1255            mCameraDevice.cancelAutoFocus();
1256        }
1257        stopPreview();
1258
1259        mNamedImages = null;
1260
1261        if (mLocationManager != null) mLocationManager.recordLocation(false);
1262
1263        // If we are in an image capture intent and has taken
1264        // a picture, we just clear it in onPause.
1265        mJpegImageData = null;
1266
1267        // Remove the messages and runnables in the queue.
1268        mHandler.removeCallbacksAndMessages(null);
1269
1270        closeCamera();
1271
1272        resetScreenOn();
1273        mUI.onPause();
1274
1275        mPendingSwitchCameraId = -1;
1276        if (mFocusManager != null) mFocusManager.removeMessages();
1277        MediaSaveService s = mActivity.getMediaSaveService();
1278        if (s != null) {
1279            s.setListener(null);
1280        }
1281        mUI.removeDisplayChangeListener();
1282    }
1283
1284    /**
1285     * The focus manager is the first UI related element to get initialized,
1286     * and it requires the RenderOverlay, so initialize it here
1287     */
1288    private void initializeFocusManager() {
1289        // Create FocusManager object. startPreview needs it.
1290        // if mFocusManager not null, reuse it
1291        // otherwise create a new instance
1292        if (mFocusManager != null) {
1293            mFocusManager.removeMessages();
1294        } else {
1295            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1296            mMirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
1297            String[] defaultFocusModes = mActivity.getResources().getStringArray(
1298                    R.array.pref_camera_focusmode_default_array);
1299            mFocusManager = new FocusOverlayManager(mPreferences, defaultFocusModes,
1300                    mInitialParams, this, mMirror,
1301                    mActivity.getMainLooper(), mUI);
1302        }
1303    }
1304
1305    @Override
1306    public void onConfigurationChanged(Configuration newConfig) {
1307        Log.v(TAG, "onConfigurationChanged");
1308        setDisplayOrientation();
1309    }
1310
1311    @Override
1312    public void updateCameraOrientation() {
1313        if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
1314            setDisplayOrientation();
1315        }
1316    }
1317
1318    @Override
1319    public void onActivityResult(
1320            int requestCode, int resultCode, Intent data) {
1321        switch (requestCode) {
1322            case REQUEST_CROP: {
1323                Intent intent = new Intent();
1324                if (data != null) {
1325                    Bundle extras = data.getExtras();
1326                    if (extras != null) {
1327                        intent.putExtras(extras);
1328                    }
1329                }
1330                mActivity.setResultEx(resultCode, intent);
1331                mActivity.finish();
1332
1333                File path = mActivity.getFileStreamPath(sTempCropFilename);
1334                path.delete();
1335
1336                break;
1337            }
1338        }
1339    }
1340
1341    private boolean canTakePicture() {
1342        return isCameraIdle() && (mActivity.getStorageSpaceBytes() > Storage.LOW_STORAGE_THRESHOLD_BYTES);
1343    }
1344
1345    @Override
1346    public void autoFocus() {
1347        mFocusStartTime = System.currentTimeMillis();
1348        mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
1349        setCameraState(FOCUSING);
1350    }
1351
1352    @Override
1353    public void cancelAutoFocus() {
1354        mCameraDevice.cancelAutoFocus();
1355        setCameraState(IDLE);
1356        setCameraParameters(UPDATE_PARAM_PREFERENCE);
1357    }
1358
1359    // Preview area is touched. Handle touch focus.
1360    @Override
1361    public void onSingleTapUp(View view, int x, int y) {
1362        if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
1363                || mCameraState == SNAPSHOT_IN_PROGRESS
1364                || mCameraState == SWITCHING_CAMERA
1365                || mCameraState == PREVIEW_STOPPED) {
1366            return;
1367        }
1368
1369        // Check if metering area or focus area is supported.
1370        if (!mFocusAreaSupported && !mMeteringAreaSupported) return;
1371        mFocusManager.onSingleTapUp(x, y);
1372    }
1373
1374    @Override
1375    public boolean onBackPressed() {
1376        return mUI.onBackPressed();
1377    }
1378
1379    @Override
1380    public boolean onKeyDown(int keyCode, KeyEvent event) {
1381        switch (keyCode) {
1382            case KeyEvent.KEYCODE_VOLUME_UP:
1383            case KeyEvent.KEYCODE_VOLUME_DOWN:
1384            case KeyEvent.KEYCODE_FOCUS:
1385                if (/*TODO: mActivity.isInCameraApp() &&*/ mFirstTimeInitialized) {
1386                    if (event.getRepeatCount() == 0) {
1387                        onShutterButtonFocus(true);
1388                    }
1389                    return true;
1390                }
1391                return false;
1392            case KeyEvent.KEYCODE_CAMERA:
1393                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1394                    onShutterButtonClick();
1395                }
1396                return true;
1397            case KeyEvent.KEYCODE_DPAD_CENTER:
1398                // If we get a dpad center event without any focused view, move
1399                // the focus to the shutter button and press it.
1400                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1401                    // Start auto-focus immediately to reduce shutter lag. After
1402                    // the shutter button gets the focus, onShutterButtonFocus()
1403                    // will be called again but it is fine.
1404                    onShutterButtonFocus(true);
1405                    mUI.pressShutterButton();
1406                }
1407                return true;
1408        }
1409        return false;
1410    }
1411
1412    @Override
1413    public boolean onKeyUp(int keyCode, KeyEvent event) {
1414        switch (keyCode) {
1415            case KeyEvent.KEYCODE_VOLUME_UP:
1416            case KeyEvent.KEYCODE_VOLUME_DOWN:
1417                if (/*mActivity.isInCameraApp() && */ mFirstTimeInitialized) {
1418                    onShutterButtonClick();
1419                    return true;
1420                }
1421                return false;
1422            case KeyEvent.KEYCODE_FOCUS:
1423                if (mFirstTimeInitialized) {
1424                    onShutterButtonFocus(false);
1425                }
1426                return true;
1427        }
1428        return false;
1429    }
1430
1431    private void closeCamera() {
1432        if (mCameraDevice != null) {
1433            mCameraDevice.setZoomChangeListener(null);
1434            mCameraDevice.setFaceDetectionCallback(null, null);
1435            mCameraDevice.setErrorCallback(null);
1436            CameraHolder.instance().release();
1437            mFaceDetectionStarted = false;
1438            mCameraDevice = null;
1439            setCameraState(PREVIEW_STOPPED);
1440            mFocusManager.onCameraReleased();
1441        }
1442    }
1443
1444    private void setDisplayOrientation() {
1445        mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
1446        mDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId);
1447        mCameraDisplayOrientation = mDisplayOrientation;
1448        mUI.setDisplayOrientation(mDisplayOrientation);
1449        if (mFocusManager != null) {
1450            mFocusManager.setDisplayOrientation(mDisplayOrientation);
1451        }
1452        // Change the camera display orientation
1453        if (mCameraDevice != null) {
1454            mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
1455        }
1456    }
1457
1458    /** Only called by UI thread. */
1459    private void setupPreview() {
1460        mFocusManager.resetTouchFocus();
1461        startPreview();
1462    }
1463
1464    // This can only be called by UI Thread.
1465    private void startPreview() {
1466        if (mPaused) {
1467            return;
1468        }
1469        SurfaceTexture st = mUI.getSurfaceTexture();
1470        if (st == null) {
1471            Log.w(TAG, "startPreview: surfaceTexture is not ready.");
1472            return;
1473        }
1474        if (!mCameraPreviewParamsReady) {
1475            Log.w(TAG, "startPreview: parameters for preview is not ready.");
1476            return;
1477        }
1478        mCameraDevice.setErrorCallback(mErrorCallback);
1479
1480        // ICS camera frameworks has a bug. Face detection state is not cleared
1481        // after taking a picture. Stop the preview to work around it. The bug
1482        // was fixed in JB.
1483        if (mCameraState != PREVIEW_STOPPED) stopPreview();
1484
1485        setDisplayOrientation();
1486
1487        if (!mSnapshotOnIdle) {
1488            // If the focus mode is continuous autofocus, call cancelAutoFocus to
1489            // resume it because it may have been paused by autoFocus call.
1490            if (CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusManager.getFocusMode())) {
1491                mCameraDevice.cancelAutoFocus();
1492            }
1493            mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
1494        }
1495        setCameraParameters(UPDATE_PARAM_ALL);
1496        // Let UI set its expected aspect ratio
1497        mCameraDevice.setPreviewTexture(st);
1498
1499        Log.v(TAG, "startPreview");
1500        mCameraDevice.startPreview();
1501        mFocusManager.onPreviewStarted();
1502        onPreviewStarted();
1503
1504        if (mSnapshotOnIdle) {
1505            mHandler.post(mDoSnapRunnable);
1506        }
1507    }
1508
1509    @Override
1510    public void stopPreview() {
1511        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1512            Log.v(TAG, "stopPreview");
1513            mCameraDevice.stopPreview();
1514            mFaceDetectionStarted = false;
1515        }
1516        setCameraState(PREVIEW_STOPPED);
1517        if (mFocusManager != null) mFocusManager.onPreviewStopped();
1518    }
1519
1520    @SuppressWarnings("deprecation")
1521    private void updateCameraParametersInitialize() {
1522        // Reset preview frame rate to the maximum because it may be lowered by
1523        // video camera application.
1524        int[] fpsRange = CameraUtil.getPhotoPreviewFpsRange(mParameters);
1525        if (fpsRange != null && fpsRange.length > 0) {
1526            mParameters.setPreviewFpsRange(
1527                    fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX],
1528                    fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]);
1529        }
1530
1531        mParameters.set(CameraUtil.RECORDING_HINT, CameraUtil.FALSE);
1532
1533        // Disable video stabilization. Convenience methods not available in API
1534        // level <= 14
1535        String vstabSupported = mParameters.get("video-stabilization-supported");
1536        if ("true".equals(vstabSupported)) {
1537            mParameters.set("video-stabilization", "false");
1538        }
1539    }
1540
1541    private void updateCameraParametersZoom() {
1542        // Set zoom.
1543        if (mParameters.isZoomSupported()) {
1544            mParameters.setZoom(mZoomValue);
1545        }
1546    }
1547
1548    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1549    private void setAutoExposureLockIfSupported() {
1550        if (mAeLockSupported) {
1551            mParameters.setAutoExposureLock(mFocusManager.getAeAwbLock());
1552        }
1553    }
1554
1555    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1556    private void setAutoWhiteBalanceLockIfSupported() {
1557        if (mAwbLockSupported) {
1558            mParameters.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
1559        }
1560    }
1561
1562    private void setFocusAreasIfSupported() {
1563        if (mFocusAreaSupported) {
1564            mParameters.setFocusAreas(mFocusManager.getFocusAreas());
1565        }
1566    }
1567
1568    private void setMeteringAreasIfSupported() {
1569        if (mMeteringAreaSupported) {
1570            mParameters.setMeteringAreas(mFocusManager.getMeteringAreas());
1571        }
1572    }
1573
1574    private boolean updateCameraParametersPreference() {
1575        setAutoExposureLockIfSupported();
1576        setAutoWhiteBalanceLockIfSupported();
1577        setFocusAreasIfSupported();
1578        setMeteringAreasIfSupported();
1579
1580        // Set picture size.
1581        String pictureSize = mPreferences.getString(
1582                CameraSettings.KEY_PICTURE_SIZE, null);
1583        if (pictureSize == null) {
1584            CameraSettings.initialCameraPictureSize(mActivity, mParameters);
1585        } else {
1586            List<Size> supported = mParameters.getSupportedPictureSizes();
1587            CameraSettings.setCameraPictureSize(
1588                    pictureSize, supported, mParameters);
1589        }
1590        Size size = mParameters.getPictureSize();
1591
1592        // Set a preview size that is closest to the viewfinder height and has
1593        // the right aspect ratio.
1594        List<Size> sizes = mParameters.getSupportedPreviewSizes();
1595        Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes,
1596                (double) size.width / size.height);
1597        Size original = mParameters.getPreviewSize();
1598        if (!original.equals(optimalSize)) {
1599            mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
1600
1601            // Zoom related settings will be changed for different preview
1602            // sizes, so set and read the parameters to get latest values
1603            if (mHandler.getLooper() == Looper.myLooper()) {
1604                // On UI thread only, not when camera starts up
1605                setupPreview();
1606            } else {
1607                mCameraDevice.setParameters(mParameters);
1608            }
1609            mParameters = mCameraDevice.getParameters();
1610        }
1611        Log.v(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height);
1612
1613        // Since changing scene mode may change supported values, set scene mode
1614        // first. HDR is a scene mode. To promote it in UI, it is stored in a
1615        // separate preference.
1616        String onValue = mActivity.getString(R.string.setting_on_value);
1617        String hdr = mPreferences.getString(CameraSettings.KEY_CAMERA_HDR,
1618                mActivity.getString(R.string.pref_camera_hdr_default));
1619        String hdrPlus = mPreferences.getString(CameraSettings.KEY_CAMERA_HDR_PLUS,
1620                mActivity.getString(R.string.pref_camera_hdr_plus_default));
1621        boolean hdrOn = onValue.equals(hdr);
1622        boolean hdrPlusOn = onValue.equals(hdrPlus);
1623
1624        boolean doGcamModeSwitch = false;
1625        if (hdrPlusOn && GcamHelper.hasGcamCapture()) {
1626            // Kick off mode switch to gcam.
1627            doGcamModeSwitch = true;
1628        } else {
1629            if (hdrOn) {
1630                mSceneMode = CameraUtil.SCENE_MODE_HDR;
1631            } else {
1632                mSceneMode = mPreferences.getString(
1633                        CameraSettings.KEY_SCENE_MODE,
1634                        mActivity.getString(R.string.pref_camera_scenemode_default));
1635            }
1636        }
1637        if (CameraUtil.isSupported(mSceneMode, mParameters.getSupportedSceneModes())) {
1638            if (!mParameters.getSceneMode().equals(mSceneMode)) {
1639                mParameters.setSceneMode(mSceneMode);
1640
1641                // Setting scene mode will change the settings of flash mode,
1642                // white balance, and focus mode. Here we read back the
1643                // parameters, so we can know those settings.
1644                mCameraDevice.setParameters(mParameters);
1645                mParameters = mCameraDevice.getParameters();
1646            }
1647        } else {
1648            mSceneMode = mParameters.getSceneMode();
1649            if (mSceneMode == null) {
1650                mSceneMode = Parameters.SCENE_MODE_AUTO;
1651            }
1652        }
1653
1654        // Set JPEG quality.
1655        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1656                CameraProfile.QUALITY_HIGH);
1657        mParameters.setJpegQuality(jpegQuality);
1658
1659        // For the following settings, we need to check if the settings are
1660        // still supported by latest driver, if not, ignore the settings.
1661
1662        // Set exposure compensation
1663        int value = CameraSettings.readExposure(mPreferences);
1664        int max = mParameters.getMaxExposureCompensation();
1665        int min = mParameters.getMinExposureCompensation();
1666        if (value >= min && value <= max) {
1667            mParameters.setExposureCompensation(value);
1668        } else {
1669            Log.w(TAG, "invalid exposure range: " + value);
1670        }
1671
1672        if (Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
1673            // Set flash mode.
1674            String flashMode = mPreferences.getString(
1675                    CameraSettings.KEY_FLASH_MODE,
1676                    mActivity.getString(R.string.pref_camera_flashmode_default));
1677            List<String> supportedFlash = mParameters.getSupportedFlashModes();
1678            if (CameraUtil.isSupported(flashMode, supportedFlash)) {
1679                mParameters.setFlashMode(flashMode);
1680            } else {
1681                flashMode = mParameters.getFlashMode();
1682                if (flashMode == null) {
1683                    flashMode = mActivity.getString(
1684                            R.string.pref_camera_flashmode_no_flash);
1685                }
1686            }
1687
1688            // Set white balance parameter.
1689            String whiteBalance = mPreferences.getString(
1690                    CameraSettings.KEY_WHITE_BALANCE,
1691                    mActivity.getString(R.string.pref_camera_whitebalance_default));
1692            if (CameraUtil.isSupported(whiteBalance,
1693                    mParameters.getSupportedWhiteBalance())) {
1694                mParameters.setWhiteBalance(whiteBalance);
1695            } else {
1696                whiteBalance = mParameters.getWhiteBalance();
1697                if (whiteBalance == null) {
1698                    whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1699                }
1700            }
1701
1702            // Set focus mode.
1703            mFocusManager.overrideFocusMode(null);
1704            mParameters.setFocusMode(mFocusManager.getFocusMode());
1705        } else {
1706            mFocusManager.overrideFocusMode(mParameters.getFocusMode());
1707        }
1708
1709        if (mContinuousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
1710            updateAutoFocusMoveCallback();
1711        }
1712
1713        return doGcamModeSwitch;
1714    }
1715
1716    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1717    private void updateAutoFocusMoveCallback() {
1718        if (mParameters.getFocusMode().equals(CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE)) {
1719            mCameraDevice.setAutoFocusMoveCallback(mHandler,
1720                    (CameraAFMoveCallback) mAutoFocusMoveCallback);
1721        } else {
1722            mCameraDevice.setAutoFocusMoveCallback(null, null);
1723        }
1724    }
1725
1726    // We separate the parameters into several subsets, so we can update only
1727    // the subsets actually need updating. The PREFERENCE set needs extra
1728    // locking because the preference can be changed from GLThread as well.
1729    private void setCameraParameters(int updateSet) {
1730        boolean doModeSwitch = false;
1731
1732        if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
1733            updateCameraParametersInitialize();
1734        }
1735
1736        if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
1737            updateCameraParametersZoom();
1738        }
1739
1740        if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
1741            doModeSwitch = updateCameraParametersPreference();
1742        }
1743
1744        mCameraDevice.setParameters(mParameters);
1745
1746        // Switch to gcam module if HDR+ was selected
1747        if (doModeSwitch && !mIsImageCaptureIntent) {
1748            mHandler.sendEmptyMessage(SWITCH_TO_GCAM_MODULE);
1749        }
1750    }
1751
1752    // If the Camera is idle, update the parameters immediately, otherwise
1753    // accumulate them in mUpdateSet and update later.
1754    private void setCameraParametersWhenIdle(int additionalUpdateSet) {
1755        mUpdateSet |= additionalUpdateSet;
1756        if (mCameraDevice == null) {
1757            // We will update all the parameters when we open the device, so
1758            // we don't need to do anything now.
1759            mUpdateSet = 0;
1760            return;
1761        } else if (isCameraIdle()) {
1762            setCameraParameters(mUpdateSet);
1763            updateSceneMode();
1764            mUpdateSet = 0;
1765        } else {
1766            if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
1767                mHandler.sendEmptyMessageDelayed(
1768                        SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
1769            }
1770        }
1771    }
1772
1773    @Override
1774    public boolean isCameraIdle() {
1775        return (mCameraState == IDLE) ||
1776                (mCameraState == PREVIEW_STOPPED) ||
1777                ((mFocusManager != null) && mFocusManager.isFocusCompleted()
1778                        && (mCameraState != SWITCHING_CAMERA));
1779    }
1780
1781    @Override
1782    public boolean isImageCaptureIntent() {
1783        String action = mActivity.getIntent().getAction();
1784        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
1785                || CameraActivity.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
1786    }
1787
1788    private void setupCaptureParams() {
1789        Bundle myExtras = mActivity.getIntent().getExtras();
1790        if (myExtras != null) {
1791            mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1792            mCropValue = myExtras.getString("crop");
1793        }
1794    }
1795
1796    @Override
1797    public void onSharedPreferenceChanged() {
1798        // ignore the events after "onPause()"
1799        if (mPaused) return;
1800
1801        boolean recordLocation = RecordLocationPreference.get(
1802                mPreferences, mContentResolver);
1803        mLocationManager.recordLocation(recordLocation);
1804
1805        setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);
1806        mUI.updateOnScreenIndicators(mParameters, mPreferenceGroup, mPreferences);
1807    }
1808
1809    @Override
1810    public void onCameraPickerClicked(int cameraId) {
1811        if (mPaused || mPendingSwitchCameraId != -1) return;
1812
1813        mPendingSwitchCameraId = cameraId;
1814
1815        Log.v(TAG, "Start to switch camera. cameraId=" + cameraId);
1816        // We need to keep a preview frame for the animation before
1817        // releasing the camera. This will trigger onPreviewTextureCopied.
1818        //TODO: Need to animate the camera switch
1819        switchCamera();
1820    }
1821
1822    // Preview texture has been copied. Now camera can be released and the
1823    // animation can be started.
1824    @Override
1825    public void onPreviewTextureCopied() {
1826        mHandler.sendEmptyMessage(SWITCH_CAMERA);
1827    }
1828
1829    @Override
1830    public void onCaptureTextureCopied() {
1831    }
1832
1833    @Override
1834    public void onUserInteraction() {
1835        if (!mActivity.isFinishing()) keepScreenOnAwhile();
1836    }
1837
1838    private void resetScreenOn() {
1839        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1840        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1841    }
1842
1843    private void keepScreenOnAwhile() {
1844        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1845        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1846        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1847    }
1848
1849    @Override
1850    public void onOverriddenPreferencesClicked() {
1851        if (mPaused) return;
1852        mUI.showPreferencesToast();
1853    }
1854
1855    private void showTapToFocusToast() {
1856        // TODO: Use a toast?
1857        new RotateTextToast(mActivity, R.string.tap_to_focus, 0).show();
1858        // Clear the preference.
1859        Editor editor = mPreferences.edit();
1860        editor.putBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, false);
1861        editor.apply();
1862    }
1863
1864    private void initializeCapabilities() {
1865        mInitialParams = mCameraDevice.getParameters();
1866        mFocusAreaSupported = CameraUtil.isFocusAreaSupported(mInitialParams);
1867        mMeteringAreaSupported = CameraUtil.isMeteringAreaSupported(mInitialParams);
1868        mAeLockSupported = CameraUtil.isAutoExposureLockSupported(mInitialParams);
1869        mAwbLockSupported = CameraUtil.isAutoWhiteBalanceLockSupported(mInitialParams);
1870        mContinuousFocusSupported = mInitialParams.getSupportedFocusModes().contains(
1871                CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE);
1872    }
1873
1874    @Override
1875    public void onCountDownFinished() {
1876        mSnapshotOnIdle = false;
1877        mFocusManager.doSnap();
1878        mFocusManager.onShutterUp();
1879    }
1880
1881    @Override
1882    public void onShowSwitcherPopup() {
1883        mUI.onShowSwitcherPopup();
1884    }
1885
1886    @Override
1887    public int onZoomChanged(int index) {
1888        // Not useful to change zoom value when the activity is paused.
1889        if (mPaused) return index;
1890        mZoomValue = index;
1891        if (mParameters == null || mCameraDevice == null) return index;
1892        // Set zoom parameters asynchronously
1893        mParameters.setZoom(mZoomValue);
1894        mCameraDevice.setParameters(mParameters);
1895        Parameters p = mCameraDevice.getParameters();
1896        if (p != null) return p.getZoom();
1897        return index;
1898    }
1899
1900    @Override
1901    public int getCameraState() {
1902        return mCameraState;
1903    }
1904
1905    @Override
1906    public void onQueueStatus(boolean full) {
1907        mUI.enableShutter(!full);
1908    }
1909
1910    @Override
1911    public void onMediaSaveServiceConnected(MediaSaveService s) {
1912        // We set the listener only when both service and shutterbutton
1913        // are initialized.
1914        if (mFirstTimeInitialized) {
1915            s.setListener(this);
1916        }
1917    }
1918
1919    @Override
1920    public void onAccuracyChanged(Sensor sensor, int accuracy) {
1921    }
1922
1923    @Override
1924    public void onSensorChanged(SensorEvent event) {
1925        int type = event.sensor.getType();
1926        float[] data;
1927        if (type == Sensor.TYPE_ACCELEROMETER) {
1928            data = mGData;
1929        } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
1930            data = mMData;
1931        } else {
1932            // we should not be here.
1933            return;
1934        }
1935        for (int i = 0; i < 3 ; i++) {
1936            data[i] = event.values[i];
1937        }
1938        float[] orientation = new float[3];
1939        SensorManager.getRotationMatrix(mR, null, mGData, mMData);
1940        SensorManager.getOrientation(mR, orientation);
1941        mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
1942        if (mHeading < 0) {
1943            mHeading += 360;
1944        }
1945    }
1946
1947    @Override
1948    public void onPreviewFocusChanged(boolean previewFocused) {
1949        mUI.onPreviewFocusChanged(previewFocused);
1950    }
1951
1952    @Override
1953    public boolean arePreviewControlsVisible() {
1954        return mUI.arePreviewControlsVisible();
1955    }
1956
1957    // For debugging only.
1958    public void setDebugUri(Uri uri) {
1959        mDebugUri = uri;
1960    }
1961
1962    // For debugging only.
1963    private void saveToDebugUri(byte[] data) {
1964        if (mDebugUri != null) {
1965            OutputStream outputStream = null;
1966            try {
1967                outputStream = mContentResolver.openOutputStream(mDebugUri);
1968                outputStream.write(data);
1969                outputStream.close();
1970            } catch (IOException e) {
1971                Log.e(TAG, "Exception while writing debug jpeg file", e);
1972            } finally {
1973                CameraUtil.closeSilently(outputStream);
1974            }
1975        }
1976    }
1977
1978/* Below is no longer needed, except to get rid of compile error
1979 * TODO: Remove these
1980 */
1981
1982    // TODO: Delete this function after old camera code is removed
1983    @Override
1984    public void onRestorePreferencesClicked() {}
1985
1986}
1987