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