OSDN Git Service

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