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.GservicesHelper;
76 import com.android.camera.util.SessionStatsCollector;
77 import com.android.camera.util.UsageStatistics;
78 import com.android.camera.widget.AspectRatioSelector;
79 import com.android.camera2.R;
80 import com.android.ex.camera2.portability.CameraAgent.CameraAFCallback;
81 import com.android.ex.camera2.portability.CameraAgent.CameraAFMoveCallback;
82 import com.android.ex.camera2.portability.CameraAgent.CameraPictureCallback;
83 import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
84 import com.android.ex.camera2.portability.CameraAgent.CameraShutterCallback;
85 import com.android.ex.camera2.portability.CameraCapabilities;
86 import com.android.ex.camera2.portability.CameraDeviceInfo.Characteristics;
87 import com.android.ex.camera2.portability.CameraSettings;
88 import com.android.ex.camera2.portability.Size;
89 import com.google.common.logging.eventprotos;
91 import java.io.ByteArrayOutputStream;
93 import java.io.FileNotFoundException;
94 import java.io.FileOutputStream;
95 import java.io.IOException;
96 import java.io.OutputStream;
97 import java.lang.ref.WeakReference;
98 import java.util.ArrayList;
99 import java.util.List;
100 import java.util.Vector;
102 public class PhotoModule
104 implements PhotoController,
107 FocusOverlayManager.Listener,
109 SettingsManager.OnSettingChangedListener,
111 CountDownView.OnCountDownStatusListener {
113 private static final String PHOTO_MODULE_STRING_ID = "PhotoModule";
115 private static final Log.Tag TAG = new Log.Tag(PHOTO_MODULE_STRING_ID);
117 // We number the request code from 1000 to avoid collision with Gallery.
118 private static final int REQUEST_CROP = 1000;
120 // Messages defined for the UI thread handler.
121 private static final int MSG_FIRST_TIME_INIT = 1;
122 private static final int MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE = 2;
124 // The subset of parameters we need to update in setCameraParameters().
125 private static final int UPDATE_PARAM_INITIALIZE = 1;
126 private static final int UPDATE_PARAM_ZOOM = 2;
127 private static final int UPDATE_PARAM_PREFERENCE = 4;
128 private static final int UPDATE_PARAM_ALL = -1;
130 // This is the delay before we execute onResume tasks when coming
131 // from the lock screen, to allow time for onPause to execute.
132 private static final int ON_RESUME_TASKS_DELAY_MSEC = 20;
134 private static final String DEBUG_IMAGE_PREFIX = "DEBUG_";
136 private CameraActivity mActivity;
137 private CameraProxy mCameraDevice;
138 private int mCameraId;
139 private CameraCapabilities mCameraCapabilities;
140 private CameraSettings mCameraSettings;
141 private boolean mPaused;
145 // The activity is going to switch to the specified camera id. This is
146 // needed because texture copy is done in GL thread. -1 means camera is not
148 protected int mPendingSwitchCameraId = -1;
150 // When setCameraParametersWhenIdle() is called, we accumulate the subsets
151 // needed to be updated in mUpdateSet.
152 private int mUpdateSet;
154 private int mZoomValue; // The current zoom value.
155 private int mTimerDuration;
156 /** Set when a volume button is clicked to take photo */
157 private boolean mVolumeButtonClickedFlag = false;
159 private boolean mFocusAreaSupported;
160 private boolean mMeteringAreaSupported;
161 private boolean mAeLockSupported;
162 private boolean mAwbLockSupported;
163 private boolean mContinuousFocusSupported;
165 // The degrees of the device rotated clockwise from its natural orientation.
166 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
168 private static final String sTempCropFilename = "crop-temp";
170 private boolean mFaceDetectionStarted = false;
172 // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
173 private String mCropValue;
174 private Uri mSaveUri;
176 private Uri mDebugUri;
178 // We use a queue to generated names of the images to be used later
179 // when the image is ready to be saved.
180 private NamedImages mNamedImages;
182 private final Runnable mDoSnapRunnable = new Runnable() {
185 onShutterButtonClick();
190 * An unpublished intent flag requesting to return as soon as capturing is
191 * completed. TODO: consider publishing by moving into MediaStore.
193 private static final String EXTRA_QUICK_CAPTURE =
194 "android.intent.extra.quickCapture";
196 // The display rotation in degrees. This is only valid when mCameraState is
197 // not PREVIEW_STOPPED.
198 private int mDisplayRotation;
199 // The value for android.hardware.Camera.setDisplayOrientation.
200 private int mCameraDisplayOrientation;
201 // The value for UI components like indicators.
202 private int mDisplayOrientation;
203 // The value for cameradevice.CameraSettings.setPhotoRotationDegrees.
204 private int mJpegRotation;
205 // Indicates whether we are using front camera
206 private boolean mMirror;
207 private boolean mFirstTimeInitialized;
208 private boolean mIsImageCaptureIntent;
210 private int mCameraState = PREVIEW_STOPPED;
211 private boolean mSnapshotOnIdle = false;
213 private ContentResolver mContentResolver;
215 private AppController mAppController;
217 private final PostViewPictureCallback mPostViewPictureCallback =
218 new PostViewPictureCallback();
219 private final RawPictureCallback mRawPictureCallback =
220 new RawPictureCallback();
221 private final AutoFocusCallback mAutoFocusCallback =
222 new AutoFocusCallback();
223 private final Object mAutoFocusMoveCallback =
224 ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
225 ? new AutoFocusMoveCallback()
228 private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
230 private long mFocusStartTime;
231 private long mShutterCallbackTime;
232 private long mPostViewPictureCallbackTime;
233 private long mRawPictureCallbackTime;
234 private long mJpegPictureCallbackTime;
235 private long mOnResumeTime;
236 private byte[] mJpegImageData;
237 /** Touch coordinate for shutter button press. */
238 private TouchCoordinate mShutterTouchCoordinate;
241 // These latency time are for the CameraLatency test.
242 public long mAutoFocusTime;
243 public long mShutterLag;
244 public long mShutterToPictureDisplayedTime;
245 public long mPictureDisplayedToJpegCallbackTime;
246 public long mJpegCallbackFinishTime;
247 public long mCaptureStartTime;
249 // This handles everything about focus.
250 private FocusOverlayManager mFocusManager;
252 private final int mGcamModeIndex;
253 private final CountdownSoundPlayer mCountdownSoundPlayer = new CountdownSoundPlayer();
255 private CameraCapabilities.SceneMode mSceneMode;
257 private final Handler mHandler = new MainHandler(this);
259 private boolean mQuickCapture;
260 private SensorManager mSensorManager;
261 private final float[] mGData = new float[3];
262 private final float[] mMData = new float[3];
263 private final float[] mR = new float[16];
264 private int mHeading = -1;
266 /** True if all the parameters needed to start preview is ready. */
267 private boolean mCameraPreviewParamsReady = false;
269 private final MediaSaver.OnMediaSavedListener mOnMediaSavedListener =
270 new MediaSaver.OnMediaSavedListener() {
272 public void onMediaSaved(Uri uri) {
274 mActivity.notifyNewMedia(uri);
278 private boolean mShouldResizeTo16x9 = false;
280 private final Runnable mResumeTaskRunnable = new Runnable() {
288 * We keep the flash setting before entering scene modes (HDR)
289 * and restore it after HDR is off.
291 private String mFlashModeBeforeSceneMode;
294 * This callback gets called when user select whether or not to
295 * turn on geo-tagging.
297 public interface LocationDialogCallback {
299 * Gets called after user selected/unselected geo-tagging feature.
301 * @param selected whether or not geo-tagging feature is selected
303 public void onLocationTaggingSelected(boolean selected);
307 * This callback defines the text that is shown in the aspect ratio selection
308 * dialog, provides the current aspect ratio, and gets notified when user changes
309 * aspect ratio selection in the dialog.
311 public interface AspectRatioDialogCallback {
313 * Returns current aspect ratio that is being used to set as default.
315 public AspectRatioSelector.AspectRatio getCurrentAspectRatio();
318 * Gets notified when user has made the aspect ratio selection.
320 * @param newAspectRatio aspect ratio that user has selected
321 * @param dialogHandlingFinishedRunnable runnable to run when the operations
322 * needed to handle changes from dialog
325 public void onAspectRatioSelected(AspectRatioSelector.AspectRatio newAspectRatio,
326 Runnable dialogHandlingFinishedRunnable);
329 private void checkDisplayRotation() {
330 // Set the display orientation if display rotation has changed.
331 // Sometimes this happens when the device is held upside
332 // down and camera app is opened. Rotation animation will
333 // take some time and the rotation value we have got may be
334 // wrong. Framework does not have a callback for this now.
335 if (CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation) {
336 setDisplayOrientation();
338 if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
339 mHandler.postDelayed(new Runnable() {
342 checkDisplayRotation();
349 * This Handler is used to post message back onto the main thread of the
352 private static class MainHandler extends Handler {
353 private final WeakReference<PhotoModule> mModule;
355 public MainHandler(PhotoModule module) {
356 super(Looper.getMainLooper());
357 mModule = new WeakReference<PhotoModule>(module);
361 public void handleMessage(Message msg) {
362 PhotoModule module = mModule.get();
363 if (module == null) {
367 case MSG_FIRST_TIME_INIT: {
368 module.initializeFirstTime();
372 case MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE: {
373 module.setCameraParametersWhenIdle(0);
380 private void switchToGcamCapture() {
381 if (mActivity != null && mGcamModeIndex != 0) {
382 SettingsManager settingsManager = mActivity.getSettingsManager();
383 settingsManager.set(SettingsManager.SCOPE_GLOBAL,
384 Keys.KEY_CAMERA_HDR_PLUS, true);
386 // Disable the HDR+ button to prevent callbacks from being
387 // queued before the correct callback is attached to the button
388 // in the new module. The new module will set the enabled/disabled
389 // of this button when the module's preferred camera becomes available.
390 ButtonManager buttonManager = mActivity.getButtonManager();
392 buttonManager.disableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
394 mAppController.getCameraAppUI().freezeScreenUntilPreviewReady();
396 // Do not post this to avoid this module switch getting interleaved with
397 // other button callbacks.
398 mActivity.onModeSelected(mGcamModeIndex);
400 buttonManager.enableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
405 * Constructs a new photo module.
407 public PhotoModule(AppController app) {
409 mGcamModeIndex = app.getAndroidContext().getResources()
410 .getInteger(R.integer.camera_mode_gcam);
414 public String getPeekAccessibilityString() {
415 return mAppController.getAndroidContext()
416 .getResources().getString(R.string.photo_accessibility_peek);
420 public String getModuleStringIdentifier() {
421 return PHOTO_MODULE_STRING_ID;
425 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
426 mActivity = activity;
427 // TODO: Need to look at the controller interface to see if we can get
428 // rid of passing in the activity directly.
429 mAppController = mActivity;
431 mUI = new PhotoUI(mActivity, this, mActivity.getModuleLayoutRoot());
432 mActivity.setPreviewStatusListener(mUI);
434 SettingsManager settingsManager = mActivity.getSettingsManager();
435 mCameraId = settingsManager.getInteger(mAppController.getModuleScope(),
438 // TODO: Move this to SettingsManager as a part of upgrade procedure.
439 if (!settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
440 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
441 // Switch to back camera to set aspect ratio.
442 mCameraId = settingsManager.getIntegerDefault(Keys.KEY_CAMERA_ID);
445 mContentResolver = mActivity.getContentResolver();
447 // Surface texture is from camera screen nail and startPreview needs it.
448 // This must be done before startPreview.
449 mIsImageCaptureIntent = isImageCaptureIntent();
451 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
452 mSensorManager = (SensorManager) (mActivity.getSystemService(Context.SENSOR_SERVICE));
453 mUI.setCountdownFinishedListener(this);
455 // TODO: Make this a part of app controller API.
456 View cancelButton = mActivity.findViewById(R.id.shutter_cancel_button);
457 cancelButton.setOnClickListener(new View.OnClickListener() {
459 public void onClick(View view) {
465 private void cancelCountDown() {
466 if (mUI.isCountingDown()) {
467 // Cancel on-going countdown.
468 mUI.cancelCountDown();
470 mAppController.getCameraAppUI().transitionToCapture();
471 mAppController.getCameraAppUI().showModeOptions();
475 public boolean isUsingBottomBar() {
479 private void initializeControlByIntent() {
480 if (mIsImageCaptureIntent) {
481 mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
482 setupCaptureParams();
486 private void onPreviewStarted() {
487 mAppController.onPreviewStarted();
488 setCameraState(IDLE);
489 startFaceDetection();
494 * Prompt the user to pick to record location and choose aspect ratio for the
495 * very first run of camera only.
497 private void settingsFirstRun() {
498 final SettingsManager settingsManager = mActivity.getSettingsManager();
500 if (mActivity.isSecureCamera() || isImageCaptureIntent()) {
504 boolean locationPrompt = !settingsManager.isSet(SettingsManager.SCOPE_GLOBAL,
505 Keys.KEY_RECORD_LOCATION);
506 boolean aspectRatioPrompt = !settingsManager.getBoolean(
507 SettingsManager.SCOPE_GLOBAL, Keys.KEY_USER_SELECTED_ASPECT_RATIO);
508 if (!locationPrompt && !aspectRatioPrompt) {
512 // Check if the back camera exists
513 int backCameraId = mAppController.getCameraProvider().getFirstBackCameraId();
514 if (backCameraId == -1) {
515 // If there is no back camera, do not show the prompt.
519 if (locationPrompt) {
520 // Show both location and aspect ratio selection dialog.
521 mUI.showLocationAndAspectRatioDialog(new LocationDialogCallback(){
523 public void onLocationTaggingSelected(boolean selected) {
524 Keys.setLocation(mActivity.getSettingsManager(), selected,
525 mActivity.getLocationManager());
527 }, createAspectRatioDialogCallback());
529 // App upgrade. Only show aspect ratio selection.
530 mUI.showAspectRatioDialog(createAspectRatioDialogCallback());
534 private AspectRatioDialogCallback createAspectRatioDialogCallback() {
535 Size currentSize = mCameraSettings.getCurrentPhotoSize();
536 float aspectRatio = (float) currentSize.width() / (float) currentSize.height();
537 if (aspectRatio < 1f) {
538 aspectRatio = 1 / aspectRatio;
540 final AspectRatioSelector.AspectRatio currentAspectRatio;
541 if (Math.abs(aspectRatio - 4f / 3f) <= 0.1f) {
542 currentAspectRatio = AspectRatioSelector.AspectRatio.ASPECT_RATIO_4x3;
543 } else if (Math.abs(aspectRatio - 16f / 9f) <= 0.1f) {
544 currentAspectRatio = AspectRatioSelector.AspectRatio.ASPECT_RATIO_16x9;
546 // TODO: Log error and not show dialog.
550 List<Size> sizes = mCameraCapabilities.getSupportedPhotoSizes();
551 List<Size> pictureSizes = ResolutionUtil
552 .getDisplayableSizesFromSupported(sizes, true);
554 // This logic below finds the largest resolution for each aspect ratio.
555 // TODO: Move this somewhere that can be shared with SettingsActivity
556 int aspectRatio4x3Resolution = 0;
557 int aspectRatio16x9Resolution = 0;
558 Size largestSize4x3 = new Size(0, 0);
559 Size largestSize16x9 = new Size(0, 0);
560 for (Size size : pictureSizes) {
561 float pictureAspectRatio = (float) size.width() / (float) size.height();
562 pictureAspectRatio = pictureAspectRatio < 1 ?
563 1f / pictureAspectRatio : pictureAspectRatio;
564 int resolution = size.width() * size.height();
565 if (Math.abs(pictureAspectRatio - 4f / 3f) < 0.1f) {
566 if (resolution > aspectRatio4x3Resolution) {
567 aspectRatio4x3Resolution = resolution;
568 largestSize4x3 = size;
570 } else if (Math.abs(pictureAspectRatio - 16f / 9f) < 0.1f) {
571 if (resolution > aspectRatio16x9Resolution) {
572 aspectRatio16x9Resolution = resolution;
573 largestSize16x9 = size;
578 // Use the largest 4x3 and 16x9 sizes as candidates for picture size selection.
579 final Size size4x3ToSelect = largestSize4x3;
580 final Size size16x9ToSelect = largestSize16x9;
582 AspectRatioDialogCallback callback = new AspectRatioDialogCallback() {
585 public AspectRatioSelector.AspectRatio getCurrentAspectRatio() {
586 return currentAspectRatio;
590 public void onAspectRatioSelected(AspectRatioSelector.AspectRatio newAspectRatio,
591 Runnable dialogHandlingFinishedRunnable) {
592 if (newAspectRatio == AspectRatioSelector.AspectRatio.ASPECT_RATIO_4x3) {
593 String largestSize4x3Text = SettingsUtil.sizeToSetting(size4x3ToSelect);
594 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
595 Keys.KEY_PICTURE_SIZE_BACK,
597 } else if (newAspectRatio == AspectRatioSelector.AspectRatio.ASPECT_RATIO_16x9) {
598 String largestSize16x9Text = SettingsUtil.sizeToSetting(size16x9ToSelect);
599 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
600 Keys.KEY_PICTURE_SIZE_BACK,
601 largestSize16x9Text);
603 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
604 Keys.KEY_USER_SELECTED_ASPECT_RATIO, true);
605 String aspectRatio = mActivity.getSettingsManager().getString(
606 SettingsManager.SCOPE_GLOBAL,
607 Keys.KEY_USER_SELECTED_ASPECT_RATIO);
608 Log.e(TAG, "aspect ratio after setting it to true=" + aspectRatio);
609 if (newAspectRatio != currentAspectRatio) {
612 mUI.setRunnableForNextFrame(dialogHandlingFinishedRunnable);
614 mHandler.post(dialogHandlingFinishedRunnable);
622 public void onPreviewUIReady() {
627 public void onPreviewUIDestroyed() {
628 if (mCameraDevice == null) {
631 mCameraDevice.setPreviewTexture(null);
636 public void startPreCaptureAnimation() {
637 mAppController.startPreCaptureAnimation();
640 private void onCameraOpened() {
642 initializeControlByIntent();
645 private void switchCamera() {
651 mAppController.freezeScreenUntilPreviewReady();
652 SettingsManager settingsManager = mActivity.getSettingsManager();
654 Log.i(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
656 mCameraId = mPendingSwitchCameraId;
657 settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID, mCameraId);
660 if (mFocusManager != null) {
661 mFocusManager.removeMessages();
664 mMirror = isCameraFrontFacing();
665 mFocusManager.setMirror(mMirror);
666 // Start switch camera animation. Post a message because
667 // onFrameAvailable from the old camera may already exist.
671 * Uses the {@link CameraProvider} to open the currently-selected camera
672 * device, using {@link GservicesHelper} to choose between API-1 and API-2.
674 private void requestCameraOpen() {
675 mActivity.getCameraProvider().requestCamera(mCameraId,
676 GservicesHelper.useCamera2ApiThroughPortabilityLayer(mActivity));
679 private final ButtonManager.ButtonCallback mCameraCallback =
680 new ButtonManager.ButtonCallback() {
682 public void onStateChanged(int state) {
683 // At the time this callback is fired, the camera id
684 // has be set to the desired camera.
686 if (mPaused || mAppController.getCameraProvider().waitingForCamera()) {
689 // If switching to back camera, and HDR+ is still on,
690 // switch back to gcam, otherwise handle callback normally.
691 SettingsManager settingsManager = mActivity.getSettingsManager();
692 if (Keys.isCameraBackFacing(settingsManager,
693 mAppController.getModuleScope())) {
694 if (Keys.requestsReturnToHdrPlus(settingsManager,
695 mAppController.getModuleScope())) {
696 switchToGcamCapture();
701 mPendingSwitchCameraId = state;
703 Log.d(TAG, "Start to switch camera. cameraId=" + state);
704 // We need to keep a preview frame for the animation before
705 // releasing the camera. This will trigger
706 // onPreviewTextureCopied.
707 // TODO: Need to animate the camera switch
712 private final ButtonManager.ButtonCallback mHdrPlusCallback =
713 new ButtonManager.ButtonCallback() {
715 public void onStateChanged(int state) {
716 SettingsManager settingsManager = mActivity.getSettingsManager();
717 if (GcamHelper.hasGcamCapture()) {
718 // Set the camera setting to default backfacing.
719 settingsManager.setToDefault(mAppController.getModuleScope(),
721 switchToGcamCapture();
723 if (Keys.isHdrOn(settingsManager)) {
724 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_SCENE_MODE,
725 mCameraCapabilities.getStringifier().stringify(
726 CameraCapabilities.SceneMode.HDR));
728 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_SCENE_MODE,
729 mCameraCapabilities.getStringifier().stringify(
730 CameraCapabilities.SceneMode.AUTO));
732 updateParametersSceneMode();
733 mCameraDevice.applySettings(mCameraSettings);
739 private final View.OnClickListener mCancelCallback = new View.OnClickListener() {
741 public void onClick(View v) {
742 onCaptureCancelled();
746 private final View.OnClickListener mDoneCallback = new View.OnClickListener() {
748 public void onClick(View v) {
753 private final View.OnClickListener mRetakeCallback = new View.OnClickListener() {
755 public void onClick(View v) {
756 mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
762 public void hardResetSettings(SettingsManager settingsManager) {
763 // PhotoModule should hard reset HDR+ to off,
764 // and HDR to off if HDR+ is supported.
765 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false);
766 if (GcamHelper.hasGcamCapture()) {
767 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR, false);
772 public HardwareSpec getHardwareSpec() {
773 return (mCameraSettings != null ?
774 new HardwareSpecImpl(getCameraProvider(), mCameraCapabilities) : null);
778 public CameraAppUI.BottomBarUISpec getBottomBarSpec() {
779 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
781 bottomBarSpec.enableCamera = true;
782 bottomBarSpec.cameraCallback = mCameraCallback;
783 bottomBarSpec.enableFlash = !mAppController.getSettingsManager()
784 .getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR);
785 bottomBarSpec.enableHdr = true;
786 bottomBarSpec.hdrCallback = mHdrPlusCallback;
787 bottomBarSpec.enableGridLines = true;
788 if (mCameraCapabilities != null) {
789 bottomBarSpec.enableExposureCompensation = true;
790 bottomBarSpec.exposureCompensationSetCallback =
791 new CameraAppUI.BottomBarUISpec.ExposureCompensationSetCallback() {
793 public void setExposure(int value) {
794 setExposureCompensation(value);
797 bottomBarSpec.minExposureCompensation =
798 mCameraCapabilities.getMinExposureCompensation();
799 bottomBarSpec.maxExposureCompensation =
800 mCameraCapabilities.getMaxExposureCompensation();
801 bottomBarSpec.exposureCompensationStep =
802 mCameraCapabilities.getExposureCompensationStep();
805 bottomBarSpec.enableSelfTimer = true;
806 bottomBarSpec.showSelfTimer = true;
808 if (isImageCaptureIntent()) {
809 bottomBarSpec.showCancel = true;
810 bottomBarSpec.cancelCallback = mCancelCallback;
811 bottomBarSpec.showDone = true;
812 bottomBarSpec.doneCallback = mDoneCallback;
813 bottomBarSpec.showRetake = true;
814 bottomBarSpec.retakeCallback = mRetakeCallback;
817 return bottomBarSpec;
820 // either open a new camera or switch cameras
821 private void openCameraCommon() {
822 mUI.onCameraOpened(mCameraCapabilities, mCameraSettings);
823 if (mIsImageCaptureIntent) {
824 // Set hdr plus to default: off.
825 SettingsManager settingsManager = mActivity.getSettingsManager();
826 settingsManager.setToDefault(SettingsManager.SCOPE_GLOBAL,
827 Keys.KEY_CAMERA_HDR_PLUS);
833 public void updatePreviewAspectRatio(float aspectRatio) {
834 mAppController.updatePreviewAspectRatio(aspectRatio);
837 private void resetExposureCompensation() {
838 SettingsManager settingsManager = mActivity.getSettingsManager();
839 if (settingsManager == null) {
840 Log.e(TAG, "Settings manager is null!");
843 settingsManager.setToDefault(mAppController.getCameraScope(),
847 // Snapshots can only be taken after this is called. It should be called
848 // once only. We could have done these things in onCreate() but we want to
849 // make preview screen appear as soon as possible.
850 private void initializeFirstTime() {
851 if (mFirstTimeInitialized || mPaused) {
855 mUI.initializeFirstTime();
857 // We set the listener only when both service and shutterbutton
859 getServices().getMemoryManager().addListener(this);
861 mNamedImages = new NamedImages();
863 mFirstTimeInitialized = true;
866 mActivity.updateStorageSpaceAndHint(null);
869 // If the activity is paused and resumed, this method will be called in
871 private void initializeSecondTime() {
872 getServices().getMemoryManager().addListener(this);
873 mNamedImages = new NamedImages();
874 mUI.initializeSecondTime(mCameraCapabilities, mCameraSettings);
877 private void addIdleHandler() {
878 MessageQueue queue = Looper.myQueue();
879 queue.addIdleHandler(new MessageQueue.IdleHandler() {
881 public boolean queueIdle() {
882 Storage.ensureOSXCompatible();
889 public void startFaceDetection() {
890 if (mFaceDetectionStarted) {
893 if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) {
894 mFaceDetectionStarted = true;
895 mUI.onStartFaceDetection(mDisplayOrientation, isCameraFrontFacing());
896 mCameraDevice.setFaceDetectionCallback(mHandler, mUI);
897 mCameraDevice.startFaceDetection();
898 SessionStatsCollector.instance().faceScanActive(true);
903 public void stopFaceDetection() {
904 if (!mFaceDetectionStarted) {
907 if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) {
908 mFaceDetectionStarted = false;
909 mCameraDevice.setFaceDetectionCallback(null, null);
910 mCameraDevice.stopFaceDetection();
912 SessionStatsCollector.instance().faceScanActive(false);
916 private final class ShutterCallback
917 implements CameraShutterCallback {
919 private final boolean mNeedsAnimation;
921 public ShutterCallback(boolean needsAnimation) {
922 mNeedsAnimation = needsAnimation;
926 public void onShutter(CameraProxy camera) {
927 mShutterCallbackTime = System.currentTimeMillis();
928 mShutterLag = mShutterCallbackTime - mCaptureStartTime;
929 Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
930 if (mNeedsAnimation) {
931 mActivity.runOnUiThread(new Runnable() {
934 animateAfterShutter();
941 private final class PostViewPictureCallback
942 implements CameraPictureCallback {
944 public void onPictureTaken(byte[] data, CameraProxy camera) {
945 mPostViewPictureCallbackTime = System.currentTimeMillis();
946 Log.v(TAG, "mShutterToPostViewCallbackTime = "
947 + (mPostViewPictureCallbackTime - mShutterCallbackTime)
952 private final class RawPictureCallback
953 implements CameraPictureCallback {
955 public void onPictureTaken(byte[] rawData, CameraProxy camera) {
956 mRawPictureCallbackTime = System.currentTimeMillis();
957 Log.v(TAG, "mShutterToRawCallbackTime = "
958 + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
962 private static class ResizeBundle {
964 float targetAspectRatio;
969 * @return Cropped image if the target aspect ratio is larger than the jpeg
970 * aspect ratio on the long axis. The original jpeg otherwise.
972 private ResizeBundle cropJpegDataToAspectRatio(ResizeBundle dataBundle) {
974 final byte[] jpegData = dataBundle.jpegData;
975 final ExifInterface exif = dataBundle.exif;
976 float targetAspectRatio = dataBundle.targetAspectRatio;
978 Bitmap original = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
979 int originalWidth = original.getWidth();
980 int originalHeight = original.getHeight();
984 if (originalWidth > originalHeight) {
985 newHeight = (int) (originalWidth / targetAspectRatio);
986 newWidth = originalWidth;
988 newWidth = (int) (originalHeight / targetAspectRatio);
989 newHeight = originalHeight;
991 int xOffset = (originalWidth - newWidth)/2;
992 int yOffset = (originalHeight - newHeight)/2;
994 if (xOffset < 0 || yOffset < 0) {
998 Bitmap resized = Bitmap.createBitmap(original,xOffset,yOffset,newWidth, newHeight);
999 exif.setTagValue(ExifInterface.TAG_PIXEL_X_DIMENSION, new Integer(newWidth));
1000 exif.setTagValue(ExifInterface.TAG_PIXEL_Y_DIMENSION, new Integer(newHeight));
1002 ByteArrayOutputStream stream = new ByteArrayOutputStream();
1004 resized.compress(Bitmap.CompressFormat.JPEG, 90, stream);
1005 dataBundle.jpegData = stream.toByteArray();
1009 private final class JpegPictureCallback
1010 implements CameraPictureCallback {
1013 public JpegPictureCallback(Location loc) {
1018 public void onPictureTaken(final byte[] originalJpegData, final CameraProxy camera) {
1019 mAppController.setShutterEnabled(true);
1023 if (mIsImageCaptureIntent) {
1026 if (mSceneMode == CameraCapabilities.SceneMode.HDR) {
1027 mUI.setSwipingEnabled(true);
1030 mJpegPictureCallbackTime = System.currentTimeMillis();
1031 // If postview callback has arrived, the captured image is displayed
1032 // in postview callback. If not, the captured image is displayed in
1033 // raw picture callback.
1034 if (mPostViewPictureCallbackTime != 0) {
1035 mShutterToPictureDisplayedTime =
1036 mPostViewPictureCallbackTime - mShutterCallbackTime;
1037 mPictureDisplayedToJpegCallbackTime =
1038 mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
1040 mShutterToPictureDisplayedTime =
1041 mRawPictureCallbackTime - mShutterCallbackTime;
1042 mPictureDisplayedToJpegCallbackTime =
1043 mJpegPictureCallbackTime - mRawPictureCallbackTime;
1045 Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
1046 + mPictureDisplayedToJpegCallbackTime + "ms");
1048 mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden.
1049 if (!mIsImageCaptureIntent) {
1053 long now = System.currentTimeMillis();
1054 mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
1055 Log.v(TAG, "mJpegCallbackFinishTime = " + mJpegCallbackFinishTime + "ms");
1056 mJpegPictureCallbackTime = 0;
1058 final ExifInterface exif = Exif.getExif(originalJpegData);
1060 if (mShouldResizeTo16x9) {
1061 final ResizeBundle dataBundle = new ResizeBundle();
1062 dataBundle.jpegData = originalJpegData;
1063 dataBundle.targetAspectRatio = ResolutionUtil.NEXUS_5_LARGE_16_BY_9_ASPECT_RATIO;
1064 dataBundle.exif = exif;
1065 new AsyncTask<ResizeBundle, Void, ResizeBundle>() {
1068 protected ResizeBundle doInBackground(ResizeBundle... resizeBundles) {
1069 return cropJpegDataToAspectRatio(resizeBundles[0]);
1073 protected void onPostExecute(ResizeBundle result) {
1074 saveFinalPhoto(result.jpegData, result.exif, camera);
1076 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dataBundle);
1079 saveFinalPhoto(originalJpegData, exif, camera);
1083 void saveFinalPhoto(final byte[] jpegData, final ExifInterface exif, CameraProxy camera) {
1085 int orientation = Exif.getOrientation(exif);
1087 float zoomValue = 0f;
1088 if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
1089 int zoomIndex = mCameraSettings.getCurrentZoomIndex();
1090 List<Integer> zoomRatios = mCameraCapabilities.getZoomRatioList();
1091 if (zoomRatios != null && zoomIndex < zoomRatios.size()) {
1092 zoomValue = 0.01f * zoomRatios.get(zoomIndex);
1096 boolean hdrOn = CameraCapabilities.SceneMode.HDR == mSceneMode;
1097 String flashSetting =
1098 mActivity.getSettingsManager().getString(mAppController.getCameraScope(),
1099 Keys.KEY_FLASH_MODE);
1100 boolean gridLinesOn = Keys.areGridLinesOn(mActivity.getSettingsManager());
1101 UsageStatistics.instance().photoCaptureDoneEvent(
1102 eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
1103 mNamedImages.mQueue.lastElement().title + ".jpg", exif,
1104 isCameraFrontFacing(), hdrOn, zoomValue, flashSetting, gridLinesOn,
1105 (float) mTimerDuration, mShutterTouchCoordinate, mVolumeButtonClickedFlag);
1106 mShutterTouchCoordinate = null;
1107 mVolumeButtonClickedFlag = false;
1109 if (!mIsImageCaptureIntent) {
1110 // Calculate the width and the height of the jpeg.
1111 Integer exifWidth = exif.getTagIntValue(ExifInterface.TAG_PIXEL_X_DIMENSION);
1112 Integer exifHeight = exif.getTagIntValue(ExifInterface.TAG_PIXEL_Y_DIMENSION);
1114 if (mShouldResizeTo16x9 && exifWidth != null && exifHeight != null) {
1116 height = exifHeight;
1119 s = mCameraSettings.getCurrentPhotoSize();
1120 if ((mJpegRotation + orientation) % 180 == 0) {
1122 height = s.height();
1128 NamedEntity name = mNamedImages.getNextNameEntity();
1129 String title = (name == null) ? null : name.title;
1130 long date = (name == null) ? -1 : name.date;
1132 // Handle debug mode outputs
1133 if (mDebugUri != null) {
1134 // If using a debug uri, save jpeg there.
1135 saveToDebugUri(jpegData);
1137 // Adjust the title of the debug image shown in mediastore.
1138 if (title != null) {
1139 title = DEBUG_IMAGE_PREFIX + title;
1143 if (title == null) {
1144 Log.e(TAG, "Unbalanced name/data pair");
1147 date = mCaptureStartTime;
1149 if (mHeading >= 0) {
1150 // heading direction has been updated by the sensor.
1151 ExifTag directionRefTag = exif.buildTag(
1152 ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
1153 ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION);
1154 ExifTag directionTag = exif.buildTag(
1155 ExifInterface.TAG_GPS_IMG_DIRECTION,
1156 new Rational(mHeading, 1));
1157 exif.setTag(directionRefTag);
1158 exif.setTag(directionTag);
1160 getServices().getMediaSaver().addImage(
1161 jpegData, title, date, mLocation, width, height,
1162 orientation, exif, mOnMediaSavedListener, mContentResolver);
1164 // Animate capture with real jpeg data instead of a preview
1166 mUI.animateCapture(jpegData, orientation, mMirror);
1168 mJpegImageData = jpegData;
1169 if (!mQuickCapture) {
1170 mUI.showCapturedImageForReview(jpegData, orientation, mMirror);
1176 // Send the taken photo to remote shutter listeners, if any are
1178 getServices().getRemoteShutterListener().onPictureTaken(jpegData);
1180 // Check this in advance of each shot so we don't add to shutter
1181 // latency. It's true that someone else could write to the SD card
1182 // in the mean time and fill it, but that could have happened
1183 // between the shutter press and saving the JPEG too.
1184 mActivity.updateStorageSpaceAndHint(null);
1188 private final class AutoFocusCallback implements CameraAFCallback {
1190 public void onAutoFocus(boolean focused, CameraProxy camera) {
1191 SessionStatsCollector.instance().autofocusResult(focused);
1196 mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
1197 Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms focused = "+focused);
1198 setCameraState(IDLE);
1199 mFocusManager.onAutoFocus(focused, false);
1203 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1204 private final class AutoFocusMoveCallback
1205 implements CameraAFMoveCallback {
1207 public void onAutoFocusMoving(
1208 boolean moving, CameraProxy camera) {
1209 mFocusManager.onAutoFocusMoving(moving);
1210 SessionStatsCollector.instance().autofocusMoving(moving);
1215 * This class is just a thread-safe queue for name,date holder objects.
1217 public static class NamedImages {
1218 private final Vector<NamedEntity> mQueue;
1220 public NamedImages() {
1221 mQueue = new Vector<NamedEntity>();
1224 public void nameNewImage(long date) {
1225 NamedEntity r = new NamedEntity();
1226 r.title = CameraUtil.createJpegName(date);
1231 public NamedEntity getNextNameEntity() {
1232 synchronized (mQueue) {
1233 if (!mQueue.isEmpty()) {
1234 return mQueue.remove(0);
1240 public static class NamedEntity {
1241 public String title;
1246 private void setCameraState(int state) {
1247 mCameraState = state;
1249 case PREVIEW_STOPPED:
1250 case SNAPSHOT_IN_PROGRESS:
1251 case SWITCHING_CAMERA:
1252 // TODO: Tell app UI to disable swipe
1254 case PhotoController.IDLE:
1255 // TODO: Tell app UI to enable swipe
1260 private void animateAfterShutter() {
1261 // Only animate when in full screen capture mode
1262 // i.e. If monkey/a user swipes to the gallery during picture taking,
1263 // don't show animation
1264 if (!mIsImageCaptureIntent) {
1270 public boolean capture() {
1271 // If we are already in the middle of taking a snapshot or the image
1272 // save request is full then ignore.
1273 if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
1274 || mCameraState == SWITCHING_CAMERA || !mAppController.isShutterEnabled()) {
1277 mCaptureStartTime = System.currentTimeMillis();
1279 mPostViewPictureCallbackTime = 0;
1280 mJpegImageData = null;
1282 final boolean animateBefore = (mSceneMode == CameraCapabilities.SceneMode.HDR);
1284 if (animateBefore) {
1285 animateAfterShutter();
1288 // Set rotation and gps data.
1291 if (mActivity.isAutoRotateScreen()) {
1292 orientation = mDisplayRotation;
1294 orientation = mOrientation;
1296 Characteristics info =
1297 mActivity.getCameraProvider().getCharacteristics(mCameraId);
1298 mJpegRotation = info.getJpegOrientation(orientation);
1299 Location loc = mActivity.getLocationManager().getCurrentLocation();
1300 CameraUtil.setGpsParameters(mCameraSettings, loc);
1301 mCameraDevice.applySettings(mCameraSettings);
1303 // We don't want user to press the button again while taking a
1304 // multi-second HDR photo.
1305 mAppController.setShutterEnabled(false);
1306 mCameraDevice.takePicture(mHandler,
1307 new ShutterCallback(!animateBefore),
1308 mRawPictureCallback, mPostViewPictureCallback,
1309 new JpegPictureCallback(loc));
1311 mNamedImages.nameNewImage(mCaptureStartTime);
1313 mFaceDetectionStarted = false;
1314 setCameraState(SNAPSHOT_IN_PROGRESS);
1319 public void setFocusParameters() {
1320 setCameraParameters(UPDATE_PARAM_PREFERENCE);
1323 private void updateSceneMode() {
1324 // If scene mode is set, we cannot set flash mode, white balance, and
1325 // focus mode, instead, we read it from driver
1326 if (CameraCapabilities.SceneMode.AUTO != mSceneMode) {
1327 overrideCameraSettings(mCameraSettings.getCurrentFlashMode(),
1328 mCameraSettings.getCurrentFocusMode());
1332 private void overrideCameraSettings(CameraCapabilities.FlashMode flashMode,
1333 CameraCapabilities.FocusMode focusMode) {
1334 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
1335 SettingsManager settingsManager = mActivity.getSettingsManager();
1336 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FLASH_MODE,
1337 stringifier.stringify(flashMode));
1338 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FOCUS_MODE,
1339 stringifier.stringify(focusMode));
1343 public void onOrientationChanged(int orientation) {
1344 // We keep the last known orientation. So if the user first orient
1345 // the camera then point the camera to floor or sky, we still have
1346 // the correct orientation.
1347 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
1350 mOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
1354 public void onCameraAvailable(CameraProxy cameraProxy) {
1358 mCameraDevice = cameraProxy;
1360 initializeCapabilities();
1362 // Reset zoom value index.
1364 if (mFocusManager == null) {
1365 initializeFocusManager();
1367 mFocusManager.updateCapabilities(mCameraCapabilities);
1369 // Do camera parameter dependent initialization.
1370 mCameraSettings = mCameraDevice.getSettings();
1371 setCameraParameters(UPDATE_PARAM_ALL);
1372 // Set a listener which updates camera parameters based
1373 // on changed settings.
1374 SettingsManager settingsManager = mActivity.getSettingsManager();
1375 settingsManager.addListener(this);
1376 mCameraPreviewParamsReady = true;
1384 public void onCaptureCancelled() {
1385 mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent());
1390 public void onCaptureRetake() {
1394 mUI.hidePostCaptureAlert();
1395 mUI.hideIntentReviewImageView();
1400 public void onCaptureDone() {
1405 byte[] data = mJpegImageData;
1407 if (mCropValue == null) {
1408 // First handle the no crop case -- just return the value. If the
1409 // caller specifies a "save uri" then write the data to its
1410 // stream. Otherwise, pass back a scaled down version of the bitmap
1411 // directly in the extras.
1412 if (mSaveUri != null) {
1413 OutputStream outputStream = null;
1415 outputStream = mContentResolver.openOutputStream(mSaveUri);
1416 outputStream.write(data);
1417 outputStream.close();
1419 mActivity.setResultEx(Activity.RESULT_OK);
1421 } catch (IOException ex) {
1424 CameraUtil.closeSilently(outputStream);
1427 ExifInterface exif = Exif.getExif(data);
1428 int orientation = Exif.getOrientation(exif);
1429 Bitmap bitmap = CameraUtil.makeBitmap(data, 50 * 1024);
1430 bitmap = CameraUtil.rotate(bitmap, orientation);
1431 mActivity.setResultEx(Activity.RESULT_OK,
1432 new Intent("inline-data").putExtra("data", bitmap));
1436 // Save the image to a temp file and invoke the cropper
1438 FileOutputStream tempStream = null;
1440 File path = mActivity.getFileStreamPath(sTempCropFilename);
1442 tempStream = mActivity.openFileOutput(sTempCropFilename, 0);
1443 tempStream.write(data);
1445 tempUri = Uri.fromFile(path);
1446 } catch (FileNotFoundException ex) {
1447 mActivity.setResultEx(Activity.RESULT_CANCELED);
1450 } catch (IOException ex) {
1451 mActivity.setResultEx(Activity.RESULT_CANCELED);
1455 CameraUtil.closeSilently(tempStream);
1458 Bundle newExtras = new Bundle();
1459 if (mCropValue.equals("circle")) {
1460 newExtras.putString("circleCrop", "true");
1462 if (mSaveUri != null) {
1463 newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
1465 newExtras.putBoolean(CameraUtil.KEY_RETURN_DATA, true);
1467 if (mActivity.isSecureCamera()) {
1468 newExtras.putBoolean(CameraUtil.KEY_SHOW_WHEN_LOCKED, true);
1471 // TODO: Share this constant.
1472 final String CROP_ACTION = "com.android.camera.action.CROP";
1473 Intent cropIntent = new Intent(CROP_ACTION);
1475 cropIntent.setData(tempUri);
1476 cropIntent.putExtras(newExtras);
1478 mActivity.startActivityForResult(cropIntent, REQUEST_CROP);
1483 public void onShutterCoordinate(TouchCoordinate coord) {
1484 mShutterTouchCoordinate = coord;
1488 public void onShutterButtonFocus(boolean pressed) {
1489 // Do nothing. We don't support half-press to focus anymore.
1493 public void onShutterButtonClick() {
1494 if (mPaused || (mCameraState == SWITCHING_CAMERA)
1495 || (mCameraState == PREVIEW_STOPPED)) {
1496 mVolumeButtonClickedFlag = false;
1500 // Do not take the picture if there is not enough storage.
1501 if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1502 Log.i(TAG, "Not enough space or storage not ready. remaining="
1503 + mActivity.getStorageSpaceBytes());
1504 mVolumeButtonClickedFlag = false;
1507 Log.d(TAG, "onShutterButtonClick: mCameraState=" + mCameraState +
1508 " mVolumeButtonClickedFlag=" + mVolumeButtonClickedFlag);
1510 int countDownDuration = mActivity.getSettingsManager()
1511 .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);
1512 mTimerDuration = countDownDuration;
1513 if (countDownDuration > 0) {
1514 // Start count down.
1515 mAppController.getCameraAppUI().transitionToCancel();
1516 mAppController.getCameraAppUI().hideModeOptions();
1517 mUI.startCountdown(countDownDuration);
1524 private void focusAndCapture() {
1525 if (mSceneMode == CameraCapabilities.SceneMode.HDR) {
1526 mUI.setSwipingEnabled(false);
1528 // If the user wants to do a snapshot while the previous one is still
1529 // in progress, remember the fact and do it after we finish the previous
1530 // one and re-start the preview. Snapshot in progress also includes the
1531 // state that autofocus is focusing and a picture will be taken when
1532 // focus callback arrives.
1533 if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)) {
1534 if (!mIsImageCaptureIntent) {
1535 mSnapshotOnIdle = true;
1540 mSnapshotOnIdle = false;
1541 mFocusManager.focusAndCapture(mCameraSettings.getCurrentFocusMode());
1545 public void onRemainingSecondsChanged(int remainingSeconds) {
1546 mCountdownSoundPlayer.onRemainingSecondsChanged(remainingSeconds);
1550 public void onCountDownFinished() {
1551 if (mIsImageCaptureIntent) {
1552 mAppController.getCameraAppUI().transitionToIntentReviewLayout();
1554 mAppController.getCameraAppUI().transitionToCapture();
1556 mAppController.getCameraAppUI().showModeOptions();
1563 private void onResumeTasks() {
1567 Log.v(TAG, "Executing onResumeTasks.");
1569 mCountdownSoundPlayer.loadSounds();
1570 if (mFocusManager != null) {
1571 // If camera is not open when resume is called, focus manager will
1572 // not be initialized yet, in which case it will start listening to
1573 // preview area size change later in the initialization.
1574 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1576 mAppController.addPreviewAreaSizeChangedListener(mUI);
1578 CameraProvider camProvider = mActivity.getCameraProvider();
1579 if (camProvider == null) {
1580 // No camera provider, the Activity is destroyed already.
1583 requestCameraOpen();
1585 mJpegPictureCallbackTime = 0;
1588 mOnResumeTime = SystemClock.uptimeMillis();
1589 checkDisplayRotation();
1591 // If first time initialization is not finished, put it in the
1593 if (!mFirstTimeInitialized) {
1594 mHandler.sendEmptyMessage(MSG_FIRST_TIME_INIT);
1596 initializeSecondTime();
1599 Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
1600 if (gsensor != null) {
1601 mSensorManager.registerListener(this, gsensor, SensorManager.SENSOR_DELAY_NORMAL);
1604 Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
1605 if (msensor != null) {
1606 mSensorManager.registerListener(this, msensor, SensorManager.SENSOR_DELAY_NORMAL);
1609 getServices().getRemoteShutterListener().onModuleReady(this);
1610 SessionStatsCollector.instance().sessionActive(true);
1614 * @return Whether the currently active camera is front-facing.
1616 private boolean isCameraFrontFacing() {
1617 return mAppController.getCameraProvider().getCharacteristics(mCameraId)
1622 * The focus manager is the first UI related element to get initialized, and
1623 * it requires the RenderOverlay, so initialize it here
1625 private void initializeFocusManager() {
1626 // Create FocusManager object. startPreview needs it.
1627 // if mFocusManager not null, reuse it
1628 // otherwise create a new instance
1629 if (mFocusManager != null) {
1630 mFocusManager.removeMessages();
1632 mMirror = isCameraFrontFacing();
1633 String[] defaultFocusModesStrings = mActivity.getResources().getStringArray(
1634 R.array.pref_camera_focusmode_default_array);
1635 ArrayList<CameraCapabilities.FocusMode> defaultFocusModes =
1636 new ArrayList<CameraCapabilities.FocusMode>();
1637 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
1638 for (String modeString : defaultFocusModesStrings) {
1639 CameraCapabilities.FocusMode mode = stringifier.focusModeFromString(modeString);
1641 defaultFocusModes.add(mode);
1645 new FocusOverlayManager(mAppController, defaultFocusModes,
1646 mCameraCapabilities, this, mMirror, mActivity.getMainLooper(),
1648 MotionManager motionManager = getServices().getMotionManager();
1649 if (motionManager != null) {
1650 motionManager.addListener(mFocusManager);
1653 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1657 * @return Whether we are resuming from within the lockscreen.
1659 private boolean isResumeFromLockscreen() {
1660 String action = mActivity.getIntent().getAction();
1661 return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action)
1662 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action));
1666 public void resume() {
1669 // Add delay on resume from lock screen only, in order to to speed up
1670 // the onResume --> onPause --> onResume cycle from lock screen.
1671 // Don't do always because letting go of thread can cause delay.
1672 if (isResumeFromLockscreen()) {
1673 Log.v(TAG, "On resume, from lock screen.");
1674 // Note: onPauseAfterSuper() will delete this runnable, so we will
1675 // at most have 1 copy queued up.
1676 mHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC);
1678 Log.v(TAG, "On resume.");
1684 public void pause() {
1686 mHandler.removeCallbacks(mResumeTaskRunnable);
1687 getServices().getRemoteShutterListener().onModuleExit();
1688 SessionStatsCollector.instance().sessionActive(false);
1690 Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
1691 if (gsensor != null) {
1692 mSensorManager.unregisterListener(this, gsensor);
1695 Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
1696 if (msensor != null) {
1697 mSensorManager.unregisterListener(this, msensor);
1700 // Reset the focus first. Camera CTS does not guarantee that
1701 // cancelAutoFocus is allowed after preview stops.
1702 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1703 mCameraDevice.cancelAutoFocus();
1706 // If the camera has not been opened asynchronously yet,
1707 // and startPreview hasn't been called, then this is a no-op.
1708 // (e.g. onResume -> onPause -> onResume).
1711 mCountdownSoundPlayer.release();
1713 mNamedImages = null;
1714 // If we are in an image capture intent and has taken
1715 // a picture, we just clear it in onPause.
1716 mJpegImageData = null;
1718 // Remove the messages and runnables in the queue.
1719 mHandler.removeCallbacksAndMessages(null);
1722 mActivity.enableKeepScreenOn(false);
1725 mPendingSwitchCameraId = -1;
1726 if (mFocusManager != null) {
1727 mFocusManager.removeMessages();
1729 getServices().getMemoryManager().removeListener(this);
1730 mAppController.removePreviewAreaSizeChangedListener(mFocusManager);
1731 mAppController.removePreviewAreaSizeChangedListener(mUI);
1733 SettingsManager settingsManager = mActivity.getSettingsManager();
1734 settingsManager.removeListener(this);
1738 public void destroy() {
1739 // TODO: implement this.
1743 public void onLayoutOrientationChanged(boolean isLandscape) {
1744 setDisplayOrientation();
1748 public void updateCameraOrientation() {
1749 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
1750 setDisplayOrientation();
1754 private boolean canTakePicture() {
1755 return isCameraIdle()
1756 && (mActivity.getStorageSpaceBytes() > Storage.LOW_STORAGE_THRESHOLD_BYTES);
1760 public void autoFocus() {
1761 Log.v(TAG,"Starting auto focus");
1762 mFocusStartTime = System.currentTimeMillis();
1763 mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
1764 SessionStatsCollector.instance().autofocusManualTrigger();
1765 setCameraState(FOCUSING);
1769 public void cancelAutoFocus() {
1770 mCameraDevice.cancelAutoFocus();
1771 setCameraState(IDLE);
1772 setCameraParameters(UPDATE_PARAM_PREFERENCE);
1776 public void onSingleTapUp(View view, int x, int y) {
1777 if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
1778 || mCameraState == SNAPSHOT_IN_PROGRESS
1779 || mCameraState == SWITCHING_CAMERA
1780 || mCameraState == PREVIEW_STOPPED) {
1784 // Check if metering area or focus area is supported.
1785 if (!mFocusAreaSupported && !mMeteringAreaSupported) {
1788 mFocusManager.onSingleTapUp(x, y);
1792 public boolean onBackPressed() {
1793 return mUI.onBackPressed();
1797 public boolean onKeyDown(int keyCode, KeyEvent event) {
1799 case KeyEvent.KEYCODE_VOLUME_UP:
1800 case KeyEvent.KEYCODE_VOLUME_DOWN:
1801 case KeyEvent.KEYCODE_FOCUS:
1802 if (/* TODO: mActivity.isInCameraApp() && */mFirstTimeInitialized &&
1803 !mActivity.getCameraAppUI().isInIntentReview()) {
1804 if (event.getRepeatCount() == 0) {
1805 onShutterButtonFocus(true);
1810 case KeyEvent.KEYCODE_CAMERA:
1811 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1812 onShutterButtonClick();
1815 case KeyEvent.KEYCODE_DPAD_CENTER:
1816 // If we get a dpad center event without any focused view, move
1817 // the focus to the shutter button and press it.
1818 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1819 // Start auto-focus immediately to reduce shutter lag. After
1820 // the shutter button gets the focus, onShutterButtonFocus()
1821 // will be called again but it is fine.
1822 onShutterButtonFocus(true);
1830 public boolean onKeyUp(int keyCode, KeyEvent event) {
1832 case KeyEvent.KEYCODE_VOLUME_UP:
1833 case KeyEvent.KEYCODE_VOLUME_DOWN:
1834 if (/* mActivity.isInCameraApp() && */mFirstTimeInitialized &&
1835 !mActivity.getCameraAppUI().isInIntentReview()) {
1836 if (mUI.isCountingDown()) {
1839 mVolumeButtonClickedFlag = true;
1840 onShutterButtonClick();
1845 case KeyEvent.KEYCODE_FOCUS:
1846 if (mFirstTimeInitialized) {
1847 onShutterButtonFocus(false);
1854 private void closeCamera() {
1855 if (mCameraDevice != null) {
1856 stopFaceDetection();
1857 mCameraDevice.setZoomChangeListener(null);
1858 mCameraDevice.setFaceDetectionCallback(null, null);
1859 mCameraDevice.setErrorCallback(null, null);
1861 mFaceDetectionStarted = false;
1862 mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
1863 mCameraDevice = null;
1864 setCameraState(PREVIEW_STOPPED);
1865 mFocusManager.onCameraReleased();
1869 private void setDisplayOrientation() {
1870 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
1871 Characteristics info =
1872 mActivity.getCameraProvider().getCharacteristics(mCameraId);
1873 mDisplayOrientation = info.getPreviewOrientation(mDisplayRotation);
1874 mCameraDisplayOrientation = mDisplayOrientation;
1875 mUI.setDisplayOrientation(mDisplayOrientation);
1876 if (mFocusManager != null) {
1877 mFocusManager.setDisplayOrientation(mDisplayOrientation);
1879 // Change the camera display orientation
1880 if (mCameraDevice != null) {
1881 mCameraDevice.setDisplayOrientation(mDisplayRotation);
1885 /** Only called by UI thread. */
1886 private void setupPreview() {
1887 mFocusManager.resetTouchFocus();
1892 * Returns whether we can/should start the preview or not.
1894 private boolean checkPreviewPreconditions() {
1899 if (mCameraDevice == null) {
1900 Log.w(TAG, "startPreview: camera device not ready yet.");
1904 SurfaceTexture st = mActivity.getCameraAppUI().getSurfaceTexture();
1906 Log.w(TAG, "startPreview: surfaceTexture is not ready.");
1910 if (!mCameraPreviewParamsReady) {
1911 Log.w(TAG, "startPreview: parameters for preview is not ready.");
1918 * The start/stop preview should only run on the UI thread.
1920 private void startPreview() {
1921 if (!checkPreviewPreconditions()) {
1925 mCameraDevice.setErrorCallback(mHandler, mErrorCallback);
1926 setDisplayOrientation();
1928 if (!mSnapshotOnIdle) {
1929 // If the focus mode is continuous autofocus, call cancelAutoFocus
1930 // to resume it because it may have been paused by autoFocus call.
1931 if (mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) ==
1932 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
1933 mCameraDevice.cancelAutoFocus();
1935 mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
1937 setCameraParameters(UPDATE_PARAM_ALL);
1938 mCameraDevice.setPreviewTexture(mActivity.getCameraAppUI().getSurfaceTexture());
1940 Log.i(TAG, "startPreview");
1941 mCameraDevice.startPreview();
1943 mFocusManager.onPreviewStarted();
1945 SessionStatsCollector.instance().previewActive(true);
1946 if (mSnapshotOnIdle) {
1947 mHandler.post(mDoSnapRunnable);
1952 public void stopPreview() {
1953 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1954 Log.i(TAG, "stopPreview");
1955 mCameraDevice.stopPreview();
1956 mFaceDetectionStarted = false;
1958 setCameraState(PREVIEW_STOPPED);
1959 if (mFocusManager != null) {
1960 mFocusManager.onPreviewStopped();
1962 SessionStatsCollector.instance().previewActive(false);
1966 public void onSettingChanged(SettingsManager settingsManager, String key) {
1967 if (key.equals(Keys.KEY_FLASH_MODE)) {
1968 updateParametersFlashMode();
1970 if (key.equals(Keys.KEY_CAMERA_HDR)) {
1971 if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1972 Keys.KEY_CAMERA_HDR)) {
1974 mAppController.getButtonManager().disableButton(ButtonManager.BUTTON_FLASH);
1975 mFlashModeBeforeSceneMode = settingsManager.getString(
1976 mAppController.getCameraScope(), Keys.KEY_FLASH_MODE);
1978 if (mFlashModeBeforeSceneMode != null) {
1979 settingsManager.set(mAppController.getCameraScope(),
1980 Keys.KEY_FLASH_MODE,
1981 mFlashModeBeforeSceneMode);
1982 updateParametersFlashMode();
1983 mFlashModeBeforeSceneMode = null;
1985 mAppController.getButtonManager().enableButton(ButtonManager.BUTTON_FLASH);
1989 if (mCameraDevice != null) {
1990 mCameraDevice.applySettings(mCameraSettings);
1994 private void updateCameraParametersInitialize() {
1995 // Reset preview frame rate to the maximum because it may be lowered by
1996 // video camera application.
1997 int[] fpsRange = CameraUtil.getPhotoPreviewFpsRange(mCameraCapabilities);
1998 if (fpsRange != null && fpsRange.length > 0) {
1999 mCameraSettings.setPreviewFpsRange(fpsRange[0], fpsRange[1]);
2002 mCameraSettings.setRecordingHintEnabled(false);
2004 if (mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_STABILIZATION)) {
2005 mCameraSettings.setVideoStabilization(false);
2009 private void updateCameraParametersZoom() {
2011 if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
2012 mCameraSettings.setZoomIndex(mZoomValue);
2016 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
2017 private void setAutoExposureLockIfSupported() {
2018 if (mAeLockSupported) {
2019 mCameraSettings.setAutoExposureLock(mFocusManager.getAeAwbLock());
2023 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
2024 private void setAutoWhiteBalanceLockIfSupported() {
2025 if (mAwbLockSupported) {
2026 mCameraSettings.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
2030 private void setFocusAreasIfSupported() {
2031 if (mFocusAreaSupported) {
2032 mCameraSettings.setFocusAreas(mFocusManager.getFocusAreas());
2036 private void setMeteringAreasIfSupported() {
2037 if (mMeteringAreaSupported) {
2038 mCameraSettings.setMeteringAreas(mFocusManager.getMeteringAreas());
2042 private void updateCameraParametersPreference() {
2043 setAutoExposureLockIfSupported();
2044 setAutoWhiteBalanceLockIfSupported();
2045 setFocusAreasIfSupported();
2046 setMeteringAreasIfSupported();
2048 // Initialize focus mode.
2049 mFocusManager.overrideFocusMode(null);
2051 .setFocusMode(mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
2052 SessionStatsCollector.instance().autofocusActive(
2053 mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) ==
2054 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE
2057 // Set picture size.
2058 updateParametersPictureSize();
2060 // Set JPEG quality.
2061 updateParametersPictureQuality();
2063 // For the following settings, we need to check if the settings are
2064 // still supported by latest driver, if not, ignore the settings.
2066 // Set exposure compensation
2067 updateParametersExposureCompensation();
2069 // Set the scene mode: also sets flash and white balance.
2070 updateParametersSceneMode();
2072 if (mContinuousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
2073 updateAutoFocusMoveCallback();
2077 private void updateParametersPictureSize() {
2078 SettingsManager settingsManager = mActivity.getSettingsManager();
2079 String pictureSizeKey = isCameraFrontFacing() ? Keys.KEY_PICTURE_SIZE_FRONT
2080 : Keys.KEY_PICTURE_SIZE_BACK;
2081 String pictureSize = settingsManager.getString(SettingsManager.SCOPE_GLOBAL,
2084 List<Size> supported = mCameraCapabilities.getSupportedPhotoSizes();
2085 CameraPictureSizesCacher.updateSizesForCamera(mAppController.getAndroidContext(),
2086 mCameraDevice.getCameraId(), supported);
2087 SettingsUtil.setCameraPictureSize(pictureSize, supported, mCameraSettings,
2088 mCameraDevice.getCameraId());
2090 Size size = SettingsUtil.getPhotoSize(pictureSize, supported,
2091 mCameraDevice.getCameraId());
2092 if (ApiHelper.IS_NEXUS_5) {
2093 if (ResolutionUtil.NEXUS_5_LARGE_16_BY_9.equals(pictureSize)) {
2094 mShouldResizeTo16x9 = true;
2096 mShouldResizeTo16x9 = false;
2100 // Set a preview size that is closest to the viewfinder height and has
2101 // the right aspect ratio.
2102 List<Size> sizes = mCameraCapabilities.getSupportedPreviewSizes();
2103 Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes,
2104 (double) size.width() / size.height());
2105 Size original = mCameraSettings.getCurrentPreviewSize();
2106 if (!optimalSize.equals(original)) {
2107 mCameraSettings.setPreviewSize(optimalSize);
2109 // Zoom related settings will be changed for different preview
2110 // sizes, so set and read the parameters to get latest values
2111 if (mHandler.getLooper() == Looper.myLooper()) {
2112 // On UI thread only, not when camera starts up
2115 mCameraDevice.applySettings(mCameraSettings);
2117 mCameraSettings = mCameraDevice.getSettings();
2120 if (optimalSize.width() != 0 && optimalSize.height() != 0) {
2121 mUI.updatePreviewAspectRatio((float) optimalSize.width()
2122 / (float) optimalSize.height());
2124 Log.i(TAG, "Preview size is " + optimalSize);
2127 private void updateParametersPictureQuality() {
2128 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
2129 CameraProfile.QUALITY_HIGH);
2130 mCameraSettings.setPhotoJpegCompressionQuality(jpegQuality);
2133 private void updateParametersExposureCompensation() {
2134 SettingsManager settingsManager = mActivity.getSettingsManager();
2135 if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
2136 Keys.KEY_EXPOSURE_COMPENSATION_ENABLED)) {
2137 int value = settingsManager.getInteger(mAppController.getCameraScope(),
2139 int max = mCameraCapabilities.getMaxExposureCompensation();
2140 int min = mCameraCapabilities.getMinExposureCompensation();
2141 if (value >= min && value <= max) {
2142 mCameraSettings.setExposureCompensationIndex(value);
2144 Log.w(TAG, "invalid exposure range: " + value);
2147 // If exposure compensation is not enabled, reset the exposure compensation value.
2148 setExposureCompensation(0);
2153 private void updateParametersSceneMode() {
2154 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
2155 SettingsManager settingsManager = mActivity.getSettingsManager();
2157 mSceneMode = stringifier.
2158 sceneModeFromString(settingsManager.getString(mAppController.getCameraScope(),
2159 Keys.KEY_SCENE_MODE));
2160 if (mCameraCapabilities.supports(mSceneMode)) {
2161 if (mCameraSettings.getCurrentSceneMode() != mSceneMode) {
2162 mCameraSettings.setSceneMode(mSceneMode);
2164 // Setting scene mode will change the settings of flash mode,
2165 // white balance, and focus mode. Here we read back the
2166 // parameters, so we can know those settings.
2167 mCameraDevice.applySettings(mCameraSettings);
2168 mCameraSettings = mCameraDevice.getSettings();
2171 mSceneMode = mCameraSettings.getCurrentSceneMode();
2172 if (mSceneMode == null) {
2173 mSceneMode = CameraCapabilities.SceneMode.AUTO;
2177 if (CameraCapabilities.SceneMode.AUTO == mSceneMode) {
2179 updateParametersFlashMode();
2182 mFocusManager.overrideFocusMode(null);
2183 mCameraSettings.setFocusMode(
2184 mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
2186 mFocusManager.overrideFocusMode(mCameraSettings.getCurrentFocusMode());
2190 private void updateParametersFlashMode() {
2191 SettingsManager settingsManager = mActivity.getSettingsManager();
2193 CameraCapabilities.FlashMode flashMode = mCameraCapabilities.getStringifier()
2194 .flashModeFromString(settingsManager.getString(mAppController.getCameraScope(),
2195 Keys.KEY_FLASH_MODE));
2196 if (mCameraCapabilities.supports(flashMode)) {
2197 mCameraSettings.setFlashMode(flashMode);
2201 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
2202 private void updateAutoFocusMoveCallback() {
2203 if (mCameraSettings.getCurrentFocusMode() ==
2204 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
2205 mCameraDevice.setAutoFocusMoveCallback(mHandler,
2206 (CameraAFMoveCallback) mAutoFocusMoveCallback);
2208 mCameraDevice.setAutoFocusMoveCallback(null, null);
2213 * Sets the exposure compensation to the given value and also updates settings.
2215 * @param value exposure compensation value to be set
2217 public void setExposureCompensation(int value) {
2218 int max = mCameraCapabilities.getMaxExposureCompensation();
2219 int min = mCameraCapabilities.getMinExposureCompensation();
2220 if (value >= min && value <= max) {
2221 mCameraSettings.setExposureCompensationIndex(value);
2222 SettingsManager settingsManager = mActivity.getSettingsManager();
2223 settingsManager.set(mAppController.getCameraScope(),
2224 Keys.KEY_EXPOSURE, value);
2226 Log.w(TAG, "invalid exposure range: " + value);
2230 // We separate the parameters into several subsets, so we can update only
2231 // the subsets actually need updating. The PREFERENCE set needs extra
2232 // locking because the preference can be changed from GLThread as well.
2233 private void setCameraParameters(int updateSet) {
2234 if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
2235 updateCameraParametersInitialize();
2238 if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
2239 updateCameraParametersZoom();
2242 if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
2243 updateCameraParametersPreference();
2246 mCameraDevice.applySettings(mCameraSettings);
2249 // If the Camera is idle, update the parameters immediately, otherwise
2250 // accumulate them in mUpdateSet and update later.
2251 private void setCameraParametersWhenIdle(int additionalUpdateSet) {
2252 mUpdateSet |= additionalUpdateSet;
2253 if (mCameraDevice == null) {
2254 // We will update all the parameters when we open the device, so
2255 // we don't need to do anything now.
2258 } else if (isCameraIdle()) {
2259 setCameraParameters(mUpdateSet);
2263 if (!mHandler.hasMessages(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
2264 mHandler.sendEmptyMessageDelayed(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
2270 public boolean isCameraIdle() {
2271 return (mCameraState == IDLE) ||
2272 (mCameraState == PREVIEW_STOPPED) ||
2273 ((mFocusManager != null) && mFocusManager.isFocusCompleted()
2274 && (mCameraState != SWITCHING_CAMERA));
2278 public boolean isImageCaptureIntent() {
2279 String action = mActivity.getIntent().getAction();
2280 return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
2281 || CameraActivity.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
2284 private void setupCaptureParams() {
2285 Bundle myExtras = mActivity.getIntent().getExtras();
2286 if (myExtras != null) {
2287 mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
2288 mCropValue = myExtras.getString("crop");
2292 private void initializeCapabilities() {
2293 mCameraCapabilities = mCameraDevice.getCapabilities();
2294 mFocusAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA);
2295 mMeteringAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.METERING_AREA);
2296 mAeLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_EXPOSURE_LOCK);
2297 mAwbLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_WHITE_BALANCE_LOCK);
2298 mContinuousFocusSupported =
2299 mCameraCapabilities.supports(CameraCapabilities.FocusMode.CONTINUOUS_PICTURE);
2302 // TODO: Use zoomRatio device API rather than deprecated zoomIndex
2304 public int onZoomChanged(int index) {
2305 // Not useful to change zoom value when the activity is paused.
2310 if (mCameraSettings == null || mCameraDevice == null) {
2313 // Set zoom parameters asynchronously
2314 mCameraSettings.setZoomIndex(mZoomValue);
2315 mCameraDevice.applySettings(mCameraSettings);
2316 CameraSettings settings = mCameraDevice.getSettings();
2317 if (settings != null) {
2318 return settings.getCurrentZoomIndex();
2324 public int getCameraState() {
2325 return mCameraState;
2329 public void onMemoryStateChanged(int state) {
2330 mAppController.setShutterEnabled(state == MemoryManager.STATE_OK);
2334 public void onLowMemory() {
2335 // Not much we can do in the photo module.
2339 public void onAccuracyChanged(Sensor sensor, int accuracy) {
2343 public void onSensorChanged(SensorEvent event) {
2344 int type = event.sensor.getType();
2346 if (type == Sensor.TYPE_ACCELEROMETER) {
2348 } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
2351 // we should not be here.
2354 for (int i = 0; i < 3; i++) {
2355 data[i] = event.values[i];
2357 float[] orientation = new float[3];
2358 SensorManager.getRotationMatrix(mR, null, mGData, mMData);
2359 SensorManager.getOrientation(mR, orientation);
2360 mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
2366 // For debugging only.
2367 public void setDebugUri(Uri uri) {
2371 // For debugging only.
2372 private void saveToDebugUri(byte[] data) {
2373 if (mDebugUri != null) {
2374 OutputStream outputStream = null;
2376 outputStream = mContentResolver.openOutputStream(mDebugUri);
2377 outputStream.write(data);
2378 outputStream.close();
2379 } catch (IOException e) {
2380 Log.e(TAG, "Exception while writing debug jpeg file", e);
2382 CameraUtil.closeSilently(outputStream);
2388 public void onRemoteShutterPress() {
2389 mHandler.post(new Runnable() {
2398 * This class manages the loading/releasing/playing of the sounds needed for
2401 private class CountdownSoundPlayer {
2402 private SoundPool mSoundPool;
2403 private int mBeepOnce;
2404 private int mBeepTwice;
2408 if (mSoundPool == null) {
2409 mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0);
2410 mBeepOnce = mSoundPool.load(mAppController.getAndroidContext(), R.raw.beep_once, 1);
2411 mBeepTwice = mSoundPool.load(mAppController.getAndroidContext(), R.raw.beep_twice, 1);
2415 void onRemainingSecondsChanged(int newVal) {
2416 if (mSoundPool == null) {
2417 Log.e(TAG, "Cannot play sound - they have not been loaded.");
2421 mSoundPool.play(mBeepTwice, 1.0f, 1.0f, 0, 0, 1.0f);
2422 } else if (newVal == 2 || newVal == 3) {
2423 mSoundPool.play(mBeepOnce, 1.0f, 1.0f, 0, 0, 1.0f);
2428 if (mSoundPool != null) {
2429 mSoundPool.release();