OSDN Git Service

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