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