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