OSDN Git Service

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