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