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