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