PhotoModule.java revision e546d48b4acfbf2441b685b2c7d6a4e14241f34b
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        Log.v(TAG, "On resume.");
1194        if (mOpenCameraFail || mCameraDisabled) return;
1195
1196        mJpegPictureCallbackTime = 0;
1197        mZoomValue = 0;
1198        resetExposureCompensation();
1199        if (!prepareCamera()) {
1200            // Camera failure.
1201            return;
1202        }
1203
1204        // If first time initialization is not finished, put it in the
1205        // message queue.
1206        if (!mFirstTimeInitialized) {
1207            mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1208        } else {
1209            initializeSecondTime();
1210        }
1211        mUI.initDisplayChangeListener();
1212        keepScreenOnAwhile();
1213
1214        UsageStatistics.onContentViewChanged(
1215                UsageStatistics.COMPONENT_CAMERA, "PhotoModule");
1216
1217        Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
1218        if (gsensor != null) {
1219            mSensorManager.registerListener(this, gsensor, SensorManager.SENSOR_DELAY_NORMAL);
1220        }
1221
1222        Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
1223        if (msensor != null) {
1224            mSensorManager.registerListener(this, msensor, SensorManager.SENSOR_DELAY_NORMAL);
1225        }
1226    }
1227
1228    @Override
1229    public void onPauseBeforeSuper() {
1230        mPaused = true;
1231        Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
1232        if (gsensor != null) {
1233            mSensorManager.unregisterListener(this, gsensor);
1234        }
1235
1236        Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
1237        if (msensor != null) {
1238            mSensorManager.unregisterListener(this, msensor);
1239        }
1240    }
1241
1242    @Override
1243    public void onPauseAfterSuper() {
1244        Log.v(TAG, "On pause.");
1245        mUI.showPreviewCover();
1246        // When camera is started from secure lock screen for the first time
1247        // after screen on, the activity gets onCreate->onResume->onPause->onResume.
1248        // To reduce the latency, keep the camera for a short time so it does
1249        // not need to be opened again.
1250        if (mCameraDevice != null && mActivity.isSecureCamera()
1251                && CameraActivity.isFirstStartAfterScreenOn()) {
1252            CameraActivity.resetFirstStartAfterScreenOn();
1253            CameraHolder.instance().keep(KEEP_CAMERA_TIMEOUT);
1254        }
1255        // Reset the focus first. Camera CTS does not guarantee that
1256        // cancelAutoFocus is allowed after preview stops.
1257        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1258            mCameraDevice.cancelAutoFocus();
1259        }
1260        stopPreview();
1261
1262        mNamedImages = null;
1263
1264        if (mLocationManager != null) mLocationManager.recordLocation(false);
1265
1266        // If we are in an image capture intent and has taken
1267        // a picture, we just clear it in onPause.
1268        mJpegImageData = null;
1269
1270        // Remove the messages and runnables in the queue.
1271        mHandler.removeCallbacksAndMessages(null);
1272
1273        closeCamera();
1274
1275        resetScreenOn();
1276        mUI.onPause();
1277
1278        mPendingSwitchCameraId = -1;
1279        if (mFocusManager != null) mFocusManager.removeMessages();
1280        MediaSaveService s = mActivity.getMediaSaveService();
1281        if (s != null) {
1282            s.setListener(null);
1283        }
1284        mUI.removeDisplayChangeListener();
1285    }
1286
1287    /**
1288     * The focus manager is the first UI related element to get initialized,
1289     * and it requires the RenderOverlay, so initialize it here
1290     */
1291    private void initializeFocusManager() {
1292        // Create FocusManager object. startPreview needs it.
1293        // if mFocusManager not null, reuse it
1294        // otherwise create a new instance
1295        if (mFocusManager != null) {
1296            mFocusManager.removeMessages();
1297        } else {
1298            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
1299            mMirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
1300            String[] defaultFocusModes = mActivity.getResources().getStringArray(
1301                    R.array.pref_camera_focusmode_default_array);
1302            mFocusManager = new FocusOverlayManager(mPreferences, defaultFocusModes,
1303                    mInitialParams, this, mMirror,
1304                    mActivity.getMainLooper(), mUI);
1305        }
1306    }
1307
1308    @Override
1309    public void onConfigurationChanged(Configuration newConfig) {
1310        Log.v(TAG, "onConfigurationChanged");
1311        setDisplayOrientation();
1312    }
1313
1314    @Override
1315    public void updateCameraOrientation() {
1316        if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
1317            setDisplayOrientation();
1318        }
1319    }
1320
1321    @Override
1322    public void onActivityResult(
1323            int requestCode, int resultCode, Intent data) {
1324        switch (requestCode) {
1325            case REQUEST_CROP: {
1326                Intent intent = new Intent();
1327                if (data != null) {
1328                    Bundle extras = data.getExtras();
1329                    if (extras != null) {
1330                        intent.putExtras(extras);
1331                    }
1332                }
1333                mActivity.setResultEx(resultCode, intent);
1334                mActivity.finish();
1335
1336                File path = mActivity.getFileStreamPath(sTempCropFilename);
1337                path.delete();
1338
1339                break;
1340            }
1341        }
1342    }
1343
1344    private boolean canTakePicture() {
1345        return isCameraIdle() && (mActivity.getStorageSpaceBytes() > Storage.LOW_STORAGE_THRESHOLD_BYTES);
1346    }
1347
1348    @Override
1349    public void autoFocus() {
1350        mFocusStartTime = System.currentTimeMillis();
1351        mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
1352        setCameraState(FOCUSING);
1353    }
1354
1355    @Override
1356    public void cancelAutoFocus() {
1357        mCameraDevice.cancelAutoFocus();
1358        setCameraState(IDLE);
1359        setCameraParameters(UPDATE_PARAM_PREFERENCE);
1360    }
1361
1362    // Preview area is touched. Handle touch focus.
1363    @Override
1364    public void onSingleTapUp(View view, int x, int y) {
1365        if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
1366                || mCameraState == SNAPSHOT_IN_PROGRESS
1367                || mCameraState == SWITCHING_CAMERA
1368                || mCameraState == PREVIEW_STOPPED) {
1369            return;
1370        }
1371
1372        // Check if metering area or focus area is supported.
1373        if (!mFocusAreaSupported && !mMeteringAreaSupported) return;
1374        mFocusManager.onSingleTapUp(x, y);
1375    }
1376
1377    @Override
1378    public boolean onBackPressed() {
1379        return mUI.onBackPressed();
1380    }
1381
1382    @Override
1383    public boolean onKeyDown(int keyCode, KeyEvent event) {
1384        switch (keyCode) {
1385            case KeyEvent.KEYCODE_VOLUME_UP:
1386            case KeyEvent.KEYCODE_VOLUME_DOWN:
1387            case KeyEvent.KEYCODE_FOCUS:
1388                if (/*TODO: mActivity.isInCameraApp() &&*/ mFirstTimeInitialized) {
1389                    if (event.getRepeatCount() == 0) {
1390                        onShutterButtonFocus(true);
1391                    }
1392                    return true;
1393                }
1394                return false;
1395            case KeyEvent.KEYCODE_CAMERA:
1396                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1397                    onShutterButtonClick();
1398                }
1399                return true;
1400            case KeyEvent.KEYCODE_DPAD_CENTER:
1401                // If we get a dpad center event without any focused view, move
1402                // the focus to the shutter button and press it.
1403                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1404                    // Start auto-focus immediately to reduce shutter lag. After
1405                    // the shutter button gets the focus, onShutterButtonFocus()
1406                    // will be called again but it is fine.
1407                    onShutterButtonFocus(true);
1408                    mUI.pressShutterButton();
1409                }
1410                return true;
1411        }
1412        return false;
1413    }
1414
1415    @Override
1416    public boolean onKeyUp(int keyCode, KeyEvent event) {
1417        switch (keyCode) {
1418            case KeyEvent.KEYCODE_VOLUME_UP:
1419            case KeyEvent.KEYCODE_VOLUME_DOWN:
1420                if (/*mActivity.isInCameraApp() && */ mFirstTimeInitialized) {
1421                    onShutterButtonClick();
1422                    return true;
1423                }
1424                return false;
1425            case KeyEvent.KEYCODE_FOCUS:
1426                if (mFirstTimeInitialized) {
1427                    onShutterButtonFocus(false);
1428                }
1429                return true;
1430        }
1431        return false;
1432    }
1433
1434    private void closeCamera() {
1435        if (mCameraDevice != null) {
1436            mCameraDevice.setZoomChangeListener(null);
1437            mCameraDevice.setFaceDetectionCallback(null, null);
1438            mCameraDevice.setErrorCallback(null);
1439            CameraHolder.instance().release();
1440            mFaceDetectionStarted = false;
1441            mCameraDevice = null;
1442            setCameraState(PREVIEW_STOPPED);
1443            mFocusManager.onCameraReleased();
1444        }
1445    }
1446
1447    private void setDisplayOrientation() {
1448        mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
1449        mDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, mCameraId);
1450        mCameraDisplayOrientation = mDisplayOrientation;
1451        mUI.setDisplayOrientation(mDisplayOrientation);
1452        if (mFocusManager != null) {
1453            mFocusManager.setDisplayOrientation(mDisplayOrientation);
1454        }
1455        // Change the camera display orientation
1456        if (mCameraDevice != null) {
1457            mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
1458        }
1459    }
1460
1461    /** Only called by UI thread. */
1462    private void setupPreview() {
1463        mFocusManager.resetTouchFocus();
1464        startPreview();
1465    }
1466
1467    // This can only be called by UI Thread.
1468    private void startPreview() {
1469        if (mPaused || mCameraDevice == null) {
1470            return;
1471        }
1472        SurfaceTexture st = mUI.getSurfaceTexture();
1473        if (st == null) {
1474            Log.w(TAG, "startPreview: surfaceTexture is not ready.");
1475            return;
1476        }
1477        if (!mCameraPreviewParamsReady) {
1478            Log.w(TAG, "startPreview: parameters for preview is not ready.");
1479            return;
1480        }
1481        mCameraDevice.setErrorCallback(mErrorCallback);
1482
1483        // ICS camera frameworks has a bug. Face detection state is not cleared
1484        // after taking a picture. Stop the preview to work around it. The bug
1485        // was fixed in JB.
1486        if (mCameraState != PREVIEW_STOPPED) stopPreview();
1487
1488        setDisplayOrientation();
1489
1490        if (!mSnapshotOnIdle) {
1491            // If the focus mode is continuous autofocus, call cancelAutoFocus to
1492            // resume it because it may have been paused by autoFocus call.
1493            if (CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusManager.getFocusMode())) {
1494                mCameraDevice.cancelAutoFocus();
1495            }
1496            mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
1497        }
1498        setCameraParameters(UPDATE_PARAM_ALL);
1499        // Let UI set its expected aspect ratio
1500        mCameraDevice.setPreviewTexture(st);
1501
1502        Log.v(TAG, "startPreview");
1503        mCameraDevice.startPreview();
1504        mFocusManager.onPreviewStarted();
1505        onPreviewStarted();
1506
1507        if (mSnapshotOnIdle) {
1508            mHandler.post(mDoSnapRunnable);
1509        }
1510    }
1511
1512    @Override
1513    public void stopPreview() {
1514        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1515            Log.v(TAG, "stopPreview");
1516            mCameraDevice.stopPreview();
1517            mFaceDetectionStarted = false;
1518        }
1519        setCameraState(PREVIEW_STOPPED);
1520        if (mFocusManager != null) mFocusManager.onPreviewStopped();
1521    }
1522
1523    @SuppressWarnings("deprecation")
1524    private void updateCameraParametersInitialize() {
1525        // Reset preview frame rate to the maximum because it may be lowered by
1526        // video camera application.
1527        int[] fpsRange = CameraUtil.getPhotoPreviewFpsRange(mParameters);
1528        if (fpsRange != null && fpsRange.length > 0) {
1529            mParameters.setPreviewFpsRange(
1530                    fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX],
1531                    fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]);
1532        }
1533
1534        mParameters.set(CameraUtil.RECORDING_HINT, CameraUtil.FALSE);
1535
1536        // Disable video stabilization. Convenience methods not available in API
1537        // level <= 14
1538        String vstabSupported = mParameters.get("video-stabilization-supported");
1539        if ("true".equals(vstabSupported)) {
1540            mParameters.set("video-stabilization", "false");
1541        }
1542    }
1543
1544    private void updateCameraParametersZoom() {
1545        // Set zoom.
1546        if (mParameters.isZoomSupported()) {
1547            mParameters.setZoom(mZoomValue);
1548        }
1549    }
1550
1551    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1552    private void setAutoExposureLockIfSupported() {
1553        if (mAeLockSupported) {
1554            mParameters.setAutoExposureLock(mFocusManager.getAeAwbLock());
1555        }
1556    }
1557
1558    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1559    private void setAutoWhiteBalanceLockIfSupported() {
1560        if (mAwbLockSupported) {
1561            mParameters.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
1562        }
1563    }
1564
1565    private void setFocusAreasIfSupported() {
1566        if (mFocusAreaSupported) {
1567            mParameters.setFocusAreas(mFocusManager.getFocusAreas());
1568        }
1569    }
1570
1571    private void setMeteringAreasIfSupported() {
1572        if (mMeteringAreaSupported) {
1573            mParameters.setMeteringAreas(mFocusManager.getMeteringAreas());
1574        }
1575    }
1576
1577    private boolean updateCameraParametersPreference() {
1578        setAutoExposureLockIfSupported();
1579        setAutoWhiteBalanceLockIfSupported();
1580        setFocusAreasIfSupported();
1581        setMeteringAreasIfSupported();
1582
1583        // Set picture size.
1584        String pictureSize = mPreferences.getString(
1585                CameraSettings.KEY_PICTURE_SIZE, null);
1586        if (pictureSize == null) {
1587            CameraSettings.initialCameraPictureSize(mActivity, mParameters);
1588        } else {
1589            List<Size> supported = mParameters.getSupportedPictureSizes();
1590            CameraSettings.setCameraPictureSize(
1591                    pictureSize, supported, mParameters);
1592        }
1593        Size size = mParameters.getPictureSize();
1594
1595        // Set a preview size that is closest to the viewfinder height and has
1596        // the right aspect ratio.
1597        List<Size> sizes = mParameters.getSupportedPreviewSizes();
1598        Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes,
1599                (double) size.width / size.height);
1600        Size original = mParameters.getPreviewSize();
1601        if (!original.equals(optimalSize)) {
1602            mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
1603            if(optimalSize.width != 0 && optimalSize.height != 0) {
1604                mUI.updatePreviewAspectRatio((float) optimalSize.width
1605                        / (float) optimalSize.height);
1606            }
1607
1608            // Zoom related settings will be changed for different preview
1609            // sizes, so set and read the parameters to get latest values
1610            if (mHandler.getLooper() == Looper.myLooper()) {
1611                // On UI thread only, not when camera starts up
1612                setupPreview();
1613            } else {
1614                mCameraDevice.setParameters(mParameters);
1615            }
1616            mParameters = mCameraDevice.getParameters();
1617        }
1618        Log.v(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height);
1619
1620        // Since changing scene mode may change supported values, set scene mode
1621        // first. HDR is a scene mode. To promote it in UI, it is stored in a
1622        // separate preference.
1623        String onValue = mActivity.getString(R.string.setting_on_value);
1624        String hdr = mPreferences.getString(CameraSettings.KEY_CAMERA_HDR,
1625                mActivity.getString(R.string.pref_camera_hdr_default));
1626        String hdrPlus = mPreferences.getString(CameraSettings.KEY_CAMERA_HDR_PLUS,
1627                mActivity.getString(R.string.pref_camera_hdr_plus_default));
1628        boolean hdrOn = onValue.equals(hdr);
1629        boolean hdrPlusOn = onValue.equals(hdrPlus);
1630
1631        boolean doGcamModeSwitch = false;
1632        if (hdrPlusOn && GcamHelper.hasGcamCapture()) {
1633            // Kick off mode switch to gcam.
1634            doGcamModeSwitch = true;
1635        } else {
1636            if (hdrOn) {
1637                mSceneMode = CameraUtil.SCENE_MODE_HDR;
1638            } else {
1639                mSceneMode = mPreferences.getString(
1640                        CameraSettings.KEY_SCENE_MODE,
1641                        mActivity.getString(R.string.pref_camera_scenemode_default));
1642            }
1643        }
1644        if (CameraUtil.isSupported(mSceneMode, mParameters.getSupportedSceneModes())) {
1645            if (!mParameters.getSceneMode().equals(mSceneMode)) {
1646                mParameters.setSceneMode(mSceneMode);
1647
1648                // Setting scene mode will change the settings of flash mode,
1649                // white balance, and focus mode. Here we read back the
1650                // parameters, so we can know those settings.
1651                mCameraDevice.setParameters(mParameters);
1652                mParameters = mCameraDevice.getParameters();
1653            }
1654        } else {
1655            mSceneMode = mParameters.getSceneMode();
1656            if (mSceneMode == null) {
1657                mSceneMode = Parameters.SCENE_MODE_AUTO;
1658            }
1659        }
1660
1661        // Set JPEG quality.
1662        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1663                CameraProfile.QUALITY_HIGH);
1664        mParameters.setJpegQuality(jpegQuality);
1665
1666        // For the following settings, we need to check if the settings are
1667        // still supported by latest driver, if not, ignore the settings.
1668
1669        // Set exposure compensation
1670        int value = CameraSettings.readExposure(mPreferences);
1671        int max = mParameters.getMaxExposureCompensation();
1672        int min = mParameters.getMinExposureCompensation();
1673        if (value >= min && value <= max) {
1674            mParameters.setExposureCompensation(value);
1675        } else {
1676            Log.w(TAG, "invalid exposure range: " + value);
1677        }
1678
1679        if (Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
1680            // Set flash mode.
1681            String flashMode = mPreferences.getString(
1682                    CameraSettings.KEY_FLASH_MODE,
1683                    mActivity.getString(R.string.pref_camera_flashmode_default));
1684            List<String> supportedFlash = mParameters.getSupportedFlashModes();
1685            if (CameraUtil.isSupported(flashMode, supportedFlash)) {
1686                mParameters.setFlashMode(flashMode);
1687            } else {
1688                flashMode = mParameters.getFlashMode();
1689                if (flashMode == null) {
1690                    flashMode = mActivity.getString(
1691                            R.string.pref_camera_flashmode_no_flash);
1692                }
1693            }
1694
1695            // Set white balance parameter.
1696            String whiteBalance = mPreferences.getString(
1697                    CameraSettings.KEY_WHITE_BALANCE,
1698                    mActivity.getString(R.string.pref_camera_whitebalance_default));
1699            if (CameraUtil.isSupported(whiteBalance,
1700                    mParameters.getSupportedWhiteBalance())) {
1701                mParameters.setWhiteBalance(whiteBalance);
1702            } else {
1703                whiteBalance = mParameters.getWhiteBalance();
1704                if (whiteBalance == null) {
1705                    whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1706                }
1707            }
1708
1709            // Set focus mode.
1710            mFocusManager.overrideFocusMode(null);
1711            mParameters.setFocusMode(mFocusManager.getFocusMode());
1712        } else {
1713            mFocusManager.overrideFocusMode(mParameters.getFocusMode());
1714        }
1715
1716        if (mContinuousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
1717            updateAutoFocusMoveCallback();
1718        }
1719
1720        return doGcamModeSwitch;
1721    }
1722
1723    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1724    private void updateAutoFocusMoveCallback() {
1725        if (mParameters.getFocusMode().equals(CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE)) {
1726            mCameraDevice.setAutoFocusMoveCallback(mHandler,
1727                    (CameraAFMoveCallback) mAutoFocusMoveCallback);
1728        } else {
1729            mCameraDevice.setAutoFocusMoveCallback(null, null);
1730        }
1731    }
1732
1733    // We separate the parameters into several subsets, so we can update only
1734    // the subsets actually need updating. The PREFERENCE set needs extra
1735    // locking because the preference can be changed from GLThread as well.
1736    private void setCameraParameters(int updateSet) {
1737        boolean doModeSwitch = false;
1738
1739        if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
1740            updateCameraParametersInitialize();
1741        }
1742
1743        if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
1744            updateCameraParametersZoom();
1745        }
1746
1747        if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
1748            doModeSwitch = updateCameraParametersPreference();
1749        }
1750
1751        mCameraDevice.setParameters(mParameters);
1752
1753        // Switch to gcam module if HDR+ was selected
1754        if (doModeSwitch && !mIsImageCaptureIntent) {
1755            mHandler.sendEmptyMessage(SWITCH_TO_GCAM_MODULE);
1756        }
1757    }
1758
1759    // If the Camera is idle, update the parameters immediately, otherwise
1760    // accumulate them in mUpdateSet and update later.
1761    private void setCameraParametersWhenIdle(int additionalUpdateSet) {
1762        mUpdateSet |= additionalUpdateSet;
1763        if (mCameraDevice == null) {
1764            // We will update all the parameters when we open the device, so
1765            // we don't need to do anything now.
1766            mUpdateSet = 0;
1767            return;
1768        } else if (isCameraIdle()) {
1769            setCameraParameters(mUpdateSet);
1770            updateSceneMode();
1771            mUpdateSet = 0;
1772        } else {
1773            if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
1774                mHandler.sendEmptyMessageDelayed(
1775                        SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
1776            }
1777        }
1778    }
1779
1780    @Override
1781    public boolean isCameraIdle() {
1782        return (mCameraState == IDLE) ||
1783                (mCameraState == PREVIEW_STOPPED) ||
1784                ((mFocusManager != null) && mFocusManager.isFocusCompleted()
1785                        && (mCameraState != SWITCHING_CAMERA));
1786    }
1787
1788    @Override
1789    public boolean isImageCaptureIntent() {
1790        String action = mActivity.getIntent().getAction();
1791        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
1792                || CameraActivity.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
1793    }
1794
1795    private void setupCaptureParams() {
1796        Bundle myExtras = mActivity.getIntent().getExtras();
1797        if (myExtras != null) {
1798            mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1799            mCropValue = myExtras.getString("crop");
1800        }
1801    }
1802
1803    @Override
1804    public void onSharedPreferenceChanged() {
1805        // ignore the events after "onPause()"
1806        if (mPaused) return;
1807
1808        boolean recordLocation = RecordLocationPreference.get(
1809                mPreferences, mContentResolver);
1810        mLocationManager.recordLocation(recordLocation);
1811
1812        setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);
1813        mUI.updateOnScreenIndicators(mParameters, mPreferenceGroup, mPreferences);
1814    }
1815
1816    @Override
1817    public void onCameraPickerClicked(int cameraId) {
1818        if (mPaused || mPendingSwitchCameraId != -1) return;
1819
1820        mPendingSwitchCameraId = cameraId;
1821
1822        Log.v(TAG, "Start to switch camera. cameraId=" + cameraId);
1823        // We need to keep a preview frame for the animation before
1824        // releasing the camera. This will trigger onPreviewTextureCopied.
1825        //TODO: Need to animate the camera switch
1826        switchCamera();
1827    }
1828
1829    // Preview texture has been copied. Now camera can be released and the
1830    // animation can be started.
1831    @Override
1832    public void onPreviewTextureCopied() {
1833        mHandler.sendEmptyMessage(SWITCH_CAMERA);
1834    }
1835
1836    @Override
1837    public void onCaptureTextureCopied() {
1838    }
1839
1840    @Override
1841    public void onUserInteraction() {
1842        if (!mActivity.isFinishing()) keepScreenOnAwhile();
1843    }
1844
1845    private void resetScreenOn() {
1846        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1847        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1848    }
1849
1850    private void keepScreenOnAwhile() {
1851        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1852        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1853        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1854    }
1855
1856    @Override
1857    public void onOverriddenPreferencesClicked() {
1858        if (mPaused) return;
1859        mUI.showPreferencesToast();
1860    }
1861
1862    private void showTapToFocusToast() {
1863        // TODO: Use a toast?
1864        new RotateTextToast(mActivity, R.string.tap_to_focus, 0).show();
1865        // Clear the preference.
1866        Editor editor = mPreferences.edit();
1867        editor.putBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, false);
1868        editor.apply();
1869    }
1870
1871    private void initializeCapabilities() {
1872        mInitialParams = mCameraDevice.getParameters();
1873        mFocusAreaSupported = CameraUtil.isFocusAreaSupported(mInitialParams);
1874        mMeteringAreaSupported = CameraUtil.isMeteringAreaSupported(mInitialParams);
1875        mAeLockSupported = CameraUtil.isAutoExposureLockSupported(mInitialParams);
1876        mAwbLockSupported = CameraUtil.isAutoWhiteBalanceLockSupported(mInitialParams);
1877        mContinuousFocusSupported = mInitialParams.getSupportedFocusModes().contains(
1878                CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE);
1879    }
1880
1881    @Override
1882    public void onCountDownFinished() {
1883        mSnapshotOnIdle = false;
1884        mFocusManager.doSnap();
1885        mFocusManager.onShutterUp();
1886    }
1887
1888    @Override
1889    public void onShowSwitcherPopup() {
1890        mUI.onShowSwitcherPopup();
1891    }
1892
1893    @Override
1894    public int onZoomChanged(int index) {
1895        // Not useful to change zoom value when the activity is paused.
1896        if (mPaused) return index;
1897        mZoomValue = index;
1898        if (mParameters == null || mCameraDevice == null) return index;
1899        // Set zoom parameters asynchronously
1900        mParameters.setZoom(mZoomValue);
1901        mCameraDevice.setParameters(mParameters);
1902        Parameters p = mCameraDevice.getParameters();
1903        if (p != null) return p.getZoom();
1904        return index;
1905    }
1906
1907    @Override
1908    public int getCameraState() {
1909        return mCameraState;
1910    }
1911
1912    @Override
1913    public void onQueueStatus(boolean full) {
1914        mUI.enableShutter(!full);
1915    }
1916
1917    @Override
1918    public void onMediaSaveServiceConnected(MediaSaveService s) {
1919        // We set the listener only when both service and shutterbutton
1920        // are initialized.
1921        if (mFirstTimeInitialized) {
1922            s.setListener(this);
1923        }
1924    }
1925
1926    @Override
1927    public void onAccuracyChanged(Sensor sensor, int accuracy) {
1928    }
1929
1930    @Override
1931    public void onSensorChanged(SensorEvent event) {
1932        int type = event.sensor.getType();
1933        float[] data;
1934        if (type == Sensor.TYPE_ACCELEROMETER) {
1935            data = mGData;
1936        } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
1937            data = mMData;
1938        } else {
1939            // we should not be here.
1940            return;
1941        }
1942        for (int i = 0; i < 3 ; i++) {
1943            data[i] = event.values[i];
1944        }
1945        float[] orientation = new float[3];
1946        SensorManager.getRotationMatrix(mR, null, mGData, mMData);
1947        SensorManager.getOrientation(mR, orientation);
1948        mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
1949        if (mHeading < 0) {
1950            mHeading += 360;
1951        }
1952    }
1953
1954    @Override
1955    public void onPreviewFocusChanged(boolean previewFocused) {
1956        mUI.onPreviewFocusChanged(previewFocused);
1957    }
1958
1959    @Override
1960    public boolean arePreviewControlsVisible() {
1961        return mUI.arePreviewControlsVisible();
1962    }
1963
1964    // For debugging only.
1965    public void setDebugUri(Uri uri) {
1966        mDebugUri = uri;
1967    }
1968
1969    // For debugging only.
1970    private void saveToDebugUri(byte[] data) {
1971        if (mDebugUri != null) {
1972            OutputStream outputStream = null;
1973            try {
1974                outputStream = mContentResolver.openOutputStream(mDebugUri);
1975                outputStream.write(data);
1976                outputStream.close();
1977            } catch (IOException e) {
1978                Log.e(TAG, "Exception while writing debug jpeg file", e);
1979            } finally {
1980                CameraUtil.closeSilently(outputStream);
1981            }
1982        }
1983    }
1984
1985/* Below is no longer needed, except to get rid of compile error
1986 * TODO: Remove these
1987 */
1988
1989    // TODO: Delete this function after old camera code is removed
1990    @Override
1991    public void onRestorePreferencesClicked() {}
1992
1993}
1994