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