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 mAppController.getCameraAppUI().transitionToCapture();
1549 mAppController.getCameraAppUI().showModeOptions();
1556 private void onResumeTasks() {
1560 Log.v(TAG, "Executing onResumeTasks.");
1561 CameraProvider camProvider = mActivity.getCameraProvider();
1562 if (camProvider == null) {
1563 // No camera provider, the Activity is destroyed already.
1566 camProvider.requestCamera(mCameraId);
1568 mJpegPictureCallbackTime = 0;
1571 mOnResumeTime = SystemClock.uptimeMillis();
1572 checkDisplayRotation();
1574 // If first time initialization is not finished, put it in the
1576 if (!mFirstTimeInitialized) {
1577 mHandler.sendEmptyMessage(MSG_FIRST_TIME_INIT);
1579 initializeSecondTime();
1582 Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
1583 if (gsensor != null) {
1584 mSensorManager.registerListener(this, gsensor, SensorManager.SENSOR_DELAY_NORMAL);
1587 Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
1588 if (msensor != null) {
1589 mSensorManager.registerListener(this, msensor, SensorManager.SENSOR_DELAY_NORMAL);
1594 * @return Whether the currently active camera is front-facing.
1596 private boolean isCameraFrontFacing() {
1597 return mAppController.getCameraProvider().getCharacteristics(mCameraId)
1602 * The focus manager is the first UI related element to get initialized, and
1603 * it requires the RenderOverlay, so initialize it here
1605 private void initializeFocusManager() {
1606 // Create FocusManager object. startPreview needs it.
1607 // if mFocusManager not null, reuse it
1608 // otherwise create a new instance
1609 if (mFocusManager != null) {
1610 mFocusManager.removeMessages();
1612 mMirror = isCameraFrontFacing();
1613 String[] defaultFocusModesStrings = mActivity.getResources().getStringArray(
1614 R.array.pref_camera_focusmode_default_array);
1615 ArrayList<CameraCapabilities.FocusMode> defaultFocusModes = new ArrayList<>();
1616 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
1617 for (String modeString : defaultFocusModesStrings) {
1618 CameraCapabilities.FocusMode mode = stringifier.focusModeFromString(modeString);
1620 defaultFocusModes.add(mode);
1624 new FocusOverlayManager(mAppController, defaultFocusModes,
1625 mCameraCapabilities, this, mMirror, mActivity.getMainLooper(),
1627 MotionManager motionManager = getServices().getMotionManager();
1628 if (motionManager != null) {
1629 motionManager.addListener(mFocusManager);
1632 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1636 public void resume() {
1638 mCountdownSoundPlayer.loadSounds();
1639 if (mFocusManager != null) {
1640 // If camera is not open when resume is called, focus manager will
1642 // be initialized yet, in which case it will start listening to
1643 // preview area size change later in the initialization.
1644 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1646 mAppController.addPreviewAreaSizeChangedListener(mUI);
1648 // Add delay on resume from lock screen only, in order to to speed up
1649 // the onResume --> onPause --> onResume cycle from lock screen.
1650 // Don't do always because letting go of thread can cause delay.
1651 String action = mActivity.getIntent().getAction();
1652 if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action)
1653 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) {
1654 Log.v(TAG, "On resume, from lock screen.");
1655 // Note: onPauseAfterSuper() will delete this runnable, so we will
1656 // at most have 1 copy queued up.
1657 mHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC);
1659 Log.v(TAG, "On resume.");
1662 getServices().getRemoteShutterListener().onModuleReady(this);
1663 SessionStatsCollector.instance().sessionActive(true);
1667 public void pause() {
1669 mHandler.removeCallbacks(mResumeTaskRunnable);
1670 getServices().getRemoteShutterListener().onModuleExit();
1671 SessionStatsCollector.instance().sessionActive(false);
1673 Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
1674 if (gsensor != null) {
1675 mSensorManager.unregisterListener(this, gsensor);
1678 Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
1679 if (msensor != null) {
1680 mSensorManager.unregisterListener(this, msensor);
1683 // Reset the focus first. Camera CTS does not guarantee that
1684 // cancelAutoFocus is allowed after preview stops.
1685 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1686 mCameraDevice.cancelAutoFocus();
1689 // If the camera has not been opened asynchronously yet,
1690 // and startPreview hasn't been called, then this is a no-op.
1691 // (e.g. onResume -> onPause -> onResume).
1694 mCountdownSoundPlayer.release();
1696 mNamedImages = null;
1697 // If we are in an image capture intent and has taken
1698 // a picture, we just clear it in onPause.
1699 mJpegImageData = null;
1701 // Remove the messages and runnables in the queue.
1702 mHandler.removeCallbacksAndMessages(null);
1705 mActivity.enableKeepScreenOn(false);
1708 mPendingSwitchCameraId = -1;
1709 if (mFocusManager != null) {
1710 mFocusManager.removeMessages();
1712 getServices().getMemoryManager().removeListener(this);
1713 mAppController.removePreviewAreaSizeChangedListener(mFocusManager);
1714 mAppController.removePreviewAreaSizeChangedListener(mUI);
1716 SettingsManager settingsManager = mActivity.getSettingsManager();
1717 settingsManager.removeListener(this);
1721 public void destroy() {
1722 // TODO: implement this.
1726 public void onLayoutOrientationChanged(boolean isLandscape) {
1727 setDisplayOrientation();
1731 public void updateCameraOrientation() {
1732 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
1733 setDisplayOrientation();
1737 private boolean canTakePicture() {
1738 return isCameraIdle()
1739 && (mActivity.getStorageSpaceBytes() > Storage.LOW_STORAGE_THRESHOLD_BYTES);
1743 public void autoFocus() {
1744 Log.v(TAG,"Starting auto focus");
1745 mFocusStartTime = System.currentTimeMillis();
1746 mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
1747 SessionStatsCollector.instance().autofocusManualTrigger();
1748 setCameraState(FOCUSING);
1752 public void cancelAutoFocus() {
1753 mCameraDevice.cancelAutoFocus();
1754 setCameraState(IDLE);
1755 setCameraParameters(UPDATE_PARAM_PREFERENCE);
1759 public void onSingleTapUp(View view, int x, int y) {
1760 if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
1761 || mCameraState == SNAPSHOT_IN_PROGRESS
1762 || mCameraState == SWITCHING_CAMERA
1763 || mCameraState == PREVIEW_STOPPED) {
1767 // Check if metering area or focus area is supported.
1768 if (!mFocusAreaSupported && !mMeteringAreaSupported) {
1771 mFocusManager.onSingleTapUp(x, y);
1775 public boolean onBackPressed() {
1776 return mUI.onBackPressed();
1780 public boolean onKeyDown(int keyCode, KeyEvent event) {
1782 case KeyEvent.KEYCODE_VOLUME_UP:
1783 case KeyEvent.KEYCODE_VOLUME_DOWN:
1784 case KeyEvent.KEYCODE_FOCUS:
1785 if (/* TODO: mActivity.isInCameraApp() && */mFirstTimeInitialized) {
1786 if (event.getRepeatCount() == 0) {
1787 onShutterButtonFocus(true);
1792 case KeyEvent.KEYCODE_CAMERA:
1793 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1794 onShutterButtonClick();
1797 case KeyEvent.KEYCODE_DPAD_CENTER:
1798 // If we get a dpad center event without any focused view, move
1799 // the focus to the shutter button and press it.
1800 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1801 // Start auto-focus immediately to reduce shutter lag. After
1802 // the shutter button gets the focus, onShutterButtonFocus()
1803 // will be called again but it is fine.
1804 onShutterButtonFocus(true);
1812 public boolean onKeyUp(int keyCode, KeyEvent event) {
1814 case KeyEvent.KEYCODE_VOLUME_UP:
1815 case KeyEvent.KEYCODE_VOLUME_DOWN:
1816 if (/* mActivity.isInCameraApp() && */mFirstTimeInitialized) {
1817 mVolumeButtonClickedFlag = true;
1818 onShutterButtonClick();
1822 case KeyEvent.KEYCODE_FOCUS:
1823 if (mFirstTimeInitialized) {
1824 onShutterButtonFocus(false);
1831 private void closeCamera() {
1832 if (mCameraDevice != null) {
1833 stopFaceDetection();
1834 mCameraDevice.setZoomChangeListener(null);
1835 mCameraDevice.setFaceDetectionCallback(null, null);
1836 mCameraDevice.setErrorCallback(null, null);
1838 mFaceDetectionStarted = false;
1839 mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
1840 mCameraDevice = null;
1841 setCameraState(PREVIEW_STOPPED);
1842 mFocusManager.onCameraReleased();
1846 private void setDisplayOrientation() {
1847 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
1848 Characteristics info =
1849 mActivity.getCameraProvider().getCharacteristics(mCameraId);
1850 mDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, info);
1851 mCameraDisplayOrientation = mDisplayOrientation;
1852 mUI.setDisplayOrientation(mDisplayOrientation);
1853 if (mFocusManager != null) {
1854 mFocusManager.setDisplayOrientation(mDisplayOrientation);
1856 // Change the camera display orientation
1857 if (mCameraDevice != null) {
1858 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
1862 /** Only called by UI thread. */
1863 private void setupPreview() {
1864 mFocusManager.resetTouchFocus();
1869 * Returns whether we can/should start the preview or not.
1871 private boolean checkPreviewPreconditions() {
1876 if (mCameraDevice == null) {
1877 Log.w(TAG, "startPreview: camera device not ready yet.");
1881 SurfaceTexture st = mActivity.getCameraAppUI().getSurfaceTexture();
1883 Log.w(TAG, "startPreview: surfaceTexture is not ready.");
1887 if (!mCameraPreviewParamsReady) {
1888 Log.w(TAG, "startPreview: parameters for preview is not ready.");
1895 * The start/stop preview should only run on the UI thread.
1897 private void startPreview() {
1898 if (!checkPreviewPreconditions()) {
1902 mCameraDevice.setErrorCallback(mHandler, mErrorCallback);
1903 setDisplayOrientation();
1905 if (!mSnapshotOnIdle) {
1906 // If the focus mode is continuous autofocus, call cancelAutoFocus
1907 // to resume it because it may have been paused by autoFocus call.
1908 if (mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) ==
1909 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
1910 mCameraDevice.cancelAutoFocus();
1912 mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
1914 setCameraParameters(UPDATE_PARAM_ALL);
1915 mCameraDevice.setPreviewTexture(mActivity.getCameraAppUI().getSurfaceTexture());
1917 Log.i(TAG, "startPreview");
1918 mCameraDevice.startPreview();
1920 mFocusManager.onPreviewStarted();
1922 SessionStatsCollector.instance().previewActive(true);
1923 if (mSnapshotOnIdle) {
1924 mHandler.post(mDoSnapRunnable);
1929 public void stopPreview() {
1930 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1931 Log.i(TAG, "stopPreview");
1932 mCameraDevice.stopPreview();
1933 mFaceDetectionStarted = false;
1935 setCameraState(PREVIEW_STOPPED);
1936 if (mFocusManager != null) {
1937 mFocusManager.onPreviewStopped();
1939 SessionStatsCollector.instance().previewActive(false);
1943 public void onSettingChanged(SettingsManager settingsManager, String key) {
1944 if (key.equals(Keys.KEY_FLASH_MODE)) {
1945 updateParametersFlashMode();
1947 if (key.equals(Keys.KEY_CAMERA_HDR)) {
1948 if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1949 Keys.KEY_CAMERA_HDR)) {
1951 mAppController.getButtonManager().disableButton(ButtonManager.BUTTON_FLASH);
1952 mFlashModeBeforeSceneMode = settingsManager.getString(
1953 mAppController.getCameraScope(), Keys.KEY_FLASH_MODE);
1955 if (mFlashModeBeforeSceneMode != null) {
1956 settingsManager.set(mAppController.getCameraScope(),
1957 Keys.KEY_FLASH_MODE,
1958 mFlashModeBeforeSceneMode);
1959 updateParametersFlashMode();
1960 mFlashModeBeforeSceneMode = null;
1962 mAppController.getButtonManager().enableButton(ButtonManager.BUTTON_FLASH);
1966 if (mCameraDevice != null) {
1967 mCameraDevice.applySettings(mCameraSettings);
1971 private void updateCameraParametersInitialize() {
1972 // Reset preview frame rate to the maximum because it may be lowered by
1973 // video camera application.
1974 int[] fpsRange = CameraUtil.getPhotoPreviewFpsRange(mCameraCapabilities);
1975 if (fpsRange != null && fpsRange.length > 0) {
1976 mCameraSettings.setPreviewFpsRange(fpsRange[0], fpsRange[1]);
1979 mCameraSettings.setRecordingHintEnabled(false);
1981 if (mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_STABILIZATION)) {
1982 mCameraSettings.setVideoStabilization(false);
1986 private void updateCameraParametersZoom() {
1988 if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
1989 mCameraSettings.setZoomIndex(mZoomValue);
1993 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1994 private void setAutoExposureLockIfSupported() {
1995 if (mAeLockSupported) {
1996 mCameraSettings.setAutoExposureLock(mFocusManager.getAeAwbLock());
2000 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
2001 private void setAutoWhiteBalanceLockIfSupported() {
2002 if (mAwbLockSupported) {
2003 mCameraSettings.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
2007 private void setFocusAreasIfSupported() {
2008 if (mFocusAreaSupported) {
2009 mCameraSettings.setFocusAreas(mFocusManager.getFocusAreas());
2013 private void setMeteringAreasIfSupported() {
2014 if (mMeteringAreaSupported) {
2015 mCameraSettings.setMeteringAreas(mFocusManager.getMeteringAreas());
2019 private void updateCameraParametersPreference() {
2020 setAutoExposureLockIfSupported();
2021 setAutoWhiteBalanceLockIfSupported();
2022 setFocusAreasIfSupported();
2023 setMeteringAreasIfSupported();
2025 // Initialize focus mode.
2026 mFocusManager.overrideFocusMode(null);
2028 .setFocusMode(mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
2029 SessionStatsCollector.instance().autofocusActive(
2030 mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) ==
2031 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE
2034 // Set picture size.
2035 updateParametersPictureSize();
2037 // Set JPEG quality.
2038 updateParametersPictureQuality();
2040 // For the following settings, we need to check if the settings are
2041 // still supported by latest driver, if not, ignore the settings.
2043 // Set exposure compensation
2044 updateParametersExposureCompensation();
2046 // Set the scene mode: also sets flash and white balance.
2047 updateParametersSceneMode();
2049 if (mContinuousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
2050 updateAutoFocusMoveCallback();
2054 private void updateParametersPictureSize() {
2055 SettingsManager settingsManager = mActivity.getSettingsManager();
2056 String pictureSizeKey = isCameraFrontFacing() ? Keys.KEY_PICTURE_SIZE_FRONT
2057 : Keys.KEY_PICTURE_SIZE_BACK;
2058 String pictureSize = settingsManager.getString(SettingsManager.SCOPE_GLOBAL,
2061 List<Size> supported = mCameraCapabilities.getSupportedPhotoSizes();
2062 CameraPictureSizesCacher.updateSizesForCamera(mAppController.getAndroidContext(),
2063 mCameraDevice.getCameraId(), supported);
2064 SettingsUtil.setCameraPictureSize(pictureSize, supported, mCameraSettings,
2065 mCameraDevice.getCameraId());
2067 Size size = SettingsUtil.getPhotoSize(pictureSize, supported,
2068 mCameraDevice.getCameraId());
2069 if (ApiHelper.IS_NEXUS_5) {
2070 if (ResolutionUtil.NEXUS_5_LARGE_16_BY_9.equals(pictureSize)) {
2071 mShouldResizeTo16x9 = true;
2073 mShouldResizeTo16x9 = false;
2077 // Set a preview size that is closest to the viewfinder height and has
2078 // the right aspect ratio.
2079 List<Size> sizes = mCameraCapabilities.getSupportedPreviewSizes();
2080 Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes,
2081 (double) size.width() / size.height());
2082 Size original = mCameraSettings.getCurrentPreviewSize();
2083 if (!optimalSize.equals(original)) {
2084 mCameraSettings.setPreviewSize(optimalSize);
2086 // Zoom related settings will be changed for different preview
2087 // sizes, so set and read the parameters to get latest values
2088 if (mHandler.getLooper() == Looper.myLooper()) {
2089 // On UI thread only, not when camera starts up
2092 mCameraDevice.applySettings(mCameraSettings);
2094 mCameraSettings = mCameraDevice.getSettings();
2097 if (optimalSize.width() != 0 && optimalSize.height() != 0) {
2098 mUI.updatePreviewAspectRatio((float) optimalSize.width()
2099 / (float) optimalSize.height());
2101 Log.i(TAG, "Preview size is " + optimalSize);
2104 private void updateParametersPictureQuality() {
2105 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
2106 CameraProfile.QUALITY_HIGH);
2107 mCameraSettings.setPhotoJpegCompressionQuality(jpegQuality);
2110 private void updateParametersExposureCompensation() {
2111 SettingsManager settingsManager = mActivity.getSettingsManager();
2112 if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
2113 Keys.KEY_EXPOSURE_COMPENSATION_ENABLED)) {
2114 int value = settingsManager.getInteger(mAppController.getCameraScope(),
2116 int max = mCameraCapabilities.getMaxExposureCompensation();
2117 int min = mCameraCapabilities.getMinExposureCompensation();
2118 if (value >= min && value <= max) {
2119 mCameraSettings.setExposureCompensationIndex(value);
2121 Log.w(TAG, "invalid exposure range: " + value);
2124 // If exposure compensation is not enabled, reset the exposure compensation value.
2125 setExposureCompensation(0);
2130 private void updateParametersSceneMode() {
2131 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
2132 SettingsManager settingsManager = mActivity.getSettingsManager();
2134 mSceneMode = stringifier.
2135 sceneModeFromString(settingsManager.getString(mAppController.getCameraScope(),
2136 Keys.KEY_SCENE_MODE));
2137 if (mCameraCapabilities.supports(mSceneMode)) {
2138 if (mCameraSettings.getCurrentSceneMode() != mSceneMode) {
2139 mCameraSettings.setSceneMode(mSceneMode);
2141 // Setting scene mode will change the settings of flash mode,
2142 // white balance, and focus mode. Here we read back the
2143 // parameters, so we can know those settings.
2144 mCameraDevice.applySettings(mCameraSettings);
2145 mCameraSettings = mCameraDevice.getSettings();
2148 mSceneMode = mCameraSettings.getCurrentSceneMode();
2149 if (mSceneMode == null) {
2150 mSceneMode = CameraCapabilities.SceneMode.AUTO;
2154 if (CameraCapabilities.SceneMode.AUTO == mSceneMode) {
2156 updateParametersFlashMode();
2159 mFocusManager.overrideFocusMode(null);
2160 mCameraSettings.setFocusMode(
2161 mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
2163 mFocusManager.overrideFocusMode(mCameraSettings.getCurrentFocusMode());
2167 private void updateParametersFlashMode() {
2168 SettingsManager settingsManager = mActivity.getSettingsManager();
2170 CameraCapabilities.FlashMode flashMode = mCameraCapabilities.getStringifier()
2171 .flashModeFromString(settingsManager.getString(mAppController.getCameraScope(),
2172 Keys.KEY_FLASH_MODE));
2173 if (mCameraCapabilities.supports(flashMode)) {
2174 mCameraSettings.setFlashMode(flashMode);
2178 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
2179 private void updateAutoFocusMoveCallback() {
2180 if (mCameraSettings.getCurrentFocusMode() ==
2181 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
2182 mCameraDevice.setAutoFocusMoveCallback(mHandler,
2183 (CameraAFMoveCallback) mAutoFocusMoveCallback);
2185 mCameraDevice.setAutoFocusMoveCallback(null, null);
2190 * Sets the exposure compensation to the given value and also updates settings.
2192 * @param value exposure compensation value to be set
2194 public void setExposureCompensation(int value) {
2195 int max = mCameraCapabilities.getMaxExposureCompensation();
2196 int min = mCameraCapabilities.getMinExposureCompensation();
2197 if (value >= min && value <= max) {
2198 mCameraSettings.setExposureCompensationIndex(value);
2199 SettingsManager settingsManager = mActivity.getSettingsManager();
2200 settingsManager.set(mAppController.getCameraScope(),
2201 Keys.KEY_EXPOSURE, value);
2203 Log.w(TAG, "invalid exposure range: " + value);
2207 // We separate the parameters into several subsets, so we can update only
2208 // the subsets actually need updating. The PREFERENCE set needs extra
2209 // locking because the preference can be changed from GLThread as well.
2210 private void setCameraParameters(int updateSet) {
2211 if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
2212 updateCameraParametersInitialize();
2215 if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
2216 updateCameraParametersZoom();
2219 if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
2220 updateCameraParametersPreference();
2223 mCameraDevice.applySettings(mCameraSettings);
2226 // If the Camera is idle, update the parameters immediately, otherwise
2227 // accumulate them in mUpdateSet and update later.
2228 private void setCameraParametersWhenIdle(int additionalUpdateSet) {
2229 mUpdateSet |= additionalUpdateSet;
2230 if (mCameraDevice == null) {
2231 // We will update all the parameters when we open the device, so
2232 // we don't need to do anything now.
2235 } else if (isCameraIdle()) {
2236 setCameraParameters(mUpdateSet);
2240 if (!mHandler.hasMessages(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
2241 mHandler.sendEmptyMessageDelayed(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
2247 public boolean isCameraIdle() {
2248 return (mCameraState == IDLE) ||
2249 (mCameraState == PREVIEW_STOPPED) ||
2250 ((mFocusManager != null) && mFocusManager.isFocusCompleted()
2251 && (mCameraState != SWITCHING_CAMERA));
2255 public boolean isImageCaptureIntent() {
2256 String action = mActivity.getIntent().getAction();
2257 return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
2258 || CameraActivity.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
2261 private void setupCaptureParams() {
2262 Bundle myExtras = mActivity.getIntent().getExtras();
2263 if (myExtras != null) {
2264 mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
2265 mCropValue = myExtras.getString("crop");
2269 private void initializeCapabilities() {
2270 mCameraCapabilities = mCameraDevice.getCapabilities();
2271 mFocusAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA);
2272 mMeteringAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.METERING_AREA);
2273 mAeLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_EXPOSURE_LOCK);
2274 mAwbLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_WHITE_BALANCE_LOCK);
2275 mContinuousFocusSupported =
2276 mCameraCapabilities.supports(CameraCapabilities.FocusMode.CONTINUOUS_PICTURE);
2279 // TODO: Remove this
2281 public int onZoomChanged(int index) {
2282 // Not useful to change zoom value when the activity is paused.
2287 if (mCameraSettings == null || mCameraDevice == null) {
2290 // Set zoom parameters asynchronously
2291 mCameraSettings.setZoomRatio((float) mZoomValue);
2292 mCameraDevice.applySettings(mCameraSettings);
2293 CameraSettings settings = mCameraDevice.getSettings();
2294 if (settings != null) {
2295 return settings.getCurrentZoomIndex();
2301 public int getCameraState() {
2302 return mCameraState;
2306 public void onMemoryStateChanged(int state) {
2307 mAppController.setShutterEnabled(state == MemoryManager.STATE_OK);
2311 public void onLowMemory() {
2312 // Not much we can do in the photo module.
2316 public void onAccuracyChanged(Sensor sensor, int accuracy) {
2320 public void onSensorChanged(SensorEvent event) {
2321 int type = event.sensor.getType();
2323 if (type == Sensor.TYPE_ACCELEROMETER) {
2325 } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
2328 // we should not be here.
2331 for (int i = 0; i < 3; i++) {
2332 data[i] = event.values[i];
2334 float[] orientation = new float[3];
2335 SensorManager.getRotationMatrix(mR, null, mGData, mMData);
2336 SensorManager.getOrientation(mR, orientation);
2337 mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
2343 // For debugging only.
2344 public void setDebugUri(Uri uri) {
2348 // For debugging only.
2349 private void saveToDebugUri(byte[] data) {
2350 if (mDebugUri != null) {
2351 OutputStream outputStream = null;
2353 outputStream = mContentResolver.openOutputStream(mDebugUri);
2354 outputStream.write(data);
2355 outputStream.close();
2356 } catch (IOException e) {
2357 Log.e(TAG, "Exception while writing debug jpeg file", e);
2359 CameraUtil.closeSilently(outputStream);
2365 public void onRemoteShutterPress() {
2366 mHandler.post(new Runnable() {
2375 * This class manages the loading/releasing/playing of the sounds needed for
2378 private class CountdownSoundPlayer {
2379 private SoundPool mSoundPool;
2380 private int mBeepOnce;
2381 private int mBeepTwice;
2385 if (mSoundPool == null) {
2386 mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0);
2387 mBeepOnce = mSoundPool.load(mAppController.getAndroidContext(), R.raw.beep_once, 1);
2388 mBeepTwice = mSoundPool.load(mAppController.getAndroidContext(), R.raw.beep_twice, 1);
2392 void onRemainingSecondsChanged(int newVal) {
2393 if (mSoundPool == null) {
2394 Log.e(TAG, "Cannot play sound - they have not been loaded.");
2398 mSoundPool.play(mBeepTwice, 1.0f, 1.0f, 0, 0, 1.0f);
2399 } else if (newVal == 2 || newVal == 3) {
2400 mSoundPool.play(mBeepOnce, 1.0f, 1.0f, 0, 0, 1.0f);
2405 if (mSoundPool != null) {
2406 mSoundPool.release();