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