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