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