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