2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.camera;
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;
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.SessionStatsCollector;
76 import com.android.camera.util.UsageStatistics;
77 import com.android.camera.widget.AspectRatioSelector;
78 import com.android.camera2.R;
79 import com.android.ex.camera2.portability.CameraCapabilities;
80 import com.android.ex.camera2.portability.CameraAgent.CameraAFCallback;
81 import com.android.ex.camera2.portability.CameraAgent.CameraAFMoveCallback;
82 import com.android.ex.camera2.portability.CameraAgent.CameraPictureCallback;
83 import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
84 import com.android.ex.camera2.portability.CameraAgent.CameraShutterCallback;
85 import com.android.ex.camera2.portability.CameraDeviceInfo.Characteristics;
86 import com.android.ex.camera2.portability.CameraSettings;
87 import com.android.ex.camera2.portability.Size;
88 import com.google.common.logging.eventprotos;
90 import java.io.ByteArrayOutputStream;
92 import java.io.FileNotFoundException;
93 import java.io.FileOutputStream;
94 import java.io.IOException;
95 import java.io.OutputStream;
96 import java.lang.ref.WeakReference;
97 import java.util.ArrayList;
98 import java.util.List;
99 import java.util.Vector;
101 public class PhotoModule
103 implements PhotoController,
106 FocusOverlayManager.Listener,
108 SettingsManager.OnSettingChangedListener,
110 CountDownView.OnCountDownStatusListener {
112 private static final String PHOTO_MODULE_STRING_ID = "PhotoModule";
114 private static final Log.Tag TAG = new Log.Tag(PHOTO_MODULE_STRING_ID);
116 // We number the request code from 1000 to avoid collision with Gallery.
117 private static final int REQUEST_CROP = 1000;
119 // Messages defined for the UI thread handler.
120 private static final int MSG_FIRST_TIME_INIT = 1;
121 private static final int MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE = 2;
123 // The subset of parameters we need to update in setCameraParameters().
124 private static final int UPDATE_PARAM_INITIALIZE = 1;
125 private static final int UPDATE_PARAM_ZOOM = 2;
126 private static final int UPDATE_PARAM_PREFERENCE = 4;
127 private static final int UPDATE_PARAM_ALL = -1;
129 // This is the delay before we execute onResume tasks when coming
130 // from the lock screen, to allow time for onPause to execute.
131 private static final int ON_RESUME_TASKS_DELAY_MSEC = 20;
133 private static final String DEBUG_IMAGE_PREFIX = "DEBUG_";
135 private CameraActivity mActivity;
136 private CameraProxy mCameraDevice;
137 private int mCameraId;
138 private CameraCapabilities mCameraCapabilities;
139 private CameraSettings mCameraSettings;
140 private boolean mPaused;
144 // The activity is going to switch to the specified camera id. This is
145 // needed because texture copy is done in GL thread. -1 means camera is not
147 protected int mPendingSwitchCameraId = -1;
149 // When setCameraParametersWhenIdle() is called, we accumulate the subsets
150 // needed to be updated in mUpdateSet.
151 private int mUpdateSet;
153 private int mZoomValue; // The current zoom value.
154 private int mTimerDuration;
155 /** Set when a volume button is clicked to take photo */
156 private boolean mVolumeButtonClickedFlag = false;
158 private boolean mFocusAreaSupported;
159 private boolean mMeteringAreaSupported;
160 private boolean mAeLockSupported;
161 private boolean mAwbLockSupported;
162 private boolean mContinuousFocusSupported;
164 // The degrees of the device rotated clockwise from its natural orientation.
165 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
167 private static final String sTempCropFilename = "crop-temp";
169 private boolean mFaceDetectionStarted = false;
171 // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
172 private String mCropValue;
173 private Uri mSaveUri;
175 private Uri mDebugUri;
177 // We use a queue to generated names of the images to be used later
178 // when the image is ready to be saved.
179 private NamedImages mNamedImages;
181 private final Runnable mDoSnapRunnable = new Runnable() {
184 onShutterButtonClick();
189 * An unpublished intent flag requesting to return as soon as capturing is
190 * completed. TODO: consider publishing by moving into MediaStore.
192 private static final String EXTRA_QUICK_CAPTURE =
193 "android.intent.extra.quickCapture";
195 // The display rotation in degrees. This is only valid when mCameraState is
196 // not PREVIEW_STOPPED.
197 private int mDisplayRotation;
198 // The value for android.hardware.Camera.setDisplayOrientation.
199 private int mCameraDisplayOrientation;
200 // The value for UI components like indicators.
201 private int mDisplayOrientation;
202 // The value for cameradevice.CameraSettings.setPhotoRotationDegrees.
203 private int mJpegRotation;
204 // Indicates whether we are using front camera
205 private boolean mMirror;
206 private boolean mFirstTimeInitialized;
207 private boolean mIsImageCaptureIntent;
209 private int mCameraState = PREVIEW_STOPPED;
210 private boolean mSnapshotOnIdle = false;
212 private ContentResolver mContentResolver;
214 private AppController mAppController;
216 private final PostViewPictureCallback mPostViewPictureCallback =
217 new PostViewPictureCallback();
218 private final RawPictureCallback mRawPictureCallback =
219 new RawPictureCallback();
220 private final AutoFocusCallback mAutoFocusCallback =
221 new AutoFocusCallback();
222 private final Object mAutoFocusMoveCallback =
223 ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
224 ? new AutoFocusMoveCallback()
227 private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
229 private long mFocusStartTime;
230 private long mShutterCallbackTime;
231 private long mPostViewPictureCallbackTime;
232 private long mRawPictureCallbackTime;
233 private long mJpegPictureCallbackTime;
234 private long mOnResumeTime;
235 private byte[] mJpegImageData;
236 /** Touch coordinate for shutter button press. */
237 private TouchCoordinate mShutterTouchCoordinate;
240 // These latency time are for the CameraLatency test.
241 public long mAutoFocusTime;
242 public long mShutterLag;
243 public long mShutterToPictureDisplayedTime;
244 public long mPictureDisplayedToJpegCallbackTime;
245 public long mJpegCallbackFinishTime;
246 public long mCaptureStartTime;
248 // This handles everything about focus.
249 private FocusOverlayManager mFocusManager;
251 private final int mGcamModeIndex;
252 private final CountdownSoundPlayer mCountdownSoundPlayer = new CountdownSoundPlayer();
254 private CameraCapabilities.SceneMode mSceneMode;
256 private final Handler mHandler = new MainHandler(this);
258 private boolean mQuickCapture;
259 private SensorManager mSensorManager;
260 private final float[] mGData = new float[3];
261 private final float[] mMData = new float[3];
262 private final float[] mR = new float[16];
263 private int mHeading = -1;
265 /** True if all the parameters needed to start preview is ready. */
266 private boolean mCameraPreviewParamsReady = false;
268 private final MediaSaver.OnMediaSavedListener mOnMediaSavedListener =
269 new MediaSaver.OnMediaSavedListener() {
271 public void onMediaSaved(Uri uri) {
273 mActivity.notifyNewMedia(uri);
277 private boolean mShouldResizeTo16x9 = false;
279 private final Runnable mResumeTaskRunnable = new Runnable() {
287 * We keep the flash setting before entering scene modes (HDR)
288 * and restore it after HDR is off.
290 private String mFlashModeBeforeSceneMode;
293 * This callback gets called when user select whether or not to
294 * turn on geo-tagging.
296 public interface LocationDialogCallback {
298 * Gets called after user selected/unselected geo-tagging feature.
300 * @param selected whether or not geo-tagging feature is selected
302 public void onLocationTaggingSelected(boolean selected);
306 * This callback defines the text that is shown in the aspect ratio selection
307 * dialog, provides the current aspect ratio, and gets notified when user changes
308 * aspect ratio selection in the dialog.
310 public interface AspectRatioDialogCallback {
312 * Returns current aspect ratio that is being used to set as default.
314 public AspectRatioSelector.AspectRatio getCurrentAspectRatio();
317 * Gets notified when user has made the aspect ratio selection.
319 * @param newAspectRatio aspect ratio that user has selected
320 * @param dialogHandlingFinishedRunnable runnable to run when the operations
321 * needed to handle changes from dialog
324 public void onAspectRatioSelected(AspectRatioSelector.AspectRatio newAspectRatio,
325 Runnable dialogHandlingFinishedRunnable);
328 private void checkDisplayRotation() {
329 // Set the display orientation if display rotation has changed.
330 // Sometimes this happens when the device is held upside
331 // down and camera app is opened. Rotation animation will
332 // take some time and the rotation value we have got may be
333 // wrong. Framework does not have a callback for this now.
334 if (CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation) {
335 setDisplayOrientation();
337 if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
338 mHandler.postDelayed(new Runnable() {
341 checkDisplayRotation();
348 * This Handler is used to post message back onto the main thread of the
351 private static class MainHandler extends Handler {
352 private final WeakReference<PhotoModule> mModule;
354 public MainHandler(PhotoModule module) {
355 super(Looper.getMainLooper());
356 mModule = new WeakReference<PhotoModule>(module);
360 public void handleMessage(Message msg) {
361 PhotoModule module = mModule.get();
362 if (module == null) {
366 case MSG_FIRST_TIME_INIT: {
367 module.initializeFirstTime();
371 case MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE: {
372 module.setCameraParametersWhenIdle(0);
379 private void switchToGcamCapture() {
380 if (mActivity != null && mGcamModeIndex != 0) {
381 SettingsManager settingsManager = mActivity.getSettingsManager();
382 settingsManager.set(SettingsManager.SCOPE_GLOBAL,
383 Keys.KEY_CAMERA_HDR_PLUS, true);
385 // Disable the HDR+ button to prevent callbacks from being
386 // queued before the correct callback is attached to the button
387 // in the new module. The new module will set the enabled/disabled
388 // of this button when the module's preferred camera becomes available.
389 ButtonManager buttonManager = mActivity.getButtonManager();
390 buttonManager.disableButton(ButtonManager.BUTTON_HDR_PLUS);
392 mAppController.getCameraAppUI().freezeScreenUntilPreviewReady();
394 // Do not post this to avoid this module switch getting interleaved with
395 // other button callbacks.
396 mActivity.onModeSelected(mGcamModeIndex);
401 * Constructs a new photo module.
403 public PhotoModule(AppController app) {
405 mGcamModeIndex = app.getAndroidContext().getResources()
406 .getInteger(R.integer.camera_mode_gcam);
410 public String getPeekAccessibilityString() {
411 return mAppController.getAndroidContext()
412 .getResources().getString(R.string.photo_accessibility_peek);
416 public String getModuleStringIdentifier() {
417 return PHOTO_MODULE_STRING_ID;
421 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
422 mActivity = activity;
423 // TODO: Need to look at the controller interface to see if we can get
424 // rid of passing in the activity directly.
425 mAppController = mActivity;
427 mUI = new PhotoUI(mActivity, this, mActivity.getModuleLayoutRoot());
428 mActivity.setPreviewStatusListener(mUI);
430 SettingsManager settingsManager = mActivity.getSettingsManager();
431 mCameraId = settingsManager.getInteger(mAppController.getModuleScope(),
434 // TODO: Move this to SettingsManager as a part of upgrade procedure.
435 if (!settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
436 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
437 // Switch to back camera to set aspect ratio.
438 mCameraId = settingsManager.getIntegerDefault(Keys.KEY_CAMERA_ID);
441 mContentResolver = mActivity.getContentResolver();
443 // Surface texture is from camera screen nail and startPreview needs it.
444 // This must be done before startPreview.
445 mIsImageCaptureIntent = isImageCaptureIntent();
447 mActivity.getCameraProvider().requestCamera(mCameraId);
449 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
450 mSensorManager = (SensorManager) (mActivity.getSystemService(Context.SENSOR_SERVICE));
451 mUI.setCountdownFinishedListener(this);
453 // TODO: Make this a part of app controller API.
454 View cancelButton = mActivity.findViewById(R.id.shutter_cancel_button);
455 cancelButton.setOnClickListener(new View.OnClickListener() {
457 public void onClick(View view) {
463 private void cancelCountDown() {
464 if (mUI.isCountingDown()) {
465 // Cancel on-going countdown.
466 mUI.cancelCountDown();
468 mAppController.getCameraAppUI().transitionToCapture();
469 mAppController.getCameraAppUI().showModeOptions();
473 public boolean isUsingBottomBar() {
477 private void initializeControlByIntent() {
478 if (mIsImageCaptureIntent) {
479 mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
480 setupCaptureParams();
484 private void onPreviewStarted() {
485 mAppController.onPreviewStarted();
486 setCameraState(IDLE);
487 startFaceDetection();
492 * Prompt the user to pick to record location and choose aspect ratio for the
493 * very first run of camera only.
495 private void settingsFirstRun() {
496 final SettingsManager settingsManager = mActivity.getSettingsManager();
498 if (mActivity.isSecureCamera() || isImageCaptureIntent()) {
502 boolean locationPrompt = !settingsManager.isSet(SettingsManager.SCOPE_GLOBAL,
503 Keys.KEY_RECORD_LOCATION);
504 boolean aspectRatioPrompt = !settingsManager.getBoolean(
505 SettingsManager.SCOPE_GLOBAL, Keys.KEY_USER_SELECTED_ASPECT_RATIO);
506 if (!locationPrompt && !aspectRatioPrompt) {
510 // Check if the back camera exists
511 int backCameraId = mAppController.getCameraProvider().getFirstBackCameraId();
512 if (backCameraId == -1) {
513 // If there is no back camera, do not show the prompt.
517 if (locationPrompt) {
518 // Show both location and aspect ratio selection dialog.
519 mUI.showLocationAndAspectRatioDialog(new LocationDialogCallback(){
521 public void onLocationTaggingSelected(boolean selected) {
522 Keys.setLocation(mActivity.getSettingsManager(), selected,
523 mActivity.getLocationManager());
525 }, createAspectRatioDialogCallback());
527 // App upgrade. Only show aspect ratio selection.
528 mUI.showAspectRatioDialog(createAspectRatioDialogCallback());
532 private AspectRatioDialogCallback createAspectRatioDialogCallback() {
533 Size currentSize = mCameraSettings.getCurrentPhotoSize();
534 float aspectRatio = (float) currentSize.width() / (float) currentSize.height();
535 if (aspectRatio < 1f) {
536 aspectRatio = 1 / aspectRatio;
538 final AspectRatioSelector.AspectRatio currentAspectRatio;
539 if (Math.abs(aspectRatio - 4f / 3f) <= 0.1f) {
540 currentAspectRatio = AspectRatioSelector.AspectRatio.ASPECT_RATIO_4x3;
541 } else if (Math.abs(aspectRatio - 16f / 9f) <= 0.1f) {
542 currentAspectRatio = AspectRatioSelector.AspectRatio.ASPECT_RATIO_16x9;
544 // TODO: Log error and not show dialog.
548 List<Size> sizes = mCameraCapabilities.getSupportedPhotoSizes();
549 List<Size> pictureSizes = ResolutionUtil
550 .getDisplayableSizesFromSupported(sizes, true);
552 // This logic below finds the largest resolution for each aspect ratio.
553 // TODO: Move this somewhere that can be shared with SettingsActivity
554 int aspectRatio4x3Resolution = 0;
555 int aspectRatio16x9Resolution = 0;
556 Size largestSize4x3 = new Size(0, 0);
557 Size largestSize16x9 = new Size(0, 0);
558 for (Size size : pictureSizes) {
559 float pictureAspectRatio = (float) size.width() / (float) size.height();
560 pictureAspectRatio = pictureAspectRatio < 1 ?
561 1f / pictureAspectRatio : pictureAspectRatio;
562 int resolution = size.width() * size.height();
563 if (Math.abs(pictureAspectRatio - 4f / 3f) < 0.1f) {
564 if (resolution > aspectRatio4x3Resolution) {
565 aspectRatio4x3Resolution = resolution;
566 largestSize4x3 = size;
568 } else if (Math.abs(pictureAspectRatio - 16f / 9f) < 0.1f) {
569 if (resolution > aspectRatio16x9Resolution) {
570 aspectRatio16x9Resolution = resolution;
571 largestSize16x9 = size;
576 // Use the largest 4x3 and 16x9 sizes as candidates for picture size selection.
577 final Size size4x3ToSelect = largestSize4x3;
578 final Size size16x9ToSelect = largestSize16x9;
580 AspectRatioDialogCallback callback = new AspectRatioDialogCallback() {
583 public AspectRatioSelector.AspectRatio getCurrentAspectRatio() {
584 return currentAspectRatio;
588 public void onAspectRatioSelected(AspectRatioSelector.AspectRatio newAspectRatio,
589 Runnable dialogHandlingFinishedRunnable) {
590 if (newAspectRatio == AspectRatioSelector.AspectRatio.ASPECT_RATIO_4x3) {
591 String largestSize4x3Text = SettingsUtil.sizeToSetting(size4x3ToSelect);
592 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
593 Keys.KEY_PICTURE_SIZE_BACK,
595 } else if (newAspectRatio == AspectRatioSelector.AspectRatio.ASPECT_RATIO_16x9) {
596 String largestSize16x9Text = SettingsUtil.sizeToSetting(size16x9ToSelect);
597 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
598 Keys.KEY_PICTURE_SIZE_BACK,
599 largestSize16x9Text);
601 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
602 Keys.KEY_USER_SELECTED_ASPECT_RATIO, true);
603 String aspectRatio = mActivity.getSettingsManager().getString(
604 SettingsManager.SCOPE_GLOBAL,
605 Keys.KEY_USER_SELECTED_ASPECT_RATIO);
606 Log.e(TAG, "aspect ratio after setting it to true=" + aspectRatio);
607 if (newAspectRatio != currentAspectRatio) {
610 mUI.setRunnableForNextFrame(dialogHandlingFinishedRunnable);
612 mHandler.post(dialogHandlingFinishedRunnable);
620 public void onPreviewUIReady() {
625 public void onPreviewUIDestroyed() {
626 if (mCameraDevice == null) {
629 mCameraDevice.setPreviewTexture(null);
634 public void startPreCaptureAnimation() {
635 mAppController.startPreCaptureAnimation();
638 private void onCameraOpened() {
640 initializeControlByIntent();
643 private void switchCamera() {
649 mAppController.freezeScreenUntilPreviewReady();
650 SettingsManager settingsManager = mActivity.getSettingsManager();
652 Log.i(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
654 mCameraId = mPendingSwitchCameraId;
655 settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID, mCameraId);
656 mActivity.getCameraProvider().requestCamera(mCameraId);
658 if (mFocusManager != null) {
659 mFocusManager.removeMessages();
662 mMirror = isCameraFrontFacing();
663 mFocusManager.setMirror(mMirror);
664 // Start switch camera animation. Post a message because
665 // onFrameAvailable from the old camera may already exist.
668 private final ButtonManager.ButtonCallback mCameraCallback =
669 new ButtonManager.ButtonCallback() {
671 public void onStateChanged(int state) {
672 // At the time this callback is fired, the camera id
673 // has be set to the desired camera.
675 if (mPaused || mAppController.getCameraProvider().waitingForCamera()) {
678 // If switching to back camera, and HDR+ is still on,
679 // switch back to gcam, otherwise handle callback normally.
680 SettingsManager settingsManager = mActivity.getSettingsManager();
681 if (Keys.isCameraBackFacing(settingsManager,
682 mAppController.getModuleScope())) {
683 if (Keys.requestsReturnToHdrPlus(settingsManager,
684 mAppController.getModuleScope())) {
685 switchToGcamCapture();
690 mPendingSwitchCameraId = state;
692 Log.d(TAG, "Start to switch camera. cameraId=" + state);
693 // We need to keep a preview frame for the animation before
694 // releasing the camera. This will trigger
695 // onPreviewTextureCopied.
696 // TODO: Need to animate the camera switch
701 private final ButtonManager.ButtonCallback mHdrPlusCallback =
702 new ButtonManager.ButtonCallback() {
704 public void onStateChanged(int state) {
705 SettingsManager settingsManager = mActivity.getSettingsManager();
706 if (GcamHelper.hasGcamCapture()) {
707 // Set the camera setting to default backfacing.
708 settingsManager.setToDefault(mAppController.getModuleScope(),
710 switchToGcamCapture();
712 if (Keys.isHdrOn(settingsManager)) {
713 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_SCENE_MODE,
714 mCameraCapabilities.getStringifier().stringify(
715 CameraCapabilities.SceneMode.HDR));
717 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_SCENE_MODE,
718 mCameraCapabilities.getStringifier().stringify(
719 CameraCapabilities.SceneMode.AUTO));
721 updateParametersSceneMode();
722 mCameraDevice.applySettings(mCameraSettings);
728 private final View.OnClickListener mCancelCallback = new View.OnClickListener() {
730 public void onClick(View v) {
731 onCaptureCancelled();
735 private final View.OnClickListener mDoneCallback = new View.OnClickListener() {
737 public void onClick(View v) {
742 private final View.OnClickListener mRetakeCallback = new View.OnClickListener() {
744 public void onClick(View v) {
745 mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
751 public void hardResetSettings(SettingsManager settingsManager) {
752 // PhotoModule should hard reset HDR+ to off,
753 // and HDR to off if HDR+ is supported.
754 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false);
755 if (GcamHelper.hasGcamCapture()) {
756 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR, false);
761 public HardwareSpec getHardwareSpec() {
762 return (mCameraSettings != null ?
763 new HardwareSpecImpl(getCameraProvider(), mCameraCapabilities) : null);
767 public CameraAppUI.BottomBarUISpec getBottomBarSpec() {
768 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
770 bottomBarSpec.enableCamera = true;
771 bottomBarSpec.cameraCallback = mCameraCallback;
772 bottomBarSpec.enableFlash = !mAppController.getSettingsManager()
773 .getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR);
774 bottomBarSpec.enableHdr = true;
775 bottomBarSpec.hdrCallback = mHdrPlusCallback;
776 bottomBarSpec.enableGridLines = true;
777 if (mCameraCapabilities != null) {
778 bottomBarSpec.enableExposureCompensation = true;
779 bottomBarSpec.exposureCompensationSetCallback =
780 new CameraAppUI.BottomBarUISpec.ExposureCompensationSetCallback() {
782 public void setExposure(int value) {
783 setExposureCompensation(value);
786 bottomBarSpec.minExposureCompensation =
787 mCameraCapabilities.getMinExposureCompensation();
788 bottomBarSpec.maxExposureCompensation =
789 mCameraCapabilities.getMaxExposureCompensation();
790 bottomBarSpec.exposureCompensationStep =
791 mCameraCapabilities.getExposureCompensationStep();
794 bottomBarSpec.enableSelfTimer = true;
795 bottomBarSpec.showSelfTimer = true;
797 if (isImageCaptureIntent()) {
798 bottomBarSpec.showCancel = true;
799 bottomBarSpec.cancelCallback = mCancelCallback;
800 bottomBarSpec.showDone = true;
801 bottomBarSpec.doneCallback = mDoneCallback;
802 bottomBarSpec.showRetake = true;
803 bottomBarSpec.retakeCallback = mRetakeCallback;
806 return bottomBarSpec;
809 // either open a new camera or switch cameras
810 private void openCameraCommon() {
811 mUI.onCameraOpened(mCameraCapabilities, mCameraSettings);
812 if (mIsImageCaptureIntent) {
813 // Set hdr plus to default: off.
814 SettingsManager settingsManager = mActivity.getSettingsManager();
815 settingsManager.setToDefault(SettingsManager.SCOPE_GLOBAL,
816 Keys.KEY_CAMERA_HDR_PLUS);
822 public void updatePreviewAspectRatio(float aspectRatio) {
823 mAppController.updatePreviewAspectRatio(aspectRatio);
826 private void resetExposureCompensation() {
827 SettingsManager settingsManager = mActivity.getSettingsManager();
828 if (settingsManager == null) {
829 Log.e(TAG, "Settings manager is null!");
832 settingsManager.setToDefault(mAppController.getCameraScope(),
836 // Snapshots can only be taken after this is called. It should be called
837 // once only. We could have done these things in onCreate() but we want to
838 // make preview screen appear as soon as possible.
839 private void initializeFirstTime() {
840 if (mFirstTimeInitialized || mPaused) {
844 mUI.initializeFirstTime();
846 // We set the listener only when both service and shutterbutton
848 getServices().getMemoryManager().addListener(this);
850 mNamedImages = new NamedImages();
852 mFirstTimeInitialized = true;
855 mActivity.updateStorageSpaceAndHint(null);
858 // If the activity is paused and resumed, this method will be called in
860 private void initializeSecondTime() {
861 getServices().getMemoryManager().addListener(this);
862 mNamedImages = new NamedImages();
863 mUI.initializeSecondTime(mCameraCapabilities, mCameraSettings);
866 private void addIdleHandler() {
867 MessageQueue queue = Looper.myQueue();
868 queue.addIdleHandler(new MessageQueue.IdleHandler() {
870 public boolean queueIdle() {
871 Storage.ensureOSXCompatible();
878 public void startFaceDetection() {
879 if (mFaceDetectionStarted) {
882 if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) {
883 mFaceDetectionStarted = true;
884 mUI.onStartFaceDetection(mDisplayOrientation, isCameraFrontFacing());
885 mCameraDevice.setFaceDetectionCallback(mHandler, mUI);
886 mCameraDevice.startFaceDetection();
887 SessionStatsCollector.instance().faceScanActive(true);
892 public void stopFaceDetection() {
893 if (!mFaceDetectionStarted) {
896 if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) {
897 mFaceDetectionStarted = false;
898 mCameraDevice.setFaceDetectionCallback(null, null);
899 mCameraDevice.stopFaceDetection();
901 SessionStatsCollector.instance().faceScanActive(false);
905 private final class ShutterCallback
906 implements CameraShutterCallback {
908 private final boolean mNeedsAnimation;
910 public ShutterCallback(boolean needsAnimation) {
911 mNeedsAnimation = needsAnimation;
915 public void onShutter(CameraProxy camera) {
916 mShutterCallbackTime = System.currentTimeMillis();
917 mShutterLag = mShutterCallbackTime - mCaptureStartTime;
918 Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
919 if (mNeedsAnimation) {
920 mActivity.runOnUiThread(new Runnable() {
923 animateAfterShutter();
930 private final class PostViewPictureCallback
931 implements CameraPictureCallback {
933 public void onPictureTaken(byte[] data, CameraProxy camera) {
934 mPostViewPictureCallbackTime = System.currentTimeMillis();
935 Log.v(TAG, "mShutterToPostViewCallbackTime = "
936 + (mPostViewPictureCallbackTime - mShutterCallbackTime)
941 private final class RawPictureCallback
942 implements CameraPictureCallback {
944 public void onPictureTaken(byte[] rawData, CameraProxy camera) {
945 mRawPictureCallbackTime = System.currentTimeMillis();
946 Log.v(TAG, "mShutterToRawCallbackTime = "
947 + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
951 private static class ResizeBundle {
953 float targetAspectRatio;
958 * @return Cropped image if the target aspect ratio is larger than the jpeg
959 * aspect ratio on the long axis. The original jpeg otherwise.
961 private ResizeBundle cropJpegDataToAspectRatio(ResizeBundle dataBundle) {
963 final byte[] jpegData = dataBundle.jpegData;
964 final ExifInterface exif = dataBundle.exif;
965 float targetAspectRatio = dataBundle.targetAspectRatio;
967 Bitmap original = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
968 int originalWidth = original.getWidth();
969 int originalHeight = original.getHeight();
973 if (originalWidth > originalHeight) {
974 newHeight = (int) (originalWidth / targetAspectRatio);
975 newWidth = originalWidth;
977 newWidth = (int) (originalHeight / targetAspectRatio);
978 newHeight = originalHeight;
980 int xOffset = (originalWidth - newWidth)/2;
981 int yOffset = (originalHeight - newHeight)/2;
983 if (xOffset < 0 || yOffset < 0) {
987 Bitmap resized = Bitmap.createBitmap(original,xOffset,yOffset,newWidth, newHeight);
988 exif.setTagValue(ExifInterface.TAG_PIXEL_X_DIMENSION, new Integer(newWidth));
989 exif.setTagValue(ExifInterface.TAG_PIXEL_Y_DIMENSION, new Integer(newHeight));
991 ByteArrayOutputStream stream = new ByteArrayOutputStream();
993 resized.compress(Bitmap.CompressFormat.JPEG, 90, stream);
994 dataBundle.jpegData = stream.toByteArray();
998 private final class JpegPictureCallback
999 implements CameraPictureCallback {
1002 public JpegPictureCallback(Location loc) {
1007 public void onPictureTaken(final byte[] originalJpegData, final CameraProxy camera) {
1008 mAppController.setShutterEnabled(true);
1012 if (mIsImageCaptureIntent) {
1015 if (mSceneMode == CameraCapabilities.SceneMode.HDR) {
1016 mUI.setSwipingEnabled(true);
1019 mJpegPictureCallbackTime = System.currentTimeMillis();
1020 // If postview callback has arrived, the captured image is displayed
1021 // in postview callback. If not, the captured image is displayed in
1022 // raw picture callback.
1023 if (mPostViewPictureCallbackTime != 0) {
1024 mShutterToPictureDisplayedTime =
1025 mPostViewPictureCallbackTime - mShutterCallbackTime;
1026 mPictureDisplayedToJpegCallbackTime =
1027 mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
1029 mShutterToPictureDisplayedTime =
1030 mRawPictureCallbackTime - mShutterCallbackTime;
1031 mPictureDisplayedToJpegCallbackTime =
1032 mJpegPictureCallbackTime - mRawPictureCallbackTime;
1034 Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
1035 + mPictureDisplayedToJpegCallbackTime + "ms");
1037 mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden.
1038 if (!mIsImageCaptureIntent) {
1042 long now = System.currentTimeMillis();
1043 mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
1044 Log.v(TAG, "mJpegCallbackFinishTime = " + mJpegCallbackFinishTime + "ms");
1045 mJpegPictureCallbackTime = 0;
1047 final ExifInterface exif = Exif.getExif(originalJpegData);
1049 if (mShouldResizeTo16x9) {
1050 final ResizeBundle dataBundle = new ResizeBundle();
1051 dataBundle.jpegData = originalJpegData;
1052 dataBundle.targetAspectRatio = ResolutionUtil.NEXUS_5_LARGE_16_BY_9_ASPECT_RATIO;
1053 dataBundle.exif = exif;
1054 new AsyncTask<ResizeBundle, Void, ResizeBundle>() {
1057 protected ResizeBundle doInBackground(ResizeBundle... resizeBundles) {
1058 return cropJpegDataToAspectRatio(resizeBundles[0]);
1062 protected void onPostExecute(ResizeBundle result) {
1063 saveFinalPhoto(result.jpegData, result.exif, camera);
1065 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dataBundle);
1068 saveFinalPhoto(originalJpegData, exif, camera);
1072 void saveFinalPhoto(final byte[] jpegData, final ExifInterface exif, CameraProxy camera) {
1074 int orientation = Exif.getOrientation(exif);
1076 float zoomValue = 0f;
1077 if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
1078 int zoomIndex = mCameraSettings.getCurrentZoomIndex();
1079 List<Integer> zoomRatios = mCameraCapabilities.getZoomRatioList();
1080 if (zoomRatios != null && zoomIndex < zoomRatios.size()) {
1081 zoomValue = 0.01f * zoomRatios.get(zoomIndex);
1085 boolean hdrOn = CameraCapabilities.SceneMode.HDR == mSceneMode;
1086 String flashSetting =
1087 mActivity.getSettingsManager().getString(mAppController.getCameraScope(),
1088 Keys.KEY_FLASH_MODE);
1089 boolean gridLinesOn = Keys.areGridLinesOn(mActivity.getSettingsManager());
1090 UsageStatistics.instance().photoCaptureDoneEvent(
1091 eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
1092 mNamedImages.mQueue.lastElement().title + ".jpg", exif,
1093 isCameraFrontFacing(), hdrOn, zoomValue, flashSetting, gridLinesOn,
1094 (float) mTimerDuration, mShutterTouchCoordinate, mVolumeButtonClickedFlag);
1095 mShutterTouchCoordinate = null;
1096 mVolumeButtonClickedFlag = false;
1098 if (!mIsImageCaptureIntent) {
1099 // Calculate the width and the height of the jpeg.
1100 Integer exifWidth = exif.getTagIntValue(ExifInterface.TAG_PIXEL_X_DIMENSION);
1101 Integer exifHeight = exif.getTagIntValue(ExifInterface.TAG_PIXEL_Y_DIMENSION);
1103 if (mShouldResizeTo16x9 && exifWidth != null && exifHeight != null) {
1105 height = exifHeight;
1108 s = mCameraSettings.getCurrentPhotoSize();
1109 if ((mJpegRotation + orientation) % 180 == 0) {
1111 height = s.height();
1117 NamedEntity name = mNamedImages.getNextNameEntity();
1118 String title = (name == null) ? null : name.title;
1119 long date = (name == null) ? -1 : name.date;
1121 // Handle debug mode outputs
1122 if (mDebugUri != null) {
1123 // If using a debug uri, save jpeg there.
1124 saveToDebugUri(jpegData);
1126 // Adjust the title of the debug image shown in mediastore.
1127 if (title != null) {
1128 title = DEBUG_IMAGE_PREFIX + title;
1132 if (title == null) {
1133 Log.e(TAG, "Unbalanced name/data pair");
1136 date = mCaptureStartTime;
1138 if (mHeading >= 0) {
1139 // heading direction has been updated by the sensor.
1140 ExifTag directionRefTag = exif.buildTag(
1141 ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
1142 ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION);
1143 ExifTag directionTag = exif.buildTag(
1144 ExifInterface.TAG_GPS_IMG_DIRECTION,
1145 new Rational(mHeading, 1));
1146 exif.setTag(directionRefTag);
1147 exif.setTag(directionTag);
1149 getServices().getMediaSaver().addImage(
1150 jpegData, title, date, mLocation, width, height,
1151 orientation, exif, mOnMediaSavedListener, mContentResolver);
1153 // Animate capture with real jpeg data instead of a preview
1155 mUI.animateCapture(jpegData, orientation, mMirror);
1157 mJpegImageData = jpegData;
1158 if (!mQuickCapture) {
1159 mUI.showCapturedImageForReview(jpegData, orientation, mMirror);
1165 // Send the taken photo to remote shutter listeners, if any are
1167 AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
1170 getServices().getRemoteShutterListener().onPictureTaken(jpegData);
1174 // Check this in advance of each shot so we don't add to shutter
1175 // latency. It's true that someone else could write to the SD card
1176 // in the mean time and fill it, but that could have happened
1177 // between the shutter press and saving the JPEG too.
1178 mActivity.updateStorageSpaceAndHint(null);
1182 private final class AutoFocusCallback implements CameraAFCallback {
1184 public void onAutoFocus(boolean focused, CameraProxy camera) {
1185 SessionStatsCollector.instance().autofocusResult(focused);
1190 mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
1191 Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms focused = "+focused);
1192 setCameraState(IDLE);
1193 mFocusManager.onAutoFocus(focused, false);
1197 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1198 private final class AutoFocusMoveCallback
1199 implements CameraAFMoveCallback {
1201 public void onAutoFocusMoving(
1202 boolean moving, CameraProxy camera) {
1203 mFocusManager.onAutoFocusMoving(moving);
1204 SessionStatsCollector.instance().autofocusMoving(moving);
1209 * This class is just a thread-safe queue for name,date holder objects.
1211 public static class NamedImages {
1212 private final Vector<NamedEntity> mQueue;
1214 public NamedImages() {
1215 mQueue = new Vector<NamedEntity>();
1218 public void nameNewImage(long date) {
1219 NamedEntity r = new NamedEntity();
1220 r.title = CameraUtil.createJpegName(date);
1225 public NamedEntity getNextNameEntity() {
1226 synchronized (mQueue) {
1227 if (!mQueue.isEmpty()) {
1228 return mQueue.remove(0);
1234 public static class NamedEntity {
1235 public String title;
1240 private void setCameraState(int state) {
1241 mCameraState = state;
1243 case PREVIEW_STOPPED:
1244 case SNAPSHOT_IN_PROGRESS:
1245 case SWITCHING_CAMERA:
1246 // TODO: Tell app UI to disable swipe
1248 case PhotoController.IDLE:
1249 // TODO: Tell app UI to enable swipe
1254 private void animateAfterShutter() {
1255 // Only animate when in full screen capture mode
1256 // i.e. If monkey/a user swipes to the gallery during picture taking,
1257 // don't show animation
1258 if (!mIsImageCaptureIntent) {
1264 public boolean capture() {
1265 // If we are already in the middle of taking a snapshot or the image
1266 // save request is full then ignore.
1267 if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
1268 || mCameraState == SWITCHING_CAMERA || !mAppController.isShutterEnabled()) {
1271 mCaptureStartTime = System.currentTimeMillis();
1273 mPostViewPictureCallbackTime = 0;
1274 mJpegImageData = null;
1276 final boolean animateBefore = (mSceneMode == CameraCapabilities.SceneMode.HDR);
1278 if (animateBefore) {
1279 animateAfterShutter();
1282 // Set rotation and gps data.
1285 // We need to be consistent with the framework orientation (i.e. the
1286 // orientation of the UI.) when the auto-rotate screen setting is on.
1287 if (mActivity.isAutoRotateScreen()) {
1288 orientation = (360 - mDisplayRotation) % 360;
1290 orientation = mOrientation;
1292 Characteristics info =
1293 mActivity.getCameraProvider().getCharacteristics(mCameraId);
1294 mJpegRotation = CameraUtil.getJpegRotation(info, orientation);
1295 mCameraSettings.setPhotoRotationDegrees(mJpegRotation);
1296 Location loc = mActivity.getLocationManager().getCurrentLocation();
1297 CameraUtil.setGpsParameters(mCameraSettings, loc);
1298 mCameraDevice.applySettings(mCameraSettings);
1300 // We don't want user to press the button again while taking a
1301 // multi-second HDR photo.
1302 mAppController.setShutterEnabled(false);
1303 mCameraDevice.takePicture(mHandler,
1304 new ShutterCallback(!animateBefore),
1305 mRawPictureCallback, mPostViewPictureCallback,
1306 new JpegPictureCallback(loc));
1308 mNamedImages.nameNewImage(mCaptureStartTime);
1310 mFaceDetectionStarted = false;
1311 setCameraState(SNAPSHOT_IN_PROGRESS);
1316 public void setFocusParameters() {
1317 setCameraParameters(UPDATE_PARAM_PREFERENCE);
1320 private void updateSceneMode() {
1321 // If scene mode is set, we cannot set flash mode, white balance, and
1322 // focus mode, instead, we read it from driver
1323 if (CameraCapabilities.SceneMode.AUTO != mSceneMode) {
1324 overrideCameraSettings(mCameraSettings.getCurrentFlashMode(),
1325 mCameraSettings.getCurrentFocusMode());
1329 private void overrideCameraSettings(CameraCapabilities.FlashMode flashMode,
1330 CameraCapabilities.FocusMode focusMode) {
1331 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
1332 SettingsManager settingsManager = mActivity.getSettingsManager();
1333 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FLASH_MODE,
1334 stringifier.stringify(flashMode));
1335 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FOCUS_MODE,
1336 stringifier.stringify(focusMode));
1340 public void onOrientationChanged(int orientation) {
1341 // We keep the last known orientation. So if the user first orient
1342 // the camera then point the camera to floor or sky, we still have
1343 // the correct orientation.
1344 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
1347 mOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
1351 public void onCameraAvailable(CameraProxy cameraProxy) {
1355 mCameraDevice = cameraProxy;
1357 initializeCapabilities();
1359 // Reset zoom value index.
1361 if (mFocusManager == null) {
1362 initializeFocusManager();
1364 mFocusManager.updateCapabilities(mCameraCapabilities);
1366 // Do camera parameter dependent initialization.
1367 mCameraSettings = mCameraDevice.getSettings();
1368 setCameraParameters(UPDATE_PARAM_ALL);
1369 // Set a listener which updates camera parameters based
1370 // on changed settings.
1371 SettingsManager settingsManager = mActivity.getSettingsManager();
1372 settingsManager.addListener(this);
1373 mCameraPreviewParamsReady = true;
1381 public void onCaptureCancelled() {
1382 mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent());
1387 public void onCaptureRetake() {
1391 mUI.hidePostCaptureAlert();
1392 mUI.hideIntentReviewImageView();
1397 public void onCaptureDone() {
1402 byte[] data = mJpegImageData;
1404 if (mCropValue == null) {
1405 // First handle the no crop case -- just return the value. If the
1406 // caller specifies a "save uri" then write the data to its
1407 // stream. Otherwise, pass back a scaled down version of the bitmap
1408 // directly in the extras.
1409 if (mSaveUri != null) {
1410 OutputStream outputStream = null;
1412 outputStream = mContentResolver.openOutputStream(mSaveUri);
1413 outputStream.write(data);
1414 outputStream.close();
1416 mActivity.setResultEx(Activity.RESULT_OK);
1418 } catch (IOException ex) {
1421 CameraUtil.closeSilently(outputStream);
1424 ExifInterface exif = Exif.getExif(data);
1425 int orientation = Exif.getOrientation(exif);
1426 Bitmap bitmap = CameraUtil.makeBitmap(data, 50 * 1024);
1427 bitmap = CameraUtil.rotate(bitmap, orientation);
1428 mActivity.setResultEx(Activity.RESULT_OK,
1429 new Intent("inline-data").putExtra("data", bitmap));
1433 // Save the image to a temp file and invoke the cropper
1435 FileOutputStream tempStream = null;
1437 File path = mActivity.getFileStreamPath(sTempCropFilename);
1439 tempStream = mActivity.openFileOutput(sTempCropFilename, 0);
1440 tempStream.write(data);
1442 tempUri = Uri.fromFile(path);
1443 } catch (FileNotFoundException ex) {
1444 mActivity.setResultEx(Activity.RESULT_CANCELED);
1447 } catch (IOException ex) {
1448 mActivity.setResultEx(Activity.RESULT_CANCELED);
1452 CameraUtil.closeSilently(tempStream);
1455 Bundle newExtras = new Bundle();
1456 if (mCropValue.equals("circle")) {
1457 newExtras.putString("circleCrop", "true");
1459 if (mSaveUri != null) {
1460 newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
1462 newExtras.putBoolean(CameraUtil.KEY_RETURN_DATA, true);
1464 if (mActivity.isSecureCamera()) {
1465 newExtras.putBoolean(CameraUtil.KEY_SHOW_WHEN_LOCKED, true);
1468 // TODO: Share this constant.
1469 final String CROP_ACTION = "com.android.camera.action.CROP";
1470 Intent cropIntent = new Intent(CROP_ACTION);
1472 cropIntent.setData(tempUri);
1473 cropIntent.putExtras(newExtras);
1475 mActivity.startActivityForResult(cropIntent, REQUEST_CROP);
1480 public void onShutterCoordinate(TouchCoordinate coord) {
1481 mShutterTouchCoordinate = coord;
1485 public void onShutterButtonFocus(boolean pressed) {
1486 // Do nothing. We don't support half-press to focus anymore.
1490 public void onShutterButtonClick() {
1491 if (mPaused || (mCameraState == SWITCHING_CAMERA)
1492 || (mCameraState == PREVIEW_STOPPED)) {
1493 mVolumeButtonClickedFlag = false;
1497 // Do not take the picture if there is not enough storage.
1498 if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1499 Log.i(TAG, "Not enough space or storage not ready. remaining="
1500 + mActivity.getStorageSpaceBytes());
1501 mVolumeButtonClickedFlag = false;
1504 Log.d(TAG, "onShutterButtonClick: mCameraState=" + mCameraState +
1505 " mVolumeButtonClickedFlag=" + mVolumeButtonClickedFlag);
1507 int countDownDuration = mActivity.getSettingsManager()
1508 .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);
1509 mTimerDuration = countDownDuration;
1510 if (countDownDuration > 0) {
1511 // Start count down.
1512 mAppController.getCameraAppUI().transitionToCancel();
1513 mAppController.getCameraAppUI().hideModeOptions();
1514 mUI.startCountdown(countDownDuration);
1521 private void focusAndCapture() {
1522 if (mSceneMode == CameraCapabilities.SceneMode.HDR) {
1523 mUI.setSwipingEnabled(false);
1525 // If the user wants to do a snapshot while the previous one is still
1526 // in progress, remember the fact and do it after we finish the previous
1527 // one and re-start the preview. Snapshot in progress also includes the
1528 // state that autofocus is focusing and a picture will be taken when
1529 // focus callback arrives.
1530 if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)) {
1531 if (!mIsImageCaptureIntent) {
1532 mSnapshotOnIdle = true;
1537 mSnapshotOnIdle = false;
1538 mFocusManager.focusAndCapture(mCameraSettings.getCurrentFocusMode());
1542 public void onRemainingSecondsChanged(int remainingSeconds) {
1543 mCountdownSoundPlayer.onRemainingSecondsChanged(remainingSeconds);
1547 public void onCountDownFinished() {
1548 if (mIsImageCaptureIntent) {
1549 mAppController.getCameraAppUI().transitionToIntentReviewLayout();
1551 mAppController.getCameraAppUI().transitionToCapture();
1553 mAppController.getCameraAppUI().showModeOptions();
1560 private void onResumeTasks() {
1564 Log.v(TAG, "Executing onResumeTasks.");
1565 CameraProvider camProvider = mActivity.getCameraProvider();
1566 if (camProvider == null) {
1567 // No camera provider, the Activity is destroyed already.
1570 camProvider.requestCamera(mCameraId);
1572 mJpegPictureCallbackTime = 0;
1575 mOnResumeTime = SystemClock.uptimeMillis();
1576 checkDisplayRotation();
1578 // If first time initialization is not finished, put it in the
1580 if (!mFirstTimeInitialized) {
1581 mHandler.sendEmptyMessage(MSG_FIRST_TIME_INIT);
1583 initializeSecondTime();
1586 Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
1587 if (gsensor != null) {
1588 mSensorManager.registerListener(this, gsensor, SensorManager.SENSOR_DELAY_NORMAL);
1591 Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
1592 if (msensor != null) {
1593 mSensorManager.registerListener(this, msensor, SensorManager.SENSOR_DELAY_NORMAL);
1598 * @return Whether the currently active camera is front-facing.
1600 private boolean isCameraFrontFacing() {
1601 return mAppController.getCameraProvider().getCharacteristics(mCameraId)
1606 * The focus manager is the first UI related element to get initialized, and
1607 * it requires the RenderOverlay, so initialize it here
1609 private void initializeFocusManager() {
1610 // Create FocusManager object. startPreview needs it.
1611 // if mFocusManager not null, reuse it
1612 // otherwise create a new instance
1613 if (mFocusManager != null) {
1614 mFocusManager.removeMessages();
1616 mMirror = isCameraFrontFacing();
1617 String[] defaultFocusModesStrings = mActivity.getResources().getStringArray(
1618 R.array.pref_camera_focusmode_default_array);
1619 ArrayList<CameraCapabilities.FocusMode> defaultFocusModes = new ArrayList<>();
1620 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
1621 for (String modeString : defaultFocusModesStrings) {
1622 CameraCapabilities.FocusMode mode = stringifier.focusModeFromString(modeString);
1624 defaultFocusModes.add(mode);
1628 new FocusOverlayManager(mAppController, defaultFocusModes,
1629 mCameraCapabilities, this, mMirror, mActivity.getMainLooper(),
1631 MotionManager motionManager = getServices().getMotionManager();
1632 if (motionManager != null) {
1633 motionManager.addListener(mFocusManager);
1636 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1640 public void resume() {
1642 mCountdownSoundPlayer.loadSounds();
1643 if (mFocusManager != null) {
1644 // If camera is not open when resume is called, focus manager will
1646 // be initialized yet, in which case it will start listening to
1647 // preview area size change later in the initialization.
1648 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1650 mAppController.addPreviewAreaSizeChangedListener(mUI);
1652 // Add delay on resume from lock screen only, in order to to speed up
1653 // the onResume --> onPause --> onResume cycle from lock screen.
1654 // Don't do always because letting go of thread can cause delay.
1655 String action = mActivity.getIntent().getAction();
1656 if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action)
1657 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) {
1658 Log.v(TAG, "On resume, from lock screen.");
1659 // Note: onPauseAfterSuper() will delete this runnable, so we will
1660 // at most have 1 copy queued up.
1661 mHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC);
1663 Log.v(TAG, "On resume.");
1666 getServices().getRemoteShutterListener().onModuleReady(this);
1667 SessionStatsCollector.instance().sessionActive(true);
1671 public void pause() {
1673 mHandler.removeCallbacks(mResumeTaskRunnable);
1674 getServices().getRemoteShutterListener().onModuleExit();
1675 SessionStatsCollector.instance().sessionActive(false);
1677 Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
1678 if (gsensor != null) {
1679 mSensorManager.unregisterListener(this, gsensor);
1682 Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
1683 if (msensor != null) {
1684 mSensorManager.unregisterListener(this, msensor);
1687 // Reset the focus first. Camera CTS does not guarantee that
1688 // cancelAutoFocus is allowed after preview stops.
1689 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1690 mCameraDevice.cancelAutoFocus();
1693 // If the camera has not been opened asynchronously yet,
1694 // and startPreview hasn't been called, then this is a no-op.
1695 // (e.g. onResume -> onPause -> onResume).
1698 mCountdownSoundPlayer.release();
1700 mNamedImages = null;
1701 // If we are in an image capture intent and has taken
1702 // a picture, we just clear it in onPause.
1703 mJpegImageData = null;
1705 // Remove the messages and runnables in the queue.
1706 mHandler.removeCallbacksAndMessages(null);
1709 mActivity.enableKeepScreenOn(false);
1712 mPendingSwitchCameraId = -1;
1713 if (mFocusManager != null) {
1714 mFocusManager.removeMessages();
1716 getServices().getMemoryManager().removeListener(this);
1717 mAppController.removePreviewAreaSizeChangedListener(mFocusManager);
1718 mAppController.removePreviewAreaSizeChangedListener(mUI);
1720 SettingsManager settingsManager = mActivity.getSettingsManager();
1721 settingsManager.removeListener(this);
1725 public void destroy() {
1726 // TODO: implement this.
1730 public void onLayoutOrientationChanged(boolean isLandscape) {
1731 setDisplayOrientation();
1735 public void updateCameraOrientation() {
1736 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
1737 setDisplayOrientation();
1741 private boolean canTakePicture() {
1742 return isCameraIdle()
1743 && (mActivity.getStorageSpaceBytes() > Storage.LOW_STORAGE_THRESHOLD_BYTES);
1747 public void autoFocus() {
1748 Log.v(TAG,"Starting auto focus");
1749 mFocusStartTime = System.currentTimeMillis();
1750 mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
1751 SessionStatsCollector.instance().autofocusManualTrigger();
1752 setCameraState(FOCUSING);
1756 public void cancelAutoFocus() {
1757 mCameraDevice.cancelAutoFocus();
1758 setCameraState(IDLE);
1759 setCameraParameters(UPDATE_PARAM_PREFERENCE);
1763 public void onSingleTapUp(View view, int x, int y) {
1764 if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
1765 || mCameraState == SNAPSHOT_IN_PROGRESS
1766 || mCameraState == SWITCHING_CAMERA
1767 || mCameraState == PREVIEW_STOPPED) {
1771 // Check if metering area or focus area is supported.
1772 if (!mFocusAreaSupported && !mMeteringAreaSupported) {
1775 mFocusManager.onSingleTapUp(x, y);
1779 public boolean onBackPressed() {
1780 return mUI.onBackPressed();
1784 public boolean onKeyDown(int keyCode, KeyEvent event) {
1786 case KeyEvent.KEYCODE_VOLUME_UP:
1787 case KeyEvent.KEYCODE_VOLUME_DOWN:
1788 case KeyEvent.KEYCODE_FOCUS:
1789 if (/* TODO: mActivity.isInCameraApp() && */mFirstTimeInitialized &&
1790 !mActivity.getCameraAppUI().isInIntentReview()) {
1791 if (event.getRepeatCount() == 0) {
1792 onShutterButtonFocus(true);
1797 case KeyEvent.KEYCODE_CAMERA:
1798 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1799 onShutterButtonClick();
1802 case KeyEvent.KEYCODE_DPAD_CENTER:
1803 // If we get a dpad center event without any focused view, move
1804 // the focus to the shutter button and press it.
1805 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1806 // Start auto-focus immediately to reduce shutter lag. After
1807 // the shutter button gets the focus, onShutterButtonFocus()
1808 // will be called again but it is fine.
1809 onShutterButtonFocus(true);
1817 public boolean onKeyUp(int keyCode, KeyEvent event) {
1819 case KeyEvent.KEYCODE_VOLUME_UP:
1820 case KeyEvent.KEYCODE_VOLUME_DOWN:
1821 if (/* mActivity.isInCameraApp() && */mFirstTimeInitialized &&
1822 !mActivity.getCameraAppUI().isInIntentReview()) {
1823 if (mUI.isCountingDown()) {
1826 mVolumeButtonClickedFlag = true;
1827 onShutterButtonClick();
1832 case KeyEvent.KEYCODE_FOCUS:
1833 if (mFirstTimeInitialized) {
1834 onShutterButtonFocus(false);
1841 private void closeCamera() {
1842 if (mCameraDevice != null) {
1843 stopFaceDetection();
1844 mCameraDevice.setZoomChangeListener(null);
1845 mCameraDevice.setFaceDetectionCallback(null, null);
1846 mCameraDevice.setErrorCallback(null, null);
1848 mFaceDetectionStarted = false;
1849 mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
1850 mCameraDevice = null;
1851 setCameraState(PREVIEW_STOPPED);
1852 mFocusManager.onCameraReleased();
1856 private void setDisplayOrientation() {
1857 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
1858 Characteristics info =
1859 mActivity.getCameraProvider().getCharacteristics(mCameraId);
1860 mDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, info);
1861 mCameraDisplayOrientation = mDisplayOrientation;
1862 mUI.setDisplayOrientation(mDisplayOrientation);
1863 if (mFocusManager != null) {
1864 mFocusManager.setDisplayOrientation(mDisplayOrientation);
1866 // Change the camera display orientation
1867 if (mCameraDevice != null) {
1868 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
1872 /** Only called by UI thread. */
1873 private void setupPreview() {
1874 mFocusManager.resetTouchFocus();
1879 * Returns whether we can/should start the preview or not.
1881 private boolean checkPreviewPreconditions() {
1886 if (mCameraDevice == null) {
1887 Log.w(TAG, "startPreview: camera device not ready yet.");
1891 SurfaceTexture st = mActivity.getCameraAppUI().getSurfaceTexture();
1893 Log.w(TAG, "startPreview: surfaceTexture is not ready.");
1897 if (!mCameraPreviewParamsReady) {
1898 Log.w(TAG, "startPreview: parameters for preview is not ready.");
1905 * The start/stop preview should only run on the UI thread.
1907 private void startPreview() {
1908 if (!checkPreviewPreconditions()) {
1912 mCameraDevice.setErrorCallback(mHandler, mErrorCallback);
1913 setDisplayOrientation();
1915 if (!mSnapshotOnIdle) {
1916 // If the focus mode is continuous autofocus, call cancelAutoFocus
1917 // to resume it because it may have been paused by autoFocus call.
1918 if (mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) ==
1919 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
1920 mCameraDevice.cancelAutoFocus();
1922 mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
1924 setCameraParameters(UPDATE_PARAM_ALL);
1925 mCameraDevice.setPreviewTexture(mActivity.getCameraAppUI().getSurfaceTexture());
1927 Log.i(TAG, "startPreview");
1928 mCameraDevice.startPreview();
1930 mFocusManager.onPreviewStarted();
1932 SessionStatsCollector.instance().previewActive(true);
1933 if (mSnapshotOnIdle) {
1934 mHandler.post(mDoSnapRunnable);
1939 public void stopPreview() {
1940 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1941 Log.i(TAG, "stopPreview");
1942 mCameraDevice.stopPreview();
1943 mFaceDetectionStarted = false;
1945 setCameraState(PREVIEW_STOPPED);
1946 if (mFocusManager != null) {
1947 mFocusManager.onPreviewStopped();
1949 SessionStatsCollector.instance().previewActive(false);
1953 public void onSettingChanged(SettingsManager settingsManager, String key) {
1954 if (key.equals(Keys.KEY_FLASH_MODE)) {
1955 updateParametersFlashMode();
1957 if (key.equals(Keys.KEY_CAMERA_HDR)) {
1958 if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1959 Keys.KEY_CAMERA_HDR)) {
1961 mAppController.getButtonManager().disableButton(ButtonManager.BUTTON_FLASH);
1962 mFlashModeBeforeSceneMode = settingsManager.getString(
1963 mAppController.getCameraScope(), Keys.KEY_FLASH_MODE);
1965 if (mFlashModeBeforeSceneMode != null) {
1966 settingsManager.set(mAppController.getCameraScope(),
1967 Keys.KEY_FLASH_MODE,
1968 mFlashModeBeforeSceneMode);
1969 updateParametersFlashMode();
1970 mFlashModeBeforeSceneMode = null;
1972 mAppController.getButtonManager().enableButton(ButtonManager.BUTTON_FLASH);
1976 if (mCameraDevice != null) {
1977 mCameraDevice.applySettings(mCameraSettings);
1981 private void updateCameraParametersInitialize() {
1982 // Reset preview frame rate to the maximum because it may be lowered by
1983 // video camera application.
1984 int[] fpsRange = CameraUtil.getPhotoPreviewFpsRange(mCameraCapabilities);
1985 if (fpsRange != null && fpsRange.length > 0) {
1986 mCameraSettings.setPreviewFpsRange(fpsRange[0], fpsRange[1]);
1989 mCameraSettings.setRecordingHintEnabled(false);
1991 if (mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_STABILIZATION)) {
1992 mCameraSettings.setVideoStabilization(false);
1996 private void updateCameraParametersZoom() {
1998 if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
1999 mCameraSettings.setZoomIndex(mZoomValue);
2003 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
2004 private void setAutoExposureLockIfSupported() {
2005 if (mAeLockSupported) {
2006 mCameraSettings.setAutoExposureLock(mFocusManager.getAeAwbLock());
2010 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
2011 private void setAutoWhiteBalanceLockIfSupported() {
2012 if (mAwbLockSupported) {
2013 mCameraSettings.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
2017 private void setFocusAreasIfSupported() {
2018 if (mFocusAreaSupported) {
2019 mCameraSettings.setFocusAreas(mFocusManager.getFocusAreas());
2023 private void setMeteringAreasIfSupported() {
2024 if (mMeteringAreaSupported) {
2025 mCameraSettings.setMeteringAreas(mFocusManager.getMeteringAreas());
2029 private void updateCameraParametersPreference() {
2030 setAutoExposureLockIfSupported();
2031 setAutoWhiteBalanceLockIfSupported();
2032 setFocusAreasIfSupported();
2033 setMeteringAreasIfSupported();
2035 // Initialize focus mode.
2036 mFocusManager.overrideFocusMode(null);
2038 .setFocusMode(mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
2039 SessionStatsCollector.instance().autofocusActive(
2040 mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) ==
2041 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE
2044 // Set picture size.
2045 updateParametersPictureSize();
2047 // Set JPEG quality.
2048 updateParametersPictureQuality();
2050 // For the following settings, we need to check if the settings are
2051 // still supported by latest driver, if not, ignore the settings.
2053 // Set exposure compensation
2054 updateParametersExposureCompensation();
2056 // Set the scene mode: also sets flash and white balance.
2057 updateParametersSceneMode();
2059 if (mContinuousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
2060 updateAutoFocusMoveCallback();
2064 private void updateParametersPictureSize() {
2065 SettingsManager settingsManager = mActivity.getSettingsManager();
2066 String pictureSizeKey = isCameraFrontFacing() ? Keys.KEY_PICTURE_SIZE_FRONT
2067 : Keys.KEY_PICTURE_SIZE_BACK;
2068 String pictureSize = settingsManager.getString(SettingsManager.SCOPE_GLOBAL,
2071 List<Size> supported = mCameraCapabilities.getSupportedPhotoSizes();
2072 CameraPictureSizesCacher.updateSizesForCamera(mAppController.getAndroidContext(),
2073 mCameraDevice.getCameraId(), supported);
2074 SettingsUtil.setCameraPictureSize(pictureSize, supported, mCameraSettings,
2075 mCameraDevice.getCameraId());
2077 Size size = SettingsUtil.getPhotoSize(pictureSize, supported,
2078 mCameraDevice.getCameraId());
2079 if (ApiHelper.IS_NEXUS_5) {
2080 if (ResolutionUtil.NEXUS_5_LARGE_16_BY_9.equals(pictureSize)) {
2081 mShouldResizeTo16x9 = true;
2083 mShouldResizeTo16x9 = false;
2087 // Set a preview size that is closest to the viewfinder height and has
2088 // the right aspect ratio.
2089 List<Size> sizes = mCameraCapabilities.getSupportedPreviewSizes();
2090 Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes,
2091 (double) size.width() / size.height());
2092 Size original = mCameraSettings.getCurrentPreviewSize();
2093 if (!optimalSize.equals(original)) {
2094 mCameraSettings.setPreviewSize(optimalSize);
2096 // Zoom related settings will be changed for different preview
2097 // sizes, so set and read the parameters to get latest values
2098 if (mHandler.getLooper() == Looper.myLooper()) {
2099 // On UI thread only, not when camera starts up
2102 mCameraDevice.applySettings(mCameraSettings);
2104 mCameraSettings = mCameraDevice.getSettings();
2107 if (optimalSize.width() != 0 && optimalSize.height() != 0) {
2108 mUI.updatePreviewAspectRatio((float) optimalSize.width()
2109 / (float) optimalSize.height());
2111 Log.i(TAG, "Preview size is " + optimalSize);
2114 private void updateParametersPictureQuality() {
2115 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
2116 CameraProfile.QUALITY_HIGH);
2117 mCameraSettings.setPhotoJpegCompressionQuality(jpegQuality);
2120 private void updateParametersExposureCompensation() {
2121 SettingsManager settingsManager = mActivity.getSettingsManager();
2122 if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
2123 Keys.KEY_EXPOSURE_COMPENSATION_ENABLED)) {
2124 int value = settingsManager.getInteger(mAppController.getCameraScope(),
2126 int max = mCameraCapabilities.getMaxExposureCompensation();
2127 int min = mCameraCapabilities.getMinExposureCompensation();
2128 if (value >= min && value <= max) {
2129 mCameraSettings.setExposureCompensationIndex(value);
2131 Log.w(TAG, "invalid exposure range: " + value);
2134 // If exposure compensation is not enabled, reset the exposure compensation value.
2135 setExposureCompensation(0);
2140 private void updateParametersSceneMode() {
2141 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
2142 SettingsManager settingsManager = mActivity.getSettingsManager();
2144 mSceneMode = stringifier.
2145 sceneModeFromString(settingsManager.getString(mAppController.getCameraScope(),
2146 Keys.KEY_SCENE_MODE));
2147 if (mCameraCapabilities.supports(mSceneMode)) {
2148 if (mCameraSettings.getCurrentSceneMode() != mSceneMode) {
2149 mCameraSettings.setSceneMode(mSceneMode);
2151 // Setting scene mode will change the settings of flash mode,
2152 // white balance, and focus mode. Here we read back the
2153 // parameters, so we can know those settings.
2154 mCameraDevice.applySettings(mCameraSettings);
2155 mCameraSettings = mCameraDevice.getSettings();
2158 mSceneMode = mCameraSettings.getCurrentSceneMode();
2159 if (mSceneMode == null) {
2160 mSceneMode = CameraCapabilities.SceneMode.AUTO;
2164 if (CameraCapabilities.SceneMode.AUTO == mSceneMode) {
2166 updateParametersFlashMode();
2169 mFocusManager.overrideFocusMode(null);
2170 mCameraSettings.setFocusMode(
2171 mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
2173 mFocusManager.overrideFocusMode(mCameraSettings.getCurrentFocusMode());
2177 private void updateParametersFlashMode() {
2178 SettingsManager settingsManager = mActivity.getSettingsManager();
2180 CameraCapabilities.FlashMode flashMode = mCameraCapabilities.getStringifier()
2181 .flashModeFromString(settingsManager.getString(mAppController.getCameraScope(),
2182 Keys.KEY_FLASH_MODE));
2183 if (mCameraCapabilities.supports(flashMode)) {
2184 mCameraSettings.setFlashMode(flashMode);
2188 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
2189 private void updateAutoFocusMoveCallback() {
2190 if (mCameraSettings.getCurrentFocusMode() ==
2191 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
2192 mCameraDevice.setAutoFocusMoveCallback(mHandler,
2193 (CameraAFMoveCallback) mAutoFocusMoveCallback);
2195 mCameraDevice.setAutoFocusMoveCallback(null, null);
2200 * Sets the exposure compensation to the given value and also updates settings.
2202 * @param value exposure compensation value to be set
2204 public void setExposureCompensation(int value) {
2205 int max = mCameraCapabilities.getMaxExposureCompensation();
2206 int min = mCameraCapabilities.getMinExposureCompensation();
2207 if (value >= min && value <= max) {
2208 mCameraSettings.setExposureCompensationIndex(value);
2209 SettingsManager settingsManager = mActivity.getSettingsManager();
2210 settingsManager.set(mAppController.getCameraScope(),
2211 Keys.KEY_EXPOSURE, value);
2213 Log.w(TAG, "invalid exposure range: " + value);
2217 // We separate the parameters into several subsets, so we can update only
2218 // the subsets actually need updating. The PREFERENCE set needs extra
2219 // locking because the preference can be changed from GLThread as well.
2220 private void setCameraParameters(int updateSet) {
2221 if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
2222 updateCameraParametersInitialize();
2225 if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
2226 updateCameraParametersZoom();
2229 if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
2230 updateCameraParametersPreference();
2233 mCameraDevice.applySettings(mCameraSettings);
2236 // If the Camera is idle, update the parameters immediately, otherwise
2237 // accumulate them in mUpdateSet and update later.
2238 private void setCameraParametersWhenIdle(int additionalUpdateSet) {
2239 mUpdateSet |= additionalUpdateSet;
2240 if (mCameraDevice == null) {
2241 // We will update all the parameters when we open the device, so
2242 // we don't need to do anything now.
2245 } else if (isCameraIdle()) {
2246 setCameraParameters(mUpdateSet);
2250 if (!mHandler.hasMessages(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
2251 mHandler.sendEmptyMessageDelayed(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
2257 public boolean isCameraIdle() {
2258 return (mCameraState == IDLE) ||
2259 (mCameraState == PREVIEW_STOPPED) ||
2260 ((mFocusManager != null) && mFocusManager.isFocusCompleted()
2261 && (mCameraState != SWITCHING_CAMERA));
2265 public boolean isImageCaptureIntent() {
2266 String action = mActivity.getIntent().getAction();
2267 return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
2268 || CameraActivity.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
2271 private void setupCaptureParams() {
2272 Bundle myExtras = mActivity.getIntent().getExtras();
2273 if (myExtras != null) {
2274 mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
2275 mCropValue = myExtras.getString("crop");
2279 private void initializeCapabilities() {
2280 mCameraCapabilities = mCameraDevice.getCapabilities();
2281 mFocusAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA);
2282 mMeteringAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.METERING_AREA);
2283 mAeLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_EXPOSURE_LOCK);
2284 mAwbLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_WHITE_BALANCE_LOCK);
2285 mContinuousFocusSupported =
2286 mCameraCapabilities.supports(CameraCapabilities.FocusMode.CONTINUOUS_PICTURE);
2289 // TODO: Remove this
2291 public int onZoomChanged(int index) {
2292 // Not useful to change zoom value when the activity is paused.
2297 if (mCameraSettings == null || mCameraDevice == null) {
2300 // Set zoom parameters asynchronously
2301 mCameraSettings.setZoomRatio((float) mZoomValue);
2302 mCameraDevice.applySettings(mCameraSettings);
2303 CameraSettings settings = mCameraDevice.getSettings();
2304 if (settings != null) {
2305 return settings.getCurrentZoomIndex();
2311 public int getCameraState() {
2312 return mCameraState;
2316 public void onMemoryStateChanged(int state) {
2317 mAppController.setShutterEnabled(state == MemoryManager.STATE_OK);
2321 public void onLowMemory() {
2322 // Not much we can do in the photo module.
2326 public void onAccuracyChanged(Sensor sensor, int accuracy) {
2330 public void onSensorChanged(SensorEvent event) {
2331 int type = event.sensor.getType();
2333 if (type == Sensor.TYPE_ACCELEROMETER) {
2335 } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
2338 // we should not be here.
2341 for (int i = 0; i < 3; i++) {
2342 data[i] = event.values[i];
2344 float[] orientation = new float[3];
2345 SensorManager.getRotationMatrix(mR, null, mGData, mMData);
2346 SensorManager.getOrientation(mR, orientation);
2347 mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
2353 // For debugging only.
2354 public void setDebugUri(Uri uri) {
2358 // For debugging only.
2359 private void saveToDebugUri(byte[] data) {
2360 if (mDebugUri != null) {
2361 OutputStream outputStream = null;
2363 outputStream = mContentResolver.openOutputStream(mDebugUri);
2364 outputStream.write(data);
2365 outputStream.close();
2366 } catch (IOException e) {
2367 Log.e(TAG, "Exception while writing debug jpeg file", e);
2369 CameraUtil.closeSilently(outputStream);
2375 public void onRemoteShutterPress() {
2376 mHandler.post(new Runnable() {
2385 * This class manages the loading/releasing/playing of the sounds needed for
2388 private class CountdownSoundPlayer {
2389 private SoundPool mSoundPool;
2390 private int mBeepOnce;
2391 private int mBeepTwice;
2395 if (mSoundPool == null) {
2396 mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0);
2397 mBeepOnce = mSoundPool.load(mAppController.getAndroidContext(), R.raw.beep_once, 1);
2398 mBeepTwice = mSoundPool.load(mAppController.getAndroidContext(), R.raw.beep_twice, 1);
2402 void onRemainingSecondsChanged(int newVal) {
2403 if (mSoundPool == null) {
2404 Log.e(TAG, "Cannot play sound - they have not been loaded.");
2408 mSoundPool.play(mBeepTwice, 1.0f, 1.0f, 0, 0, 1.0f);
2409 } else if (newVal == 2 || newVal == 3) {
2410 mSoundPool.play(mBeepOnce, 1.0f, 1.0f, 0, 0, 1.0f);
2415 if (mSoundPool != null) {
2416 mSoundPool.release();