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