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