PhotoModule.java revision 971ad50c0f220a396ffce50245617ff59ed0a09e
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.Intent;
23import android.graphics.Bitmap;
24import android.graphics.BitmapFactory;
25import android.graphics.SurfaceTexture;
26import android.location.Location;
27import android.media.CameraProfile;
28import android.net.Uri;
29import android.os.AsyncTask;
30import android.os.Build;
31import android.os.Bundle;
32import android.os.Handler;
33import android.os.Looper;
34import android.os.Message;
35import android.os.MessageQueue;
36import android.os.SystemClock;
37import android.provider.MediaStore;
38import android.view.KeyEvent;
39import android.view.View;
40
41import com.android.camera.PhotoModule.NamedImages.NamedEntity;
42import com.android.camera.app.AppController;
43import com.android.camera.app.CameraAppUI;
44import com.android.camera.app.CameraProvider;
45import com.android.camera.app.MediaSaver;
46import com.android.camera.app.MemoryManager;
47import com.android.camera.app.MemoryManager.MemoryListener;
48import com.android.camera.app.MotionManager;
49import com.android.camera.debug.Log;
50import com.android.camera.exif.ExifInterface;
51import com.android.camera.exif.ExifTag;
52import com.android.camera.exif.Rational;
53import com.android.camera.hardware.HardwareSpec;
54import com.android.camera.hardware.HardwareSpecImpl;
55import com.android.camera.hardware.HeadingSensor;
56import com.android.camera.module.ModuleController;
57import com.android.camera.one.OneCamera;
58import com.android.camera.one.OneCameraAccessException;
59import com.android.camera.remote.RemoteCameraModule;
60import com.android.camera.settings.CameraPictureSizesCacher;
61import com.android.camera.settings.Keys;
62import com.android.camera.settings.ResolutionUtil;
63import com.android.camera.settings.SettingsManager;
64import com.android.camera.stats.SessionStatsCollector;
65import com.android.camera.stats.UsageStatistics;
66import com.android.camera.ui.CountDownView;
67import com.android.camera.ui.TouchCoordinate;
68import com.android.camera.util.AndroidServices;
69import com.android.camera.util.ApiHelper;
70import com.android.camera.util.CameraUtil;
71import com.android.camera.util.GcamHelper;
72import com.android.camera.util.GservicesHelper;
73import com.android.camera.util.Size;
74import com.android.camera2.R;
75import com.android.ex.camera2.portability.CameraAgent;
76import com.android.ex.camera2.portability.CameraAgent.CameraAFCallback;
77import com.android.ex.camera2.portability.CameraAgent.CameraAFMoveCallback;
78import com.android.ex.camera2.portability.CameraAgent.CameraPictureCallback;
79import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
80import com.android.ex.camera2.portability.CameraAgent.CameraShutterCallback;
81import com.android.ex.camera2.portability.CameraCapabilities;
82import com.android.ex.camera2.portability.CameraDeviceInfo.Characteristics;
83import com.android.ex.camera2.portability.CameraSettings;
84import com.google.common.logging.eventprotos;
85
86import java.io.ByteArrayOutputStream;
87import java.io.File;
88import java.io.FileNotFoundException;
89import java.io.FileOutputStream;
90import java.io.IOException;
91import java.io.OutputStream;
92import java.lang.ref.WeakReference;
93import java.util.ArrayList;
94import java.util.List;
95import java.util.Vector;
96
97public class PhotoModule
98        extends CameraModule
99        implements PhotoController,
100        ModuleController,
101        MemoryListener,
102        FocusOverlayManager.Listener,
103        SettingsManager.OnSettingChangedListener,
104        RemoteCameraModule,
105        CountDownView.OnCountDownStatusListener {
106
107    private static final Log.Tag TAG = new Log.Tag("PhotoModule");
108
109    // We number the request code from 1000 to avoid collision with Gallery.
110    private static final int REQUEST_CROP = 1000;
111
112    // Messages defined for the UI thread handler.
113    private static final int MSG_FIRST_TIME_INIT = 1;
114    private static final int MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE = 2;
115
116    // The subset of parameters we need to update in setCameraParameters().
117    private static final int UPDATE_PARAM_INITIALIZE = 1;
118    private static final int UPDATE_PARAM_ZOOM = 2;
119    private static final int UPDATE_PARAM_PREFERENCE = 4;
120    private static final int UPDATE_PARAM_ALL = -1;
121
122    private static final String DEBUG_IMAGE_PREFIX = "DEBUG_";
123
124    private CameraActivity mActivity;
125    private CameraProxy mCameraDevice;
126    private int mCameraId;
127    private CameraCapabilities mCameraCapabilities;
128    private CameraSettings mCameraSettings;
129    private boolean mPaused;
130
131    private PhotoUI mUI;
132
133    // The activity is going to switch to the specified camera id. This is
134    // needed because texture copy is done in GL thread. -1 means camera is not
135    // switching.
136    protected int mPendingSwitchCameraId = -1;
137
138    // When setCameraParametersWhenIdle() is called, we accumulate the subsets
139    // needed to be updated in mUpdateSet.
140    private int mUpdateSet;
141
142    private float mZoomValue; // The current zoom ratio.
143    private int mTimerDuration;
144    /** Set when a volume button is clicked to take photo */
145    private boolean mVolumeButtonClickedFlag = false;
146
147    private boolean mFocusAreaSupported;
148    private boolean mMeteringAreaSupported;
149    private boolean mAeLockSupported;
150    private boolean mAwbLockSupported;
151    private boolean mContinuousFocusSupported;
152
153    private static final String sTempCropFilename = "crop-temp";
154
155    private boolean mFaceDetectionStarted = false;
156
157    // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
158    private String mCropValue;
159    private Uri mSaveUri;
160
161    private Uri mDebugUri;
162
163    // We use a queue to generated names of the images to be used later
164    // when the image is ready to be saved.
165    private NamedImages mNamedImages;
166
167    private final Runnable mDoSnapRunnable = new Runnable() {
168        @Override
169        public void run() {
170            onShutterButtonClick();
171        }
172    };
173
174    /**
175     * An unpublished intent flag requesting to return as soon as capturing is
176     * completed. TODO: consider publishing by moving into MediaStore.
177     */
178    private static final String EXTRA_QUICK_CAPTURE =
179            "android.intent.extra.quickCapture";
180
181    // The display rotation in degrees. This is only valid when mCameraState is
182    // not PREVIEW_STOPPED.
183    private int mDisplayRotation;
184    // The value for UI components like indicators.
185    private int mDisplayOrientation;
186    // The value for cameradevice.CameraSettings.setPhotoRotationDegrees.
187    private int mJpegRotation;
188    // Indicates whether we are using front camera
189    private boolean mMirror;
190    private boolean mFirstTimeInitialized;
191    private boolean mIsImageCaptureIntent;
192
193    private int mCameraState = PREVIEW_STOPPED;
194    private boolean mSnapshotOnIdle = false;
195
196    private ContentResolver mContentResolver;
197
198    private AppController mAppController;
199
200    private final PostViewPictureCallback mPostViewPictureCallback =
201            new PostViewPictureCallback();
202    private final RawPictureCallback mRawPictureCallback =
203            new RawPictureCallback();
204    private final AutoFocusCallback mAutoFocusCallback =
205            new AutoFocusCallback();
206    private final Object mAutoFocusMoveCallback =
207            ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
208                    ? new AutoFocusMoveCallback()
209                    : null;
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    /** Touch coordinate for shutter button press. */
219    private TouchCoordinate mShutterTouchCoordinate;
220
221
222    // These latency time are for the CameraLatency test.
223    public long mAutoFocusTime;
224    public long mShutterLag;
225    public long mShutterToPictureDisplayedTime;
226    public long mPictureDisplayedToJpegCallbackTime;
227    public long mJpegCallbackFinishTime;
228    public long mCaptureStartTime;
229
230    // This handles everything about focus.
231    private FocusOverlayManager mFocusManager;
232
233    private final int mGcamModeIndex;
234    private SoundPlayer mCountdownSoundPlayer;
235
236    private CameraCapabilities.SceneMode mSceneMode;
237
238    private final Handler mHandler = new MainHandler(this);
239
240    private boolean mQuickCapture;
241
242    /** Used to detect motion. We use this to release focus lock early. */
243    private MotionManager mMotionManager;
244
245    private HeadingSensor mHeadingSensor;
246
247    /** True if all the parameters needed to start preview is ready. */
248    private boolean mCameraPreviewParamsReady = false;
249
250    private final MediaSaver.OnMediaSavedListener mOnMediaSavedListener =
251            new MediaSaver.OnMediaSavedListener() {
252                @Override
253                public void onMediaSaved(Uri uri) {
254                    if (uri != null) {
255                        mActivity.notifyNewMedia(uri);
256                    }
257                }
258            };
259    private boolean mShouldResizeTo16x9 = false;
260
261    /**
262     * We keep the flash setting before entering scene modes (HDR)
263     * and restore it after HDR is off.
264     */
265    private String mFlashModeBeforeSceneMode;
266
267    private void checkDisplayRotation() {
268        // Need to just be a no-op for the quick resume-pause scenario.
269        if (mPaused) {
270            return;
271        }
272        // Set the display orientation if display rotation has changed.
273        // Sometimes this happens when the device is held upside
274        // down and camera app is opened. Rotation animation will
275        // take some time and the rotation value we have got may be
276        // wrong. Framework does not have a callback for this now.
277        if (CameraUtil.getDisplayRotation() != mDisplayRotation) {
278            setDisplayOrientation();
279        }
280        if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
281            mHandler.postDelayed(new Runnable() {
282                @Override
283                public void run() {
284                    checkDisplayRotation();
285                }
286            }, 100);
287        }
288    }
289
290    /**
291     * This Handler is used to post message back onto the main thread of the
292     * application
293     */
294    private static class MainHandler extends Handler {
295        private final WeakReference<PhotoModule> mModule;
296
297        public MainHandler(PhotoModule module) {
298            super(Looper.getMainLooper());
299            mModule = new WeakReference<PhotoModule>(module);
300        }
301
302        @Override
303        public void handleMessage(Message msg) {
304            PhotoModule module = mModule.get();
305            if (module == null) {
306                return;
307            }
308            switch (msg.what) {
309                case MSG_FIRST_TIME_INIT: {
310                    module.initializeFirstTime();
311                    break;
312                }
313
314                case MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE: {
315                    module.setCameraParametersWhenIdle(0);
316                    break;
317                }
318            }
319        }
320    }
321
322    private void switchToGcamCapture() {
323        if (mActivity != null && mGcamModeIndex != 0) {
324            SettingsManager settingsManager = mActivity.getSettingsManager();
325            settingsManager.set(SettingsManager.SCOPE_GLOBAL,
326                                Keys.KEY_CAMERA_HDR_PLUS, true);
327
328            // Disable the HDR+ button to prevent callbacks from being
329            // queued before the correct callback is attached to the button
330            // in the new module.  The new module will set the enabled/disabled
331            // of this button when the module's preferred camera becomes available.
332            ButtonManager buttonManager = mActivity.getButtonManager();
333
334            buttonManager.disableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
335
336            mAppController.getCameraAppUI().freezeScreenUntilPreviewReady();
337
338            // Do not post this to avoid this module switch getting interleaved with
339            // other button callbacks.
340            mActivity.onModeSelected(mGcamModeIndex);
341
342            buttonManager.enableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
343        }
344    }
345
346    /**
347     * Constructs a new photo module.
348     */
349    public PhotoModule(AppController app) {
350        super(app);
351        mGcamModeIndex = app.getAndroidContext().getResources()
352                .getInteger(R.integer.camera_mode_gcam);
353    }
354
355    @Override
356    public String getPeekAccessibilityString() {
357        return mAppController.getAndroidContext()
358            .getResources().getString(R.string.photo_accessibility_peek);
359    }
360
361    @Override
362    public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
363        mActivity = activity;
364        // TODO: Need to look at the controller interface to see if we can get
365        // rid of passing in the activity directly.
366        mAppController = mActivity;
367
368        mUI = new PhotoUI(mActivity, this, mActivity.getModuleLayoutRoot());
369        mActivity.setPreviewStatusListener(mUI);
370
371        SettingsManager settingsManager = mActivity.getSettingsManager();
372        mCameraId = settingsManager.getInteger(mAppController.getModuleScope(),
373                                               Keys.KEY_CAMERA_ID);
374
375        // TODO: Move this to SettingsManager as a part of upgrade procedure.
376        if (!settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
377                                        Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
378            // Switch to back camera to set aspect ratio.
379            mCameraId = settingsManager.getIntegerDefault(Keys.KEY_CAMERA_ID);
380        }
381
382        mContentResolver = mActivity.getContentResolver();
383
384        // Surface texture is from camera screen nail and startPreview needs it.
385        // This must be done before startPreview.
386        mIsImageCaptureIntent = isImageCaptureIntent();
387        mUI.setCountdownFinishedListener(this);
388
389        mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
390        mHeadingSensor = new HeadingSensor(AndroidServices.instance().provideSensorManager());
391        mCountdownSoundPlayer = new SoundPlayer(mAppController.getAndroidContext());
392
393        // TODO: Make this a part of app controller API.
394        View cancelButton = mActivity.findViewById(R.id.shutter_cancel_button);
395        cancelButton.setOnClickListener(new View.OnClickListener() {
396            @Override
397            public void onClick(View view) {
398                cancelCountDown();
399            }
400        });
401    }
402
403    private void cancelCountDown() {
404        if (mUI.isCountingDown()) {
405            // Cancel on-going countdown.
406            mUI.cancelCountDown();
407        }
408        mAppController.getCameraAppUI().transitionToCapture();
409        mAppController.getCameraAppUI().showModeOptions();
410        mAppController.setShutterEnabled(true);
411    }
412
413    @Override
414    public boolean isUsingBottomBar() {
415        return true;
416    }
417
418    private void initializeControlByIntent() {
419        if (mIsImageCaptureIntent) {
420            mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
421            setupCaptureParams();
422        }
423    }
424
425    private void onPreviewStarted() {
426        mAppController.onPreviewStarted();
427        mAppController.setShutterEnabled(true);
428        setCameraState(IDLE);
429        startFaceDetection();
430    }
431
432    @Override
433    public void onPreviewUIReady() {
434        Log.i(TAG, "onPreviewUIReady");
435        startPreview();
436    }
437
438    @Override
439    public void onPreviewUIDestroyed() {
440        if (mCameraDevice == null) {
441            return;
442        }
443        mCameraDevice.setPreviewTexture(null);
444        stopPreview();
445    }
446
447    @Override
448    public void startPreCaptureAnimation() {
449        mAppController.startFlashAnimation(false);
450    }
451
452    private void onCameraOpened() {
453        openCameraCommon();
454        initializeControlByIntent();
455    }
456
457    private void switchCamera() {
458        if (mPaused) {
459            return;
460        }
461        cancelCountDown();
462
463        mAppController.freezeScreenUntilPreviewReady();
464        SettingsManager settingsManager = mActivity.getSettingsManager();
465
466        Log.i(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
467        closeCamera();
468        mCameraId = mPendingSwitchCameraId;
469
470        settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID, mCameraId);
471        requestCameraOpen();
472        mUI.clearFaces();
473        if (mFocusManager != null) {
474            mFocusManager.removeMessages();
475        }
476
477        mMirror = isCameraFrontFacing();
478        mFocusManager.setMirror(mMirror);
479        // Start switch camera animation. Post a message because
480        // onFrameAvailable from the old camera may already exist.
481    }
482
483    /**
484     * Uses the {@link CameraProvider} to open the currently-selected camera
485     * device, using {@link GservicesHelper} to choose between API-1 and API-2.
486     */
487    private void requestCameraOpen() {
488        Log.v(TAG, "requestCameraOpen");
489        mActivity.getCameraProvider().requestCamera(mCameraId,
490                        GservicesHelper.useCamera2ApiThroughPortabilityLayer(mActivity
491                                .getContentResolver()));
492    }
493
494    private final ButtonManager.ButtonCallback mCameraCallback =
495            new ButtonManager.ButtonCallback() {
496                @Override
497                public void onStateChanged(int state) {
498                    // At the time this callback is fired, the camera id
499                    // has be set to the desired camera.
500
501                    if (mPaused || mAppController.getCameraProvider().waitingForCamera()) {
502                        return;
503                    }
504                    // If switching to back camera, and HDR+ is still on,
505                    // switch back to gcam, otherwise handle callback normally.
506                    SettingsManager settingsManager = mActivity.getSettingsManager();
507                    if (Keys.isCameraBackFacing(settingsManager,
508                                                mAppController.getModuleScope())) {
509                        if (Keys.requestsReturnToHdrPlus(settingsManager,
510                                                         mAppController.getModuleScope())) {
511                            switchToGcamCapture();
512                            return;
513                        }
514                    }
515
516                    mPendingSwitchCameraId = state;
517
518                    Log.d(TAG, "Start to switch camera. cameraId=" + state);
519                    // We need to keep a preview frame for the animation before
520                    // releasing the camera. This will trigger
521                    // onPreviewTextureCopied.
522                    // TODO: Need to animate the camera switch
523                    switchCamera();
524                }
525            };
526
527    private final ButtonManager.ButtonCallback mHdrPlusCallback =
528            new ButtonManager.ButtonCallback() {
529                @Override
530                public void onStateChanged(int state) {
531                    SettingsManager settingsManager = mActivity.getSettingsManager();
532                    if (GcamHelper.hasGcamAsSeparateModule(
533                            mAppController.getCameraFeatureConfig())) {
534                        // Set the camera setting to default backfacing.
535                        settingsManager.setToDefault(mAppController.getModuleScope(),
536                                                     Keys.KEY_CAMERA_ID);
537                        switchToGcamCapture();
538                    } else {
539                        if (Keys.isHdrOn(settingsManager)) {
540                            settingsManager.set(mAppController.getCameraScope(), Keys.KEY_SCENE_MODE,
541                                    mCameraCapabilities.getStringifier().stringify(
542                                            CameraCapabilities.SceneMode.HDR));
543                        } else {
544                            settingsManager.set(mAppController.getCameraScope(), Keys.KEY_SCENE_MODE,
545                                    mCameraCapabilities.getStringifier().stringify(
546                                            CameraCapabilities.SceneMode.AUTO));
547                        }
548                        updateParametersSceneMode();
549                        if (mCameraDevice != null) {
550                            mCameraDevice.applySettings(mCameraSettings);
551                        }
552                        updateSceneMode();
553                    }
554                }
555            };
556
557    private final View.OnClickListener mCancelCallback = new View.OnClickListener() {
558        @Override
559        public void onClick(View v) {
560            onCaptureCancelled();
561        }
562    };
563
564    private final View.OnClickListener mDoneCallback = new View.OnClickListener() {
565        @Override
566        public void onClick(View v) {
567            onCaptureDone();
568        }
569    };
570
571    private final View.OnClickListener mRetakeCallback = new View.OnClickListener() {
572        @Override
573        public void onClick(View v) {
574            mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
575            onCaptureRetake();
576        }
577    };
578
579    @Override
580    public void hardResetSettings(SettingsManager settingsManager) {
581        // PhotoModule should hard reset HDR+ to off,
582        // and HDR to off if HDR+ is supported.
583        settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false);
584        if (GcamHelper.hasGcamAsSeparateModule(mAppController.getCameraFeatureConfig())) {
585            settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR, false);
586        }
587    }
588
589    @Override
590    public HardwareSpec getHardwareSpec() {
591        return (mCameraSettings != null ?
592                new HardwareSpecImpl(getCameraProvider(), mCameraCapabilities,
593                        mAppController.getCameraFeatureConfig()) : null);
594    }
595
596    @Override
597    public CameraAppUI.BottomBarUISpec getBottomBarSpec() {
598        CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
599
600        bottomBarSpec.enableCamera = true;
601        bottomBarSpec.cameraCallback = mCameraCallback;
602        bottomBarSpec.enableFlash = !mAppController.getSettingsManager()
603            .getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR);
604        bottomBarSpec.enableHdr = true;
605        bottomBarSpec.hdrCallback = mHdrPlusCallback;
606        bottomBarSpec.enableGridLines = true;
607        if (mCameraCapabilities != null) {
608            bottomBarSpec.enableExposureCompensation = true;
609            bottomBarSpec.exposureCompensationSetCallback =
610                new CameraAppUI.BottomBarUISpec.ExposureCompensationSetCallback() {
611                @Override
612                public void setExposure(int value) {
613                    setExposureCompensation(value);
614                }
615            };
616            bottomBarSpec.minExposureCompensation =
617                mCameraCapabilities.getMinExposureCompensation();
618            bottomBarSpec.maxExposureCompensation =
619                mCameraCapabilities.getMaxExposureCompensation();
620            bottomBarSpec.exposureCompensationStep =
621                mCameraCapabilities.getExposureCompensationStep();
622        }
623
624        bottomBarSpec.enableSelfTimer = true;
625        bottomBarSpec.showSelfTimer = true;
626
627        if (isImageCaptureIntent()) {
628            bottomBarSpec.showCancel = true;
629            bottomBarSpec.cancelCallback = mCancelCallback;
630            bottomBarSpec.showDone = true;
631            bottomBarSpec.doneCallback = mDoneCallback;
632            bottomBarSpec.showRetake = true;
633            bottomBarSpec.retakeCallback = mRetakeCallback;
634        }
635
636        return bottomBarSpec;
637    }
638
639    // either open a new camera or switch cameras
640    private void openCameraCommon() {
641        mUI.onCameraOpened(mCameraCapabilities, mCameraSettings);
642        if (mIsImageCaptureIntent) {
643            // Set hdr plus to default: off.
644            SettingsManager settingsManager = mActivity.getSettingsManager();
645            settingsManager.setToDefault(SettingsManager.SCOPE_GLOBAL,
646                                         Keys.KEY_CAMERA_HDR_PLUS);
647        }
648        updateSceneMode();
649    }
650
651    @Override
652    public void updatePreviewAspectRatio(float aspectRatio) {
653        mAppController.updatePreviewAspectRatio(aspectRatio);
654    }
655
656    private void resetExposureCompensation() {
657        SettingsManager settingsManager = mActivity.getSettingsManager();
658        if (settingsManager == null) {
659            Log.e(TAG, "Settings manager is null!");
660            return;
661        }
662        settingsManager.setToDefault(mAppController.getCameraScope(),
663                                     Keys.KEY_EXPOSURE);
664    }
665
666    // Snapshots can only be taken after this is called. It should be called
667    // once only. We could have done these things in onCreate() but we want to
668    // make preview screen appear as soon as possible.
669    private void initializeFirstTime() {
670        if (mFirstTimeInitialized || mPaused) {
671            return;
672        }
673
674        mUI.initializeFirstTime();
675
676        // We set the listener only when both service and shutterbutton
677        // are initialized.
678        getServices().getMemoryManager().addListener(this);
679
680        mNamedImages = new NamedImages();
681
682        mFirstTimeInitialized = true;
683        addIdleHandler();
684
685        mActivity.updateStorageSpaceAndHint(null);
686    }
687
688    // If the activity is paused and resumed, this method will be called in
689    // onResume.
690    private void initializeSecondTime() {
691        getServices().getMemoryManager().addListener(this);
692        mNamedImages = new NamedImages();
693        mUI.initializeSecondTime(mCameraCapabilities, mCameraSettings);
694    }
695
696    private void addIdleHandler() {
697        MessageQueue queue = Looper.myQueue();
698        queue.addIdleHandler(new MessageQueue.IdleHandler() {
699            @Override
700            public boolean queueIdle() {
701                Storage.ensureOSXCompatible();
702                return false;
703            }
704        });
705    }
706
707    @Override
708    public void startFaceDetection() {
709        if (mFaceDetectionStarted || mCameraDevice == null) {
710            return;
711        }
712        if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) {
713            mFaceDetectionStarted = true;
714            mUI.onStartFaceDetection(mDisplayOrientation, isCameraFrontFacing());
715            mCameraDevice.setFaceDetectionCallback(mHandler, mUI);
716            mCameraDevice.startFaceDetection();
717            SessionStatsCollector.instance().faceScanActive(true);
718        }
719    }
720
721    @Override
722    public void stopFaceDetection() {
723        if (!mFaceDetectionStarted || mCameraDevice == null) {
724            return;
725        }
726        if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) {
727            mFaceDetectionStarted = false;
728            mCameraDevice.setFaceDetectionCallback(null, null);
729            mCameraDevice.stopFaceDetection();
730            mUI.clearFaces();
731            SessionStatsCollector.instance().faceScanActive(false);
732        }
733    }
734
735    private final class ShutterCallback
736            implements CameraShutterCallback {
737
738        private final boolean mNeedsAnimation;
739
740        public ShutterCallback(boolean needsAnimation) {
741            mNeedsAnimation = needsAnimation;
742        }
743
744        @Override
745        public void onShutter(CameraProxy camera) {
746            mShutterCallbackTime = System.currentTimeMillis();
747            mShutterLag = mShutterCallbackTime - mCaptureStartTime;
748            Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
749            if (mNeedsAnimation) {
750                mActivity.runOnUiThread(new Runnable() {
751                    @Override
752                    public void run() {
753                        animateAfterShutter();
754                    }
755                });
756            }
757        }
758    }
759
760    private final class PostViewPictureCallback
761            implements CameraPictureCallback {
762        @Override
763        public void onPictureTaken(byte[] data, CameraProxy camera) {
764            mPostViewPictureCallbackTime = System.currentTimeMillis();
765            Log.v(TAG, "mShutterToPostViewCallbackTime = "
766                    + (mPostViewPictureCallbackTime - mShutterCallbackTime)
767                    + "ms");
768        }
769    }
770
771    private final class RawPictureCallback
772            implements CameraPictureCallback {
773        @Override
774        public void onPictureTaken(byte[] rawData, CameraProxy camera) {
775            mRawPictureCallbackTime = System.currentTimeMillis();
776            Log.v(TAG, "mShutterToRawCallbackTime = "
777                    + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
778        }
779    }
780
781    private static class ResizeBundle {
782        byte[] jpegData;
783        float targetAspectRatio;
784        ExifInterface exif;
785    }
786
787    /**
788     * @return Cropped image if the target aspect ratio is larger than the jpeg
789     *         aspect ratio on the long axis. The original jpeg otherwise.
790     */
791    private ResizeBundle cropJpegDataToAspectRatio(ResizeBundle dataBundle) {
792
793        final byte[] jpegData = dataBundle.jpegData;
794        final ExifInterface exif = dataBundle.exif;
795        float targetAspectRatio = dataBundle.targetAspectRatio;
796
797        Bitmap original = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
798        int originalWidth = original.getWidth();
799        int originalHeight = original.getHeight();
800        int newWidth;
801        int newHeight;
802
803        if (originalWidth > originalHeight) {
804            newHeight = (int) (originalWidth / targetAspectRatio);
805            newWidth = originalWidth;
806        } else {
807            newWidth = (int) (originalHeight / targetAspectRatio);
808            newHeight = originalHeight;
809        }
810        int xOffset = (originalWidth - newWidth)/2;
811        int yOffset = (originalHeight - newHeight)/2;
812
813        if (xOffset < 0 || yOffset < 0) {
814            return dataBundle;
815        }
816
817        Bitmap resized = Bitmap.createBitmap(original,xOffset,yOffset,newWidth, newHeight);
818        exif.setTagValue(ExifInterface.TAG_PIXEL_X_DIMENSION, new Integer(newWidth));
819        exif.setTagValue(ExifInterface.TAG_PIXEL_Y_DIMENSION, new Integer(newHeight));
820
821        ByteArrayOutputStream stream = new ByteArrayOutputStream();
822
823        resized.compress(Bitmap.CompressFormat.JPEG, 90, stream);
824        dataBundle.jpegData = stream.toByteArray();
825        return dataBundle;
826    }
827
828    private final class JpegPictureCallback
829            implements CameraPictureCallback {
830        Location mLocation;
831
832        public JpegPictureCallback(Location loc) {
833            mLocation = loc;
834        }
835
836        @Override
837        public void onPictureTaken(final byte[] originalJpegData, final CameraProxy camera) {
838            Log.i(TAG, "onPictureTaken");
839            mAppController.setShutterEnabled(true);
840            if (mPaused) {
841                return;
842            }
843            if (mIsImageCaptureIntent) {
844                stopPreview();
845            }
846            if (mSceneMode == CameraCapabilities.SceneMode.HDR) {
847                mUI.setSwipingEnabled(true);
848            }
849
850            mJpegPictureCallbackTime = System.currentTimeMillis();
851            // If postview callback has arrived, the captured image is displayed
852            // in postview callback. If not, the captured image is displayed in
853            // raw picture callback.
854            if (mPostViewPictureCallbackTime != 0) {
855                mShutterToPictureDisplayedTime =
856                        mPostViewPictureCallbackTime - mShutterCallbackTime;
857                mPictureDisplayedToJpegCallbackTime =
858                        mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
859            } else {
860                mShutterToPictureDisplayedTime =
861                        mRawPictureCallbackTime - mShutterCallbackTime;
862                mPictureDisplayedToJpegCallbackTime =
863                        mJpegPictureCallbackTime - mRawPictureCallbackTime;
864            }
865            Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
866                    + mPictureDisplayedToJpegCallbackTime + "ms");
867
868            if (!mIsImageCaptureIntent) {
869                setupPreview();
870            }
871
872            long now = System.currentTimeMillis();
873            mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
874            Log.v(TAG, "mJpegCallbackFinishTime = " + mJpegCallbackFinishTime + "ms");
875            mJpegPictureCallbackTime = 0;
876
877            final ExifInterface exif = Exif.getExif(originalJpegData);
878            final NamedEntity name = mNamedImages.getNextNameEntity();
879            if (mShouldResizeTo16x9) {
880                final ResizeBundle dataBundle = new ResizeBundle();
881                dataBundle.jpegData = originalJpegData;
882                dataBundle.targetAspectRatio = ResolutionUtil.NEXUS_5_LARGE_16_BY_9_ASPECT_RATIO;
883                dataBundle.exif = exif;
884                new AsyncTask<ResizeBundle, Void, ResizeBundle>() {
885
886                    @Override
887                    protected ResizeBundle doInBackground(ResizeBundle... resizeBundles) {
888                        return cropJpegDataToAspectRatio(resizeBundles[0]);
889                    }
890
891                    @Override
892                    protected void onPostExecute(ResizeBundle result) {
893                        saveFinalPhoto(result.jpegData, name, result.exif, camera);
894                    }
895                }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dataBundle);
896
897            } else {
898                saveFinalPhoto(originalJpegData, name, exif, camera);
899            }
900        }
901
902        void saveFinalPhoto(final byte[] jpegData, NamedEntity name, final ExifInterface exif,
903                CameraProxy camera) {
904            int orientation = Exif.getOrientation(exif);
905
906            float zoomValue = 1.0f;
907            if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
908                zoomValue = mCameraSettings.getCurrentZoomRatio();
909            }
910            boolean hdrOn = CameraCapabilities.SceneMode.HDR == mSceneMode;
911            String flashSetting =
912                    mActivity.getSettingsManager().getString(mAppController.getCameraScope(),
913                                                             Keys.KEY_FLASH_MODE);
914            boolean gridLinesOn = Keys.areGridLinesOn(mActivity.getSettingsManager());
915            UsageStatistics.instance().photoCaptureDoneEvent(
916                    eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
917                    name.title + ".jpg", exif,
918                    isCameraFrontFacing(), hdrOn, zoomValue, flashSetting, gridLinesOn,
919                    (float) mTimerDuration, mShutterTouchCoordinate, mVolumeButtonClickedFlag);
920            mShutterTouchCoordinate = null;
921            mVolumeButtonClickedFlag = false;
922
923            if (!mIsImageCaptureIntent) {
924                // Calculate the width and the height of the jpeg.
925                Integer exifWidth = exif.getTagIntValue(ExifInterface.TAG_PIXEL_X_DIMENSION);
926                Integer exifHeight = exif.getTagIntValue(ExifInterface.TAG_PIXEL_Y_DIMENSION);
927                int width, height;
928                if (mShouldResizeTo16x9 && exifWidth != null && exifHeight != null) {
929                    width = exifWidth;
930                    height = exifHeight;
931                } else {
932                    Size s = new Size(mCameraSettings.getCurrentPhotoSize());
933                    if ((mJpegRotation + orientation) % 180 == 0) {
934                        width = s.width();
935                        height = s.height();
936                    } else {
937                        width = s.height();
938                        height = s.width();
939                    }
940                }
941                String title = (name == null) ? null : name.title;
942                long date = (name == null) ? -1 : name.date;
943
944                // Handle debug mode outputs
945                if (mDebugUri != null) {
946                    // If using a debug uri, save jpeg there.
947                    saveToDebugUri(jpegData);
948
949                    // Adjust the title of the debug image shown in mediastore.
950                    if (title != null) {
951                        title = DEBUG_IMAGE_PREFIX + title;
952                    }
953                }
954
955                if (title == null) {
956                    Log.e(TAG, "Unbalanced name/data pair");
957                } else {
958                    if (date == -1) {
959                        date = mCaptureStartTime;
960                    }
961                    int heading = mHeadingSensor.getCurrentHeading();
962                    if (heading != HeadingSensor.INVALID_HEADING) {
963                        // heading direction has been updated by the sensor.
964                        ExifTag directionRefTag = exif.buildTag(
965                                ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
966                                ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION);
967                        ExifTag directionTag = exif.buildTag(
968                                ExifInterface.TAG_GPS_IMG_DIRECTION,
969                                new Rational(heading, 1));
970                        exif.setTag(directionRefTag);
971                        exif.setTag(directionTag);
972                    }
973                    getServices().getMediaSaver().addImage(
974                            jpegData, title, date, mLocation, width, height,
975                            orientation, exif, mOnMediaSavedListener);
976                }
977                // Animate capture with real jpeg data instead of a preview
978                // frame.
979                mUI.animateCapture(jpegData, orientation, mMirror);
980            } else {
981                mJpegImageData = jpegData;
982                if (!mQuickCapture) {
983                    Log.v(TAG, "showing UI");
984                    mUI.showCapturedImageForReview(jpegData, orientation, mMirror);
985                } else {
986                    onCaptureDone();
987                }
988            }
989
990            // Send the taken photo to remote shutter listeners, if any are
991            // registered.
992            getServices().getRemoteShutterListener().onPictureTaken(jpegData);
993
994            // Check this in advance of each shot so we don't add to shutter
995            // latency. It's true that someone else could write to the SD card
996            // in the mean time and fill it, but that could have happened
997            // between the shutter press and saving the JPEG too.
998            mActivity.updateStorageSpaceAndHint(null);
999        }
1000    }
1001
1002    private final class AutoFocusCallback implements CameraAFCallback {
1003        @Override
1004        public void onAutoFocus(boolean focused, CameraProxy camera) {
1005            SessionStatsCollector.instance().autofocusResult(focused);
1006            if (mPaused) {
1007                return;
1008            }
1009
1010            mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
1011            Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms   focused = "+focused);
1012            setCameraState(IDLE);
1013            mFocusManager.onAutoFocus(focused, false);
1014        }
1015    }
1016
1017    private final class AutoFocusMoveCallback
1018            implements CameraAFMoveCallback {
1019        @Override
1020        public void onAutoFocusMoving(
1021                boolean moving, CameraProxy camera) {
1022            mFocusManager.onAutoFocusMoving(moving);
1023            SessionStatsCollector.instance().autofocusMoving(moving);
1024        }
1025    }
1026
1027    /**
1028     * This class is just a thread-safe queue for name,date holder objects.
1029     */
1030    public static class NamedImages {
1031        private final Vector<NamedEntity> mQueue;
1032
1033        public NamedImages() {
1034            mQueue = new Vector<NamedEntity>();
1035        }
1036
1037        public void nameNewImage(long date) {
1038            NamedEntity r = new NamedEntity();
1039            r.title = CameraUtil.instance().createJpegName(date);
1040            r.date = date;
1041            mQueue.add(r);
1042        }
1043
1044        public NamedEntity getNextNameEntity() {
1045            synchronized (mQueue) {
1046                if (!mQueue.isEmpty()) {
1047                    return mQueue.remove(0);
1048                }
1049            }
1050            return null;
1051        }
1052
1053        public static class NamedEntity {
1054            public String title;
1055            public long date;
1056        }
1057    }
1058
1059    private void setCameraState(int state) {
1060        mCameraState = state;
1061        switch (state) {
1062            case PREVIEW_STOPPED:
1063            case SNAPSHOT_IN_PROGRESS:
1064            case SWITCHING_CAMERA:
1065                // TODO: Tell app UI to disable swipe
1066                break;
1067            case PhotoController.IDLE:
1068                // TODO: Tell app UI to enable swipe
1069                break;
1070        }
1071    }
1072
1073    private void animateAfterShutter() {
1074        // Only animate when in full screen capture mode
1075        // i.e. If monkey/a user swipes to the gallery during picture taking,
1076        // don't show animation
1077        if (!mIsImageCaptureIntent) {
1078            mUI.animateFlash();
1079        }
1080    }
1081
1082    @Override
1083    public boolean capture() {
1084        Log.i(TAG, "capture");
1085        // If we are already in the middle of taking a snapshot or the image
1086        // save request is full then ignore.
1087        if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
1088                || mCameraState == SWITCHING_CAMERA) {
1089            return false;
1090        }
1091        setCameraState(SNAPSHOT_IN_PROGRESS);
1092
1093        mCaptureStartTime = System.currentTimeMillis();
1094
1095        mPostViewPictureCallbackTime = 0;
1096        mJpegImageData = null;
1097
1098        final boolean animateBefore = (mSceneMode == CameraCapabilities.SceneMode.HDR);
1099
1100        if (animateBefore) {
1101            animateAfterShutter();
1102        }
1103
1104        Location loc = mActivity.getLocationManager().getCurrentLocation();
1105        CameraUtil.setGpsParameters(mCameraSettings, loc);
1106        mCameraDevice.applySettings(mCameraSettings);
1107
1108        // Set JPEG orientation. Even if screen UI is locked in portrait, camera orientation should
1109        // still match device orientation (e.g., users should always get landscape photos while
1110        // capturing by putting device in landscape.)
1111        Characteristics info = mActivity.getCameraProvider().getCharacteristics(mCameraId);
1112        int sensorOrientation = info.getSensorOrientation();
1113        int deviceOrientation =
1114                mAppController.getOrientationManager().getDeviceOrientation().getDegrees();
1115        boolean isFrontCamera = info.isFacingFront();
1116        mJpegRotation =
1117                CameraUtil.getImageRotation(sensorOrientation, deviceOrientation, isFrontCamera);
1118        mCameraDevice.setJpegOrientation(mJpegRotation);
1119
1120        mCameraDevice.takePicture(mHandler,
1121                new ShutterCallback(!animateBefore),
1122                mRawPictureCallback, mPostViewPictureCallback,
1123                new JpegPictureCallback(loc));
1124
1125        mNamedImages.nameNewImage(mCaptureStartTime);
1126
1127        mFaceDetectionStarted = false;
1128        return true;
1129    }
1130
1131    @Override
1132    public void setFocusParameters() {
1133        setCameraParameters(UPDATE_PARAM_PREFERENCE);
1134    }
1135
1136    private void updateSceneMode() {
1137        // If scene mode is set, we cannot set flash mode, white balance, and
1138        // focus mode, instead, we read it from driver. Some devices don't have
1139        // any scene modes, so we must check both NO_SCENE_MODE in addition to
1140        // AUTO to check where there is no actual scene mode set.
1141        if (!(CameraCapabilities.SceneMode.AUTO == mSceneMode ||
1142                CameraCapabilities.SceneMode.NO_SCENE_MODE == mSceneMode)) {
1143            overrideCameraSettings(mCameraSettings.getCurrentFlashMode(),
1144                    mCameraSettings.getCurrentFocusMode());
1145        }
1146    }
1147
1148    private void overrideCameraSettings(CameraCapabilities.FlashMode flashMode,
1149            CameraCapabilities.FocusMode focusMode) {
1150        CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
1151        SettingsManager settingsManager = mActivity.getSettingsManager();
1152        if ((flashMode != null) && (!CameraCapabilities.FlashMode.NO_FLASH.equals(flashMode))) {
1153            String flashModeString = stringifier.stringify(flashMode);
1154            Log.v(TAG, "override flash setting to: " + flashModeString);
1155            settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FLASH_MODE,
1156                    flashModeString);
1157        } else {
1158            Log.v(TAG, "skip setting flash mode on override due to NO_FLASH");
1159        }
1160        if (focusMode != null) {
1161            String focusModeString = stringifier.stringify(focusMode);
1162            Log.v(TAG, "override focus setting to: " + focusModeString);
1163            settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FOCUS_MODE,
1164                    focusModeString);
1165        }
1166    }
1167
1168    @Override
1169    public void onCameraAvailable(CameraProxy cameraProxy) {
1170        Log.i(TAG, "onCameraAvailable");
1171        if (mPaused) {
1172            return;
1173        }
1174        mCameraDevice = cameraProxy;
1175
1176        initializeCapabilities();
1177
1178        // Reset zoom value index.
1179        mZoomValue = 1.0f;
1180        if (mFocusManager == null) {
1181            initializeFocusManager();
1182        }
1183        mFocusManager.updateCapabilities(mCameraCapabilities);
1184
1185        // Do camera parameter dependent initialization.
1186        mCameraSettings = mCameraDevice.getSettings();
1187        // Set a default flash mode and focus mode
1188        if (mCameraSettings.getCurrentFlashMode() == null) {
1189            mCameraSettings.setFlashMode(CameraCapabilities.FlashMode.NO_FLASH);
1190        }
1191        if (mCameraSettings.getCurrentFocusMode() == null) {
1192            mCameraSettings.setFocusMode(CameraCapabilities.FocusMode.AUTO);
1193        }
1194
1195        setCameraParameters(UPDATE_PARAM_ALL);
1196        // Set a listener which updates camera parameters based
1197        // on changed settings.
1198        SettingsManager settingsManager = mActivity.getSettingsManager();
1199        settingsManager.addListener(this);
1200        mCameraPreviewParamsReady = true;
1201
1202        startPreview();
1203
1204        onCameraOpened();
1205    }
1206
1207    @Override
1208    public void onCaptureCancelled() {
1209        mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent());
1210        mActivity.finish();
1211    }
1212
1213    @Override
1214    public void onCaptureRetake() {
1215        Log.i(TAG, "onCaptureRetake");
1216        if (mPaused) {
1217            return;
1218        }
1219        mUI.hidePostCaptureAlert();
1220        mUI.hideIntentReviewImageView();
1221        setupPreview();
1222    }
1223
1224    @Override
1225    public void onCaptureDone() {
1226        Log.i(TAG, "onCaptureDone");
1227        if (mPaused) {
1228            return;
1229        }
1230
1231        byte[] data = mJpegImageData;
1232
1233        if (mCropValue == null) {
1234            // First handle the no crop case -- just return the value. If the
1235            // caller specifies a "save uri" then write the data to its
1236            // stream. Otherwise, pass back a scaled down version of the bitmap
1237            // directly in the extras.
1238            if (mSaveUri != null) {
1239                OutputStream outputStream = null;
1240                try {
1241                    outputStream = mContentResolver.openOutputStream(mSaveUri);
1242                    outputStream.write(data);
1243                    outputStream.close();
1244
1245                    Log.v(TAG, "saved result to URI: " + mSaveUri);
1246                    mActivity.setResultEx(Activity.RESULT_OK);
1247                    mActivity.finish();
1248                } catch (IOException ex) {
1249                    Log.w(TAG, "exception saving result to URI: " + mSaveUri, ex);
1250                    // ignore exception
1251                } finally {
1252                    CameraUtil.closeSilently(outputStream);
1253                }
1254            } else {
1255                ExifInterface exif = Exif.getExif(data);
1256                int orientation = Exif.getOrientation(exif);
1257                Bitmap bitmap = CameraUtil.makeBitmap(data, 50 * 1024);
1258                bitmap = CameraUtil.rotate(bitmap, orientation);
1259                Log.v(TAG, "inlined bitmap into capture intent result");
1260                mActivity.setResultEx(Activity.RESULT_OK,
1261                        new Intent("inline-data").putExtra("data", bitmap));
1262                mActivity.finish();
1263            }
1264        } else {
1265            // Save the image to a temp file and invoke the cropper
1266            Uri tempUri = null;
1267            FileOutputStream tempStream = null;
1268            try {
1269                File path = mActivity.getFileStreamPath(sTempCropFilename);
1270                path.delete();
1271                tempStream = mActivity.openFileOutput(sTempCropFilename, 0);
1272                tempStream.write(data);
1273                tempStream.close();
1274                tempUri = Uri.fromFile(path);
1275                Log.v(TAG, "wrote temp file for cropping to: " + sTempCropFilename);
1276            } catch (FileNotFoundException ex) {
1277                Log.w(TAG, "error writing temp cropping file to: " + sTempCropFilename, ex);
1278                mActivity.setResultEx(Activity.RESULT_CANCELED);
1279                mActivity.finish();
1280                return;
1281            } catch (IOException ex) {
1282                Log.w(TAG, "error writing temp cropping file to: " + sTempCropFilename, ex);
1283                mActivity.setResultEx(Activity.RESULT_CANCELED);
1284                mActivity.finish();
1285                return;
1286            } finally {
1287                CameraUtil.closeSilently(tempStream);
1288            }
1289
1290            Bundle newExtras = new Bundle();
1291            if (mCropValue.equals("circle")) {
1292                newExtras.putString("circleCrop", "true");
1293            }
1294            if (mSaveUri != null) {
1295                Log.v(TAG, "setting output of cropped file to: " + mSaveUri);
1296                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
1297            } else {
1298                newExtras.putBoolean(CameraUtil.KEY_RETURN_DATA, true);
1299            }
1300            if (mActivity.isSecureCamera()) {
1301                newExtras.putBoolean(CameraUtil.KEY_SHOW_WHEN_LOCKED, true);
1302            }
1303
1304            // TODO: Share this constant.
1305            final String CROP_ACTION = "com.android.camera.action.CROP";
1306            Intent cropIntent = new Intent(CROP_ACTION);
1307
1308            cropIntent.setData(tempUri);
1309            cropIntent.putExtras(newExtras);
1310            Log.v(TAG, "starting CROP intent for capture");
1311            mActivity.startActivityForResult(cropIntent, REQUEST_CROP);
1312        }
1313    }
1314
1315    @Override
1316    public void onShutterCoordinate(TouchCoordinate coord) {
1317        mShutterTouchCoordinate = coord;
1318    }
1319
1320    @Override
1321    public void onShutterButtonFocus(boolean pressed) {
1322        // Do nothing. We don't support half-press to focus anymore.
1323    }
1324
1325    @Override
1326    public void onShutterButtonClick() {
1327        if (mPaused || (mCameraState == SWITCHING_CAMERA)
1328                || (mCameraState == PREVIEW_STOPPED)
1329                || !mAppController.isShutterEnabled()) {
1330            mVolumeButtonClickedFlag = false;
1331            return;
1332        }
1333
1334        // Do not take the picture if there is not enough storage.
1335        if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1336            Log.i(TAG, "Not enough space or storage not ready. remaining="
1337                    + mActivity.getStorageSpaceBytes());
1338            mVolumeButtonClickedFlag = false;
1339            return;
1340        }
1341        Log.d(TAG, "onShutterButtonClick: mCameraState=" + mCameraState +
1342                " mVolumeButtonClickedFlag=" + mVolumeButtonClickedFlag);
1343
1344        mAppController.setShutterEnabled(false);
1345
1346        int countDownDuration = mActivity.getSettingsManager()
1347            .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);
1348        mTimerDuration = countDownDuration;
1349        if (countDownDuration > 0) {
1350            // Start count down.
1351            mAppController.getCameraAppUI().transitionToCancel();
1352            mAppController.getCameraAppUI().hideModeOptions();
1353            mUI.startCountdown(countDownDuration);
1354            return;
1355        } else {
1356            focusAndCapture();
1357        }
1358    }
1359
1360    private void focusAndCapture() {
1361        if (mSceneMode == CameraCapabilities.SceneMode.HDR) {
1362            mUI.setSwipingEnabled(false);
1363        }
1364        // If the user wants to do a snapshot while the previous one is still
1365        // in progress, remember the fact and do it after we finish the previous
1366        // one and re-start the preview. Snapshot in progress also includes the
1367        // state that autofocus is focusing and a picture will be taken when
1368        // focus callback arrives.
1369        if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)) {
1370            if (!mIsImageCaptureIntent) {
1371                mSnapshotOnIdle = true;
1372            }
1373            return;
1374        }
1375
1376        mSnapshotOnIdle = false;
1377        mFocusManager.focusAndCapture(mCameraSettings.getCurrentFocusMode());
1378    }
1379
1380    @Override
1381    public void onRemainingSecondsChanged(int remainingSeconds) {
1382        if (remainingSeconds == 1) {
1383            mCountdownSoundPlayer.play(R.raw.timer_final_second, 0.6f);
1384        } else if (remainingSeconds == 2 || remainingSeconds == 3) {
1385            mCountdownSoundPlayer.play(R.raw.timer_increment, 0.6f);
1386        }
1387    }
1388
1389    @Override
1390    public void onCountDownFinished() {
1391        mAppController.getCameraAppUI().transitionToCapture();
1392        mAppController.getCameraAppUI().showModeOptions();
1393        if (mPaused) {
1394            return;
1395        }
1396        focusAndCapture();
1397    }
1398
1399    @Override
1400    public void resume() {
1401        mPaused = false;
1402
1403        mCountdownSoundPlayer.loadSound(R.raw.timer_final_second);
1404        mCountdownSoundPlayer.loadSound(R.raw.timer_increment);
1405        if (mFocusManager != null) {
1406            // If camera is not open when resume is called, focus manager will
1407            // not be initialized yet, in which case it will start listening to
1408            // preview area size change later in the initialization.
1409            mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1410        }
1411        mAppController.addPreviewAreaSizeChangedListener(mUI);
1412        mAppController.getCameraAppUI().showAccessibilityZoomUI(
1413                mCameraCapabilities.getMaxZoomRatio());
1414
1415        CameraProvider camProvider = mActivity.getCameraProvider();
1416        if (camProvider == null) {
1417            // No camera provider, the Activity is destroyed already.
1418            return;
1419        }
1420        requestCameraOpen();
1421
1422        mJpegPictureCallbackTime = 0;
1423        mZoomValue = 1.0f;
1424
1425        mOnResumeTime = SystemClock.uptimeMillis();
1426        checkDisplayRotation();
1427
1428        // If first time initialization is not finished, put it in the
1429        // message queue.
1430        if (!mFirstTimeInitialized) {
1431            mHandler.sendEmptyMessage(MSG_FIRST_TIME_INIT);
1432        } else {
1433            initializeSecondTime();
1434        }
1435
1436        mHeadingSensor.activate();
1437
1438        getServices().getRemoteShutterListener().onModuleReady(this);
1439        SessionStatsCollector.instance().sessionActive(true);
1440    }
1441
1442    /**
1443     * @return Whether the currently active camera is front-facing.
1444     */
1445    private boolean isCameraFrontFacing() {
1446        return mAppController.getCameraProvider().getCharacteristics(mCameraId)
1447                .isFacingFront();
1448    }
1449
1450    /**
1451     * The focus manager is the first UI related element to get initialized, and
1452     * it requires the RenderOverlay, so initialize it here
1453     */
1454    private void initializeFocusManager() {
1455        // Create FocusManager object. startPreview needs it.
1456        // if mFocusManager not null, reuse it
1457        // otherwise create a new instance
1458        if (mFocusManager != null) {
1459            mFocusManager.removeMessages();
1460        } else {
1461            mMirror = isCameraFrontFacing();
1462            String[] defaultFocusModesStrings = mActivity.getResources().getStringArray(
1463                    R.array.pref_camera_focusmode_default_array);
1464            ArrayList<CameraCapabilities.FocusMode> defaultFocusModes =
1465                    new ArrayList<CameraCapabilities.FocusMode>();
1466            CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
1467            for (String modeString : defaultFocusModesStrings) {
1468                CameraCapabilities.FocusMode mode = stringifier.focusModeFromString(modeString);
1469                if (mode != null) {
1470                    defaultFocusModes.add(mode);
1471                }
1472            }
1473            mFocusManager =
1474                    new FocusOverlayManager(mAppController, defaultFocusModes,
1475                            mCameraCapabilities, this, mMirror, mActivity.getMainLooper(),
1476                            mUI.getFocusRing());
1477            mMotionManager = getServices().getMotionManager();
1478            if (mMotionManager != null) {
1479                mMotionManager.addListener(mFocusManager);
1480            }
1481        }
1482        mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1483    }
1484
1485    /**
1486     * @return Whether we are resuming from within the lockscreen.
1487     */
1488    private boolean isResumeFromLockscreen() {
1489        String action = mActivity.getIntent().getAction();
1490        return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action)
1491                || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action));
1492    }
1493
1494    @Override
1495    public void pause() {
1496        Log.v(TAG, "pause");
1497        mPaused = true;
1498        getServices().getRemoteShutterListener().onModuleExit();
1499        SessionStatsCollector.instance().sessionActive(false);
1500
1501        mHeadingSensor.deactivate();
1502
1503        // Reset the focus first. Camera CTS does not guarantee that
1504        // cancelAutoFocus is allowed after preview stops.
1505        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1506            mCameraDevice.cancelAutoFocus();
1507        }
1508
1509        // If the camera has not been opened asynchronously yet,
1510        // and startPreview hasn't been called, then this is a no-op.
1511        // (e.g. onResume -> onPause -> onResume).
1512        stopPreview();
1513        cancelCountDown();
1514        mCountdownSoundPlayer.unloadSound(R.raw.timer_final_second);
1515        mCountdownSoundPlayer.unloadSound(R.raw.timer_increment);
1516
1517        mNamedImages = null;
1518        // If we are in an image capture intent and has taken
1519        // a picture, we just clear it in onPause.
1520        mJpegImageData = null;
1521
1522        // Remove the messages and runnables in the queue.
1523        mHandler.removeCallbacksAndMessages(null);
1524
1525        if (mMotionManager != null) {
1526            mMotionManager.removeListener(mFocusManager);
1527            mMotionManager = null;
1528        }
1529
1530        closeCamera();
1531        mActivity.enableKeepScreenOn(false);
1532        mUI.onPause();
1533
1534        mPendingSwitchCameraId = -1;
1535        if (mFocusManager != null) {
1536            mFocusManager.removeMessages();
1537        }
1538        getServices().getMemoryManager().removeListener(this);
1539        mAppController.removePreviewAreaSizeChangedListener(mFocusManager);
1540        mAppController.removePreviewAreaSizeChangedListener(mUI);
1541
1542        SettingsManager settingsManager = mActivity.getSettingsManager();
1543        settingsManager.removeListener(this);
1544    }
1545
1546    @Override
1547    public void destroy() {
1548        mCountdownSoundPlayer.release();
1549    }
1550
1551    @Override
1552    public void onLayoutOrientationChanged(boolean isLandscape) {
1553        setDisplayOrientation();
1554    }
1555
1556    @Override
1557    public void updateCameraOrientation() {
1558        if (mDisplayRotation != CameraUtil.getDisplayRotation()) {
1559            setDisplayOrientation();
1560        }
1561    }
1562
1563    private boolean canTakePicture() {
1564        return isCameraIdle()
1565                && (mActivity.getStorageSpaceBytes() > Storage.LOW_STORAGE_THRESHOLD_BYTES);
1566    }
1567
1568    @Override
1569    public void autoFocus() {
1570        if (mCameraDevice == null) {
1571            return;
1572        }
1573        Log.v(TAG,"Starting auto focus");
1574        mFocusStartTime = System.currentTimeMillis();
1575        mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
1576        SessionStatsCollector.instance().autofocusManualTrigger();
1577        setCameraState(FOCUSING);
1578    }
1579
1580    @Override
1581    public void cancelAutoFocus() {
1582        if (mCameraDevice == null) {
1583            return;
1584        }
1585        mCameraDevice.cancelAutoFocus();
1586        setCameraState(IDLE);
1587        setCameraParameters(UPDATE_PARAM_PREFERENCE);
1588    }
1589
1590    @Override
1591    public void onSingleTapUp(View view, int x, int y) {
1592        if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
1593                || mCameraState == SNAPSHOT_IN_PROGRESS
1594                || mCameraState == SWITCHING_CAMERA
1595                || mCameraState == PREVIEW_STOPPED) {
1596            return;
1597        }
1598
1599        // Check if metering area or focus area is supported.
1600        if (!mFocusAreaSupported && !mMeteringAreaSupported) {
1601            return;
1602        }
1603        mFocusManager.onSingleTapUp(x, y);
1604    }
1605
1606    @Override
1607    public boolean onBackPressed() {
1608        return mUI.onBackPressed();
1609    }
1610
1611    @Override
1612    public boolean onKeyDown(int keyCode, KeyEvent event) {
1613        switch (keyCode) {
1614            case KeyEvent.KEYCODE_VOLUME_UP:
1615            case KeyEvent.KEYCODE_VOLUME_DOWN:
1616            case KeyEvent.KEYCODE_FOCUS:
1617                if (/* TODO: mActivity.isInCameraApp() && */mFirstTimeInitialized &&
1618                    !mActivity.getCameraAppUI().isInIntentReview()) {
1619                    if (event.getRepeatCount() == 0) {
1620                        onShutterButtonFocus(true);
1621                    }
1622                    return true;
1623                }
1624                return false;
1625            case KeyEvent.KEYCODE_CAMERA:
1626                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1627                    onShutterButtonClick();
1628                }
1629                return true;
1630            case KeyEvent.KEYCODE_DPAD_CENTER:
1631                // If we get a dpad center event without any focused view, move
1632                // the focus to the shutter button and press it.
1633                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1634                    // Start auto-focus immediately to reduce shutter lag. After
1635                    // the shutter button gets the focus, onShutterButtonFocus()
1636                    // will be called again but it is fine.
1637                    onShutterButtonFocus(true);
1638                }
1639                return true;
1640        }
1641        return false;
1642    }
1643
1644    @Override
1645    public boolean onKeyUp(int keyCode, KeyEvent event) {
1646        switch (keyCode) {
1647            case KeyEvent.KEYCODE_VOLUME_UP:
1648            case KeyEvent.KEYCODE_VOLUME_DOWN:
1649                if (/* mActivity.isInCameraApp() && */mFirstTimeInitialized &&
1650                    !mActivity.getCameraAppUI().isInIntentReview()) {
1651                    if (mUI.isCountingDown()) {
1652                        cancelCountDown();
1653                    } else {
1654                        mVolumeButtonClickedFlag = true;
1655                        onShutterButtonClick();
1656                    }
1657                    return true;
1658                }
1659                return false;
1660            case KeyEvent.KEYCODE_FOCUS:
1661                if (mFirstTimeInitialized) {
1662                    onShutterButtonFocus(false);
1663                }
1664                return true;
1665        }
1666        return false;
1667    }
1668
1669    private void closeCamera() {
1670        if (mCameraDevice != null) {
1671            stopFaceDetection();
1672            mCameraDevice.setZoomChangeListener(null);
1673            mCameraDevice.setFaceDetectionCallback(null, null);
1674
1675            mFaceDetectionStarted = false;
1676            mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
1677            mCameraDevice = null;
1678            setCameraState(PREVIEW_STOPPED);
1679            mFocusManager.onCameraReleased();
1680        }
1681    }
1682
1683    private void setDisplayOrientation() {
1684        mDisplayRotation = CameraUtil.getDisplayRotation();
1685        Characteristics info =
1686                mActivity.getCameraProvider().getCharacteristics(mCameraId);
1687        mDisplayOrientation = info.getPreviewOrientation(mDisplayRotation);
1688        mUI.setDisplayOrientation(mDisplayOrientation);
1689        if (mFocusManager != null) {
1690            mFocusManager.setDisplayOrientation(mDisplayOrientation);
1691        }
1692        // Change the camera display orientation
1693        if (mCameraDevice != null) {
1694            mCameraDevice.setDisplayOrientation(mDisplayRotation);
1695        }
1696        Log.v(TAG, "setDisplayOrientation (screen:preview) " +
1697                mDisplayRotation + ":" + mDisplayOrientation);
1698    }
1699
1700    /** Only called by UI thread. */
1701    private void setupPreview() {
1702        Log.i(TAG, "setupPreview");
1703        mFocusManager.resetTouchFocus();
1704        startPreview();
1705    }
1706
1707    /**
1708     * Returns whether we can/should start the preview or not.
1709     */
1710    private boolean checkPreviewPreconditions() {
1711        if (mPaused) {
1712            return false;
1713        }
1714
1715        if (mCameraDevice == null) {
1716            Log.w(TAG, "startPreview: camera device not ready yet.");
1717            return false;
1718        }
1719
1720        SurfaceTexture st = mActivity.getCameraAppUI().getSurfaceTexture();
1721        if (st == null) {
1722            Log.w(TAG, "startPreview: surfaceTexture is not ready.");
1723            return false;
1724        }
1725
1726        if (!mCameraPreviewParamsReady) {
1727            Log.w(TAG, "startPreview: parameters for preview is not ready.");
1728            return false;
1729        }
1730        return true;
1731    }
1732
1733    /**
1734     * The start/stop preview should only run on the UI thread.
1735     */
1736    private void startPreview() {
1737        if (mCameraDevice == null) {
1738            Log.i(TAG, "attempted to start preview before camera device");
1739            // do nothing
1740            return;
1741        }
1742
1743        if (!checkPreviewPreconditions()) {
1744            return;
1745        }
1746
1747        setDisplayOrientation();
1748
1749        if (!mSnapshotOnIdle) {
1750            // If the focus mode is continuous autofocus, call cancelAutoFocus
1751            // to resume it because it may have been paused by autoFocus call.
1752            if (mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) ==
1753                    CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
1754                mCameraDevice.cancelAutoFocus();
1755            }
1756            mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
1757        }
1758
1759        // Nexus 4 must have picture size set to > 640x480 before other
1760        // parameters are set in setCameraParameters, b/18227551. This call to
1761        // updateParametersPictureSize should occur before setCameraParameters
1762        // to address the issue.
1763        updateParametersPictureSize();
1764
1765        setCameraParameters(UPDATE_PARAM_ALL);
1766
1767        mCameraDevice.setPreviewTexture(mActivity.getCameraAppUI().getSurfaceTexture());
1768
1769        Log.i(TAG, "startPreview");
1770        // If we're using API2 in portability layers, don't use startPreviewWithCallback()
1771        // b/17576554
1772        CameraAgent.CameraStartPreviewCallback startPreviewCallback =
1773            new CameraAgent.CameraStartPreviewCallback() {
1774                @Override
1775                public void onPreviewStarted() {
1776                    mFocusManager.onPreviewStarted();
1777                    PhotoModule.this.onPreviewStarted();
1778                    SessionStatsCollector.instance().previewActive(true);
1779                    if (mSnapshotOnIdle) {
1780                        mHandler.post(mDoSnapRunnable);
1781                    }
1782                }
1783            };
1784        if (GservicesHelper.useCamera2ApiThroughPortabilityLayer(mActivity.getContentResolver())) {
1785            mCameraDevice.startPreview();
1786            startPreviewCallback.onPreviewStarted();
1787        } else {
1788            mCameraDevice.startPreviewWithCallback(new Handler(Looper.getMainLooper()),
1789                    startPreviewCallback);
1790        }
1791    }
1792
1793    @Override
1794    public void stopPreview() {
1795        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1796            Log.i(TAG, "stopPreview");
1797            mCameraDevice.stopPreview();
1798            mFaceDetectionStarted = false;
1799        }
1800        setCameraState(PREVIEW_STOPPED);
1801        if (mFocusManager != null) {
1802            mFocusManager.onPreviewStopped();
1803        }
1804        SessionStatsCollector.instance().previewActive(false);
1805    }
1806
1807    @Override
1808    public void onSettingChanged(SettingsManager settingsManager, String key) {
1809        if (key.equals(Keys.KEY_FLASH_MODE)) {
1810            updateParametersFlashMode();
1811        }
1812        if (key.equals(Keys.KEY_CAMERA_HDR)) {
1813            if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1814                                           Keys.KEY_CAMERA_HDR)) {
1815                // HDR is on.
1816                mAppController.getButtonManager().disableButton(ButtonManager.BUTTON_FLASH);
1817                mFlashModeBeforeSceneMode = settingsManager.getString(
1818                        mAppController.getCameraScope(), Keys.KEY_FLASH_MODE);
1819            } else {
1820                if (mFlashModeBeforeSceneMode != null) {
1821                    settingsManager.set(mAppController.getCameraScope(),
1822                                        Keys.KEY_FLASH_MODE,
1823                                        mFlashModeBeforeSceneMode);
1824                    updateParametersFlashMode();
1825                    mFlashModeBeforeSceneMode = null;
1826                }
1827                mAppController.getButtonManager().enableButton(ButtonManager.BUTTON_FLASH);
1828            }
1829        }
1830
1831        if (mCameraDevice != null) {
1832            mCameraDevice.applySettings(mCameraSettings);
1833        }
1834    }
1835
1836    private void updateCameraParametersInitialize() {
1837        // Reset preview frame rate to the maximum because it may be lowered by
1838        // video camera application.
1839        int[] fpsRange = CameraUtil.getPhotoPreviewFpsRange(mCameraCapabilities);
1840        if (fpsRange != null && fpsRange.length > 0) {
1841            mCameraSettings.setPreviewFpsRange(fpsRange[0], fpsRange[1]);
1842        }
1843
1844        mCameraSettings.setRecordingHintEnabled(false);
1845
1846        if (mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_STABILIZATION)) {
1847            mCameraSettings.setVideoStabilization(false);
1848        }
1849    }
1850
1851    private void updateCameraParametersZoom() {
1852        // Set zoom.
1853        if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
1854            mCameraSettings.setZoomRatio(mZoomValue);
1855        }
1856    }
1857
1858    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1859    private void setAutoExposureLockIfSupported() {
1860        if (mAeLockSupported) {
1861            mCameraSettings.setAutoExposureLock(mFocusManager.getAeAwbLock());
1862        }
1863    }
1864
1865    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1866    private void setAutoWhiteBalanceLockIfSupported() {
1867        if (mAwbLockSupported) {
1868            mCameraSettings.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
1869        }
1870    }
1871
1872    private void setFocusAreasIfSupported() {
1873        if (mFocusAreaSupported) {
1874            mCameraSettings.setFocusAreas(mFocusManager.getFocusAreas());
1875        }
1876    }
1877
1878    private void setMeteringAreasIfSupported() {
1879        if (mMeteringAreaSupported) {
1880            mCameraSettings.setMeteringAreas(mFocusManager.getMeteringAreas());
1881        }
1882    }
1883
1884    private void updateCameraParametersPreference() {
1885        // some monkey tests can get here when shutting the app down
1886        // make sure mCameraDevice is still valid, b/17580046
1887        if (mCameraDevice == null) {
1888            return;
1889        }
1890
1891        setAutoExposureLockIfSupported();
1892        setAutoWhiteBalanceLockIfSupported();
1893        setFocusAreasIfSupported();
1894        setMeteringAreasIfSupported();
1895
1896        // Initialize focus mode.
1897        mFocusManager.overrideFocusMode(null);
1898        mCameraSettings
1899                .setFocusMode(mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
1900        SessionStatsCollector.instance().autofocusActive(
1901                mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) ==
1902                        CameraCapabilities.FocusMode.CONTINUOUS_PICTURE
1903        );
1904
1905        // Set JPEG quality.
1906        updateParametersPictureQuality();
1907
1908        // For the following settings, we need to check if the settings are
1909        // still supported by latest driver, if not, ignore the settings.
1910
1911        // Set exposure compensation
1912        updateParametersExposureCompensation();
1913
1914        // Set the scene mode: also sets flash and white balance.
1915        updateParametersSceneMode();
1916
1917        if (mContinuousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
1918            updateAutoFocusMoveCallback();
1919        }
1920    }
1921
1922    /**
1923     * This method sets picture size parameters. Size parameters should only be
1924     * set when the preview is stopped, and so this method is only invoked in
1925     * {@link #startPreview()} just before starting the preview.
1926     */
1927    private void updateParametersPictureSize() {
1928        if (mCameraDevice == null) {
1929            Log.w(TAG, "attempting to set picture size without caemra device");
1930            return;
1931        }
1932
1933        List<Size> supported = Size.convert(mCameraCapabilities.getSupportedPhotoSizes());
1934        CameraPictureSizesCacher.updateSizesForCamera(mAppController.getAndroidContext(),
1935                mCameraDevice.getCameraId(), supported);
1936
1937        OneCamera.Facing cameraFacing =
1938                isCameraFrontFacing() ? OneCamera.Facing.FRONT : OneCamera.Facing.BACK;
1939        Size pictureSize;
1940        try {
1941            pictureSize = mAppController.getResolutionSetting().getPictureSize(cameraFacing);
1942        } catch (OneCameraAccessException ex) {
1943            mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
1944            return;
1945        }
1946
1947        mCameraSettings.setPhotoSize(pictureSize.toPortabilitySize());
1948
1949        if (ApiHelper.IS_NEXUS_5) {
1950            if (ResolutionUtil.NEXUS_5_LARGE_16_BY_9.equals(pictureSize)) {
1951                mShouldResizeTo16x9 = true;
1952            } else {
1953                mShouldResizeTo16x9 = false;
1954            }
1955        }
1956
1957        // Set a preview size that is closest to the viewfinder height and has
1958        // the right aspect ratio.
1959        List<Size> sizes = Size.convert(mCameraCapabilities.getSupportedPreviewSizes());
1960        Size optimalSize = CameraUtil.getOptimalPreviewSize(sizes,
1961                (double) pictureSize.width() / pictureSize.height());
1962        Size original = new Size(mCameraSettings.getCurrentPreviewSize());
1963        if (!optimalSize.equals(original)) {
1964            Log.v(TAG, "setting preview size. optimal: " + optimalSize + "original: " + original);
1965            mCameraSettings.setPreviewSize(optimalSize.toPortabilitySize());
1966
1967            mCameraDevice.applySettings(mCameraSettings);
1968            mCameraSettings = mCameraDevice.getSettings();
1969        }
1970
1971        if (optimalSize.width() != 0 && optimalSize.height() != 0) {
1972            Log.v(TAG, "updating aspect ratio");
1973            mUI.updatePreviewAspectRatio((float) optimalSize.width()
1974                    / (float) optimalSize.height());
1975        }
1976        Log.d(TAG, "Preview size is " + optimalSize);
1977    }
1978
1979    private void updateParametersPictureQuality() {
1980        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1981                CameraProfile.QUALITY_HIGH);
1982        mCameraSettings.setPhotoJpegCompressionQuality(jpegQuality);
1983    }
1984
1985    private void updateParametersExposureCompensation() {
1986        SettingsManager settingsManager = mActivity.getSettingsManager();
1987        if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1988                                       Keys.KEY_EXPOSURE_COMPENSATION_ENABLED)) {
1989            int value = settingsManager.getInteger(mAppController.getCameraScope(),
1990                                                   Keys.KEY_EXPOSURE);
1991            int max = mCameraCapabilities.getMaxExposureCompensation();
1992            int min = mCameraCapabilities.getMinExposureCompensation();
1993            if (value >= min && value <= max) {
1994                mCameraSettings.setExposureCompensationIndex(value);
1995            } else {
1996                Log.w(TAG, "invalid exposure range: " + value);
1997            }
1998        } else {
1999            // If exposure compensation is not enabled, reset the exposure compensation value.
2000            setExposureCompensation(0);
2001        }
2002    }
2003
2004    private void updateParametersSceneMode() {
2005        CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
2006        SettingsManager settingsManager = mActivity.getSettingsManager();
2007
2008        mSceneMode = stringifier.
2009            sceneModeFromString(settingsManager.getString(mAppController.getCameraScope(),
2010                                                          Keys.KEY_SCENE_MODE));
2011        if (mCameraCapabilities.supports(mSceneMode)) {
2012            if (mCameraSettings.getCurrentSceneMode() != mSceneMode) {
2013                mCameraSettings.setSceneMode(mSceneMode);
2014
2015                // Setting scene mode will change the settings of flash mode,
2016                // white balance, and focus mode. Here we read back the
2017                // parameters, so we can know those settings.
2018                mCameraDevice.applySettings(mCameraSettings);
2019                mCameraSettings = mCameraDevice.getSettings();
2020            }
2021        } else {
2022            mSceneMode = mCameraSettings.getCurrentSceneMode();
2023            if (mSceneMode == null) {
2024                mSceneMode = CameraCapabilities.SceneMode.AUTO;
2025            }
2026        }
2027
2028        if (CameraCapabilities.SceneMode.AUTO == mSceneMode) {
2029            // Set flash mode.
2030            updateParametersFlashMode();
2031
2032            // Set focus mode.
2033            mFocusManager.overrideFocusMode(null);
2034            mCameraSettings.setFocusMode(
2035                    mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
2036        } else {
2037            mFocusManager.overrideFocusMode(mCameraSettings.getCurrentFocusMode());
2038        }
2039    }
2040
2041    private void updateParametersFlashMode() {
2042        SettingsManager settingsManager = mActivity.getSettingsManager();
2043
2044        CameraCapabilities.FlashMode flashMode = mCameraCapabilities.getStringifier()
2045            .flashModeFromString(settingsManager.getString(mAppController.getCameraScope(),
2046                                                           Keys.KEY_FLASH_MODE));
2047        if (mCameraCapabilities.supports(flashMode)) {
2048            mCameraSettings.setFlashMode(flashMode);
2049        }
2050    }
2051
2052    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
2053    private void updateAutoFocusMoveCallback() {
2054        if (mCameraDevice == null) {
2055            return;
2056        }
2057        if (mCameraSettings.getCurrentFocusMode() ==
2058                CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
2059            mCameraDevice.setAutoFocusMoveCallback(mHandler,
2060                    (CameraAFMoveCallback) mAutoFocusMoveCallback);
2061        } else {
2062            mCameraDevice.setAutoFocusMoveCallback(null, null);
2063        }
2064    }
2065
2066    /**
2067     * Sets the exposure compensation to the given value and also updates settings.
2068     *
2069     * @param value exposure compensation value to be set
2070     */
2071    public void setExposureCompensation(int value) {
2072        int max = mCameraCapabilities.getMaxExposureCompensation();
2073        int min = mCameraCapabilities.getMinExposureCompensation();
2074        if (value >= min && value <= max) {
2075            mCameraSettings.setExposureCompensationIndex(value);
2076            SettingsManager settingsManager = mActivity.getSettingsManager();
2077            settingsManager.set(mAppController.getCameraScope(),
2078                                Keys.KEY_EXPOSURE, value);
2079        } else {
2080            Log.w(TAG, "invalid exposure range: " + value);
2081        }
2082    }
2083
2084    // We separate the parameters into several subsets, so we can update only
2085    // the subsets actually need updating. The PREFERENCE set needs extra
2086    // locking because the preference can be changed from GLThread as well.
2087    private void setCameraParameters(int updateSet) {
2088        if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
2089            updateCameraParametersInitialize();
2090        }
2091
2092        if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
2093            updateCameraParametersZoom();
2094        }
2095
2096        if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
2097            updateCameraParametersPreference();
2098        }
2099
2100        if (mCameraDevice != null) {
2101            mCameraDevice.applySettings(mCameraSettings);
2102        }
2103    }
2104
2105    // If the Camera is idle, update the parameters immediately, otherwise
2106    // accumulate them in mUpdateSet and update later.
2107    private void setCameraParametersWhenIdle(int additionalUpdateSet) {
2108        mUpdateSet |= additionalUpdateSet;
2109        if (mCameraDevice == null) {
2110            // We will update all the parameters when we open the device, so
2111            // we don't need to do anything now.
2112            mUpdateSet = 0;
2113            return;
2114        } else if (isCameraIdle()) {
2115            setCameraParameters(mUpdateSet);
2116            updateSceneMode();
2117            mUpdateSet = 0;
2118        } else {
2119            if (!mHandler.hasMessages(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
2120                mHandler.sendEmptyMessageDelayed(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
2121            }
2122        }
2123    }
2124
2125    @Override
2126    public boolean isCameraIdle() {
2127        return (mCameraState == IDLE) ||
2128                (mCameraState == PREVIEW_STOPPED) ||
2129                ((mFocusManager != null) && mFocusManager.isFocusCompleted()
2130                && (mCameraState != SWITCHING_CAMERA));
2131    }
2132
2133    @Override
2134    public boolean isImageCaptureIntent() {
2135        String action = mActivity.getIntent().getAction();
2136        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
2137        || CameraActivity.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
2138    }
2139
2140    private void setupCaptureParams() {
2141        Bundle myExtras = mActivity.getIntent().getExtras();
2142        if (myExtras != null) {
2143            mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
2144            mCropValue = myExtras.getString("crop");
2145        }
2146    }
2147
2148    private void initializeCapabilities() {
2149        mCameraCapabilities = mCameraDevice.getCapabilities();
2150        mFocusAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA);
2151        mMeteringAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.METERING_AREA);
2152        mAeLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_EXPOSURE_LOCK);
2153        mAwbLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_WHITE_BALANCE_LOCK);
2154        mContinuousFocusSupported =
2155                mCameraCapabilities.supports(CameraCapabilities.FocusMode.CONTINUOUS_PICTURE);
2156    }
2157
2158    @Override
2159    public void onZoomChanged(float ratio) {
2160        // Not useful to change zoom value when the activity is paused.
2161        if (mPaused) {
2162            return;
2163        }
2164        mZoomValue = ratio;
2165        if (mCameraSettings == null || mCameraDevice == null) {
2166            return;
2167        }
2168        // Set zoom parameters asynchronously
2169        mCameraSettings.setZoomRatio(mZoomValue);
2170        mCameraDevice.applySettings(mCameraSettings);
2171    }
2172
2173    @Override
2174    public int getCameraState() {
2175        return mCameraState;
2176    }
2177
2178    @Override
2179    public void onMemoryStateChanged(int state) {
2180        mAppController.setShutterEnabled(state == MemoryManager.STATE_OK);
2181    }
2182
2183    @Override
2184    public void onLowMemory() {
2185        // Not much we can do in the photo module.
2186    }
2187
2188    // For debugging only.
2189    public void setDebugUri(Uri uri) {
2190        mDebugUri = uri;
2191    }
2192
2193    // For debugging only.
2194    private void saveToDebugUri(byte[] data) {
2195        if (mDebugUri != null) {
2196            OutputStream outputStream = null;
2197            try {
2198                outputStream = mContentResolver.openOutputStream(mDebugUri);
2199                outputStream.write(data);
2200                outputStream.close();
2201            } catch (IOException e) {
2202                Log.e(TAG, "Exception while writing debug jpeg file", e);
2203            } finally {
2204                CameraUtil.closeSilently(outputStream);
2205            }
2206        }
2207    }
2208
2209    @Override
2210    public void onRemoteShutterPress() {
2211        mHandler.post(new Runnable() {
2212            @Override
2213            public void run() {
2214                focusAndCapture();
2215            }
2216        });
2217    }
2218}
2219