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