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