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