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