OSDN Git Service

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