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 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
448 mSensorManager = (SensorManager) (mActivity.getSystemService(Context.SENSOR_SERVICE));
449 mUI.setCountdownFinishedListener(this);
451 // TODO: Make this a part of app controller API.
452 View cancelButton = mActivity.findViewById(R.id.shutter_cancel_button);
453 cancelButton.setOnClickListener(new View.OnClickListener() {
455 public void onClick(View view) {
461 private void cancelCountDown() {
462 if (mUI.isCountingDown()) {
463 // Cancel on-going countdown.
464 mUI.cancelCountDown();
466 mAppController.getCameraAppUI().transitionToCapture();
467 mAppController.getCameraAppUI().showModeOptions();
471 public boolean isUsingBottomBar() {
475 private void initializeControlByIntent() {
476 if (mIsImageCaptureIntent) {
477 mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
478 setupCaptureParams();
482 private void onPreviewStarted() {
483 mAppController.onPreviewStarted();
484 setCameraState(IDLE);
485 startFaceDetection();
490 * Prompt the user to pick to record location and choose aspect ratio for the
491 * very first run of camera only.
493 private void settingsFirstRun() {
494 final SettingsManager settingsManager = mActivity.getSettingsManager();
496 if (mActivity.isSecureCamera() || isImageCaptureIntent()) {
500 boolean locationPrompt = !settingsManager.isSet(SettingsManager.SCOPE_GLOBAL,
501 Keys.KEY_RECORD_LOCATION);
502 boolean aspectRatioPrompt = !settingsManager.getBoolean(
503 SettingsManager.SCOPE_GLOBAL, Keys.KEY_USER_SELECTED_ASPECT_RATIO);
504 if (!locationPrompt && !aspectRatioPrompt) {
508 // Check if the back camera exists
509 int backCameraId = mAppController.getCameraProvider().getFirstBackCameraId();
510 if (backCameraId == -1) {
511 // If there is no back camera, do not show the prompt.
515 if (locationPrompt) {
516 // Show both location and aspect ratio selection dialog.
517 mUI.showLocationAndAspectRatioDialog(new LocationDialogCallback(){
519 public void onLocationTaggingSelected(boolean selected) {
520 Keys.setLocation(mActivity.getSettingsManager(), selected,
521 mActivity.getLocationManager());
523 }, createAspectRatioDialogCallback());
525 // App upgrade. Only show aspect ratio selection.
526 mUI.showAspectRatioDialog(createAspectRatioDialogCallback());
530 private AspectRatioDialogCallback createAspectRatioDialogCallback() {
531 Size currentSize = mCameraSettings.getCurrentPhotoSize();
532 float aspectRatio = (float) currentSize.width() / (float) currentSize.height();
533 if (aspectRatio < 1f) {
534 aspectRatio = 1 / aspectRatio;
536 final AspectRatioSelector.AspectRatio currentAspectRatio;
537 if (Math.abs(aspectRatio - 4f / 3f) <= 0.1f) {
538 currentAspectRatio = AspectRatioSelector.AspectRatio.ASPECT_RATIO_4x3;
539 } else if (Math.abs(aspectRatio - 16f / 9f) <= 0.1f) {
540 currentAspectRatio = AspectRatioSelector.AspectRatio.ASPECT_RATIO_16x9;
542 // TODO: Log error and not show dialog.
546 List<Size> sizes = mCameraCapabilities.getSupportedPhotoSizes();
547 List<Size> pictureSizes = ResolutionUtil
548 .getDisplayableSizesFromSupported(sizes, true);
550 // This logic below finds the largest resolution for each aspect ratio.
551 // TODO: Move this somewhere that can be shared with SettingsActivity
552 int aspectRatio4x3Resolution = 0;
553 int aspectRatio16x9Resolution = 0;
554 Size largestSize4x3 = new Size(0, 0);
555 Size largestSize16x9 = new Size(0, 0);
556 for (Size size : pictureSizes) {
557 float pictureAspectRatio = (float) size.width() / (float) size.height();
558 pictureAspectRatio = pictureAspectRatio < 1 ?
559 1f / pictureAspectRatio : pictureAspectRatio;
560 int resolution = size.width() * size.height();
561 if (Math.abs(pictureAspectRatio - 4f / 3f) < 0.1f) {
562 if (resolution > aspectRatio4x3Resolution) {
563 aspectRatio4x3Resolution = resolution;
564 largestSize4x3 = size;
566 } else if (Math.abs(pictureAspectRatio - 16f / 9f) < 0.1f) {
567 if (resolution > aspectRatio16x9Resolution) {
568 aspectRatio16x9Resolution = resolution;
569 largestSize16x9 = size;
574 // Use the largest 4x3 and 16x9 sizes as candidates for picture size selection.
575 final Size size4x3ToSelect = largestSize4x3;
576 final Size size16x9ToSelect = largestSize16x9;
578 AspectRatioDialogCallback callback = new AspectRatioDialogCallback() {
581 public AspectRatioSelector.AspectRatio getCurrentAspectRatio() {
582 return currentAspectRatio;
586 public void onAspectRatioSelected(AspectRatioSelector.AspectRatio newAspectRatio,
587 Runnable dialogHandlingFinishedRunnable) {
588 if (newAspectRatio == AspectRatioSelector.AspectRatio.ASPECT_RATIO_4x3) {
589 String largestSize4x3Text = SettingsUtil.sizeToSetting(size4x3ToSelect);
590 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
591 Keys.KEY_PICTURE_SIZE_BACK,
593 } else if (newAspectRatio == AspectRatioSelector.AspectRatio.ASPECT_RATIO_16x9) {
594 String largestSize16x9Text = SettingsUtil.sizeToSetting(size16x9ToSelect);
595 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
596 Keys.KEY_PICTURE_SIZE_BACK,
597 largestSize16x9Text);
599 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
600 Keys.KEY_USER_SELECTED_ASPECT_RATIO, true);
601 String aspectRatio = mActivity.getSettingsManager().getString(
602 SettingsManager.SCOPE_GLOBAL,
603 Keys.KEY_USER_SELECTED_ASPECT_RATIO);
604 Log.e(TAG, "aspect ratio after setting it to true=" + aspectRatio);
605 if (newAspectRatio != currentAspectRatio) {
608 mUI.setRunnableForNextFrame(dialogHandlingFinishedRunnable);
610 mHandler.post(dialogHandlingFinishedRunnable);
618 public void onPreviewUIReady() {
623 public void onPreviewUIDestroyed() {
624 if (mCameraDevice == null) {
627 mCameraDevice.setPreviewTexture(null);
632 public void startPreCaptureAnimation() {
633 mAppController.startPreCaptureAnimation();
636 private void onCameraOpened() {
638 initializeControlByIntent();
641 private void switchCamera() {
647 mAppController.freezeScreenUntilPreviewReady();
648 SettingsManager settingsManager = mActivity.getSettingsManager();
650 Log.i(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
652 mCameraId = mPendingSwitchCameraId;
653 settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID, mCameraId);
654 mActivity.getCameraProvider().requestCamera(mCameraId);
656 if (mFocusManager != null) {
657 mFocusManager.removeMessages();
660 mMirror = isCameraFrontFacing();
661 mFocusManager.setMirror(mMirror);
662 // Start switch camera animation. Post a message because
663 // onFrameAvailable from the old camera may already exist.
666 private final ButtonManager.ButtonCallback mCameraCallback =
667 new ButtonManager.ButtonCallback() {
669 public void onStateChanged(int state) {
670 // At the time this callback is fired, the camera id
671 // has be set to the desired camera.
673 if (mPaused || mAppController.getCameraProvider().waitingForCamera()) {
676 // If switching to back camera, and HDR+ is still on,
677 // switch back to gcam, otherwise handle callback normally.
678 SettingsManager settingsManager = mActivity.getSettingsManager();
679 if (Keys.isCameraBackFacing(settingsManager,
680 mAppController.getModuleScope())) {
681 if (Keys.requestsReturnToHdrPlus(settingsManager,
682 mAppController.getModuleScope())) {
683 switchToGcamCapture();
688 mPendingSwitchCameraId = state;
690 Log.d(TAG, "Start to switch camera. cameraId=" + state);
691 // We need to keep a preview frame for the animation before
692 // releasing the camera. This will trigger
693 // onPreviewTextureCopied.
694 // TODO: Need to animate the camera switch
699 private final ButtonManager.ButtonCallback mHdrPlusCallback =
700 new ButtonManager.ButtonCallback() {
702 public void onStateChanged(int state) {
703 SettingsManager settingsManager = mActivity.getSettingsManager();
704 if (GcamHelper.hasGcamCapture()) {
705 // Set the camera setting to default backfacing.
706 settingsManager.setToDefault(mAppController.getModuleScope(),
708 switchToGcamCapture();
710 if (Keys.isHdrOn(settingsManager)) {
711 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_SCENE_MODE,
712 mCameraCapabilities.getStringifier().stringify(
713 CameraCapabilities.SceneMode.HDR));
715 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_SCENE_MODE,
716 mCameraCapabilities.getStringifier().stringify(
717 CameraCapabilities.SceneMode.AUTO));
719 updateParametersSceneMode();
720 mCameraDevice.applySettings(mCameraSettings);
726 private final View.OnClickListener mCancelCallback = new View.OnClickListener() {
728 public void onClick(View v) {
729 onCaptureCancelled();
733 private final View.OnClickListener mDoneCallback = new View.OnClickListener() {
735 public void onClick(View v) {
740 private final View.OnClickListener mRetakeCallback = new View.OnClickListener() {
742 public void onClick(View v) {
743 mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
749 public void hardResetSettings(SettingsManager settingsManager) {
750 // PhotoModule should hard reset HDR+ to off,
751 // and HDR to off if HDR+ is supported.
752 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false);
753 if (GcamHelper.hasGcamCapture()) {
754 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR, false);
759 public HardwareSpec getHardwareSpec() {
760 return (mCameraSettings != null ?
761 new HardwareSpecImpl(getCameraProvider(), mCameraCapabilities) : null);
765 public CameraAppUI.BottomBarUISpec getBottomBarSpec() {
766 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
768 bottomBarSpec.enableCamera = true;
769 bottomBarSpec.cameraCallback = mCameraCallback;
770 bottomBarSpec.enableFlash = !mAppController.getSettingsManager()
771 .getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR);
772 bottomBarSpec.enableHdr = true;
773 bottomBarSpec.hdrCallback = mHdrPlusCallback;
774 bottomBarSpec.enableGridLines = true;
775 if (mCameraCapabilities != null) {
776 bottomBarSpec.enableExposureCompensation = true;
777 bottomBarSpec.exposureCompensationSetCallback =
778 new CameraAppUI.BottomBarUISpec.ExposureCompensationSetCallback() {
780 public void setExposure(int value) {
781 setExposureCompensation(value);
784 bottomBarSpec.minExposureCompensation =
785 mCameraCapabilities.getMinExposureCompensation();
786 bottomBarSpec.maxExposureCompensation =
787 mCameraCapabilities.getMaxExposureCompensation();
788 bottomBarSpec.exposureCompensationStep =
789 mCameraCapabilities.getExposureCompensationStep();
792 bottomBarSpec.enableSelfTimer = true;
793 bottomBarSpec.showSelfTimer = true;
795 if (isImageCaptureIntent()) {
796 bottomBarSpec.showCancel = true;
797 bottomBarSpec.cancelCallback = mCancelCallback;
798 bottomBarSpec.showDone = true;
799 bottomBarSpec.doneCallback = mDoneCallback;
800 bottomBarSpec.showRetake = true;
801 bottomBarSpec.retakeCallback = mRetakeCallback;
804 return bottomBarSpec;
807 // either open a new camera or switch cameras
808 private void openCameraCommon() {
809 mUI.onCameraOpened(mCameraCapabilities, mCameraSettings);
810 if (mIsImageCaptureIntent) {
811 // Set hdr plus to default: off.
812 SettingsManager settingsManager = mActivity.getSettingsManager();
813 settingsManager.setToDefault(SettingsManager.SCOPE_GLOBAL,
814 Keys.KEY_CAMERA_HDR_PLUS);
820 public void updatePreviewAspectRatio(float aspectRatio) {
821 mAppController.updatePreviewAspectRatio(aspectRatio);
824 private void resetExposureCompensation() {
825 SettingsManager settingsManager = mActivity.getSettingsManager();
826 if (settingsManager == null) {
827 Log.e(TAG, "Settings manager is null!");
830 settingsManager.setToDefault(mAppController.getCameraScope(),
834 // Snapshots can only be taken after this is called. It should be called
835 // once only. We could have done these things in onCreate() but we want to
836 // make preview screen appear as soon as possible.
837 private void initializeFirstTime() {
838 if (mFirstTimeInitialized || mPaused) {
842 mUI.initializeFirstTime();
844 // We set the listener only when both service and shutterbutton
846 getServices().getMemoryManager().addListener(this);
848 mNamedImages = new NamedImages();
850 mFirstTimeInitialized = true;
853 mActivity.updateStorageSpaceAndHint(null);
856 // If the activity is paused and resumed, this method will be called in
858 private void initializeSecondTime() {
859 getServices().getMemoryManager().addListener(this);
860 mNamedImages = new NamedImages();
861 mUI.initializeSecondTime(mCameraCapabilities, mCameraSettings);
864 private void addIdleHandler() {
865 MessageQueue queue = Looper.myQueue();
866 queue.addIdleHandler(new MessageQueue.IdleHandler() {
868 public boolean queueIdle() {
869 Storage.ensureOSXCompatible();
876 public void startFaceDetection() {
877 if (mFaceDetectionStarted) {
880 if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) {
881 mFaceDetectionStarted = true;
882 mUI.onStartFaceDetection(mDisplayOrientation, isCameraFrontFacing());
883 mCameraDevice.setFaceDetectionCallback(mHandler, mUI);
884 mCameraDevice.startFaceDetection();
885 SessionStatsCollector.instance().faceScanActive(true);
890 public void stopFaceDetection() {
891 if (!mFaceDetectionStarted) {
894 if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) {
895 mFaceDetectionStarted = false;
896 mCameraDevice.setFaceDetectionCallback(null, null);
897 mCameraDevice.stopFaceDetection();
899 SessionStatsCollector.instance().faceScanActive(false);
903 private final class ShutterCallback
904 implements CameraShutterCallback {
906 private final boolean mNeedsAnimation;
908 public ShutterCallback(boolean needsAnimation) {
909 mNeedsAnimation = needsAnimation;
913 public void onShutter(CameraProxy camera) {
914 mShutterCallbackTime = System.currentTimeMillis();
915 mShutterLag = mShutterCallbackTime - mCaptureStartTime;
916 Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
917 if (mNeedsAnimation) {
918 mActivity.runOnUiThread(new Runnable() {
921 animateAfterShutter();
928 private final class PostViewPictureCallback
929 implements CameraPictureCallback {
931 public void onPictureTaken(byte[] data, CameraProxy camera) {
932 mPostViewPictureCallbackTime = System.currentTimeMillis();
933 Log.v(TAG, "mShutterToPostViewCallbackTime = "
934 + (mPostViewPictureCallbackTime - mShutterCallbackTime)
939 private final class RawPictureCallback
940 implements CameraPictureCallback {
942 public void onPictureTaken(byte[] rawData, CameraProxy camera) {
943 mRawPictureCallbackTime = System.currentTimeMillis();
944 Log.v(TAG, "mShutterToRawCallbackTime = "
945 + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
949 private static class ResizeBundle {
951 float targetAspectRatio;
956 * @return Cropped image if the target aspect ratio is larger than the jpeg
957 * aspect ratio on the long axis. The original jpeg otherwise.
959 private ResizeBundle cropJpegDataToAspectRatio(ResizeBundle dataBundle) {
961 final byte[] jpegData = dataBundle.jpegData;
962 final ExifInterface exif = dataBundle.exif;
963 float targetAspectRatio = dataBundle.targetAspectRatio;
965 Bitmap original = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
966 int originalWidth = original.getWidth();
967 int originalHeight = original.getHeight();
971 if (originalWidth > originalHeight) {
972 newHeight = (int) (originalWidth / targetAspectRatio);
973 newWidth = originalWidth;
975 newWidth = (int) (originalHeight / targetAspectRatio);
976 newHeight = originalHeight;
978 int xOffset = (originalWidth - newWidth)/2;
979 int yOffset = (originalHeight - newHeight)/2;
981 if (xOffset < 0 || yOffset < 0) {
985 Bitmap resized = Bitmap.createBitmap(original,xOffset,yOffset,newWidth, newHeight);
986 exif.setTagValue(ExifInterface.TAG_PIXEL_X_DIMENSION, new Integer(newWidth));
987 exif.setTagValue(ExifInterface.TAG_PIXEL_Y_DIMENSION, new Integer(newHeight));
989 ByteArrayOutputStream stream = new ByteArrayOutputStream();
991 resized.compress(Bitmap.CompressFormat.JPEG, 90, stream);
992 dataBundle.jpegData = stream.toByteArray();
996 private final class JpegPictureCallback
997 implements CameraPictureCallback {
1000 public JpegPictureCallback(Location loc) {
1005 public void onPictureTaken(final byte[] originalJpegData, final CameraProxy camera) {
1006 mAppController.setShutterEnabled(true);
1010 if (mIsImageCaptureIntent) {
1013 if (mSceneMode == CameraCapabilities.SceneMode.HDR) {
1014 mUI.setSwipingEnabled(true);
1017 mJpegPictureCallbackTime = System.currentTimeMillis();
1018 // If postview callback has arrived, the captured image is displayed
1019 // in postview callback. If not, the captured image is displayed in
1020 // raw picture callback.
1021 if (mPostViewPictureCallbackTime != 0) {
1022 mShutterToPictureDisplayedTime =
1023 mPostViewPictureCallbackTime - mShutterCallbackTime;
1024 mPictureDisplayedToJpegCallbackTime =
1025 mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
1027 mShutterToPictureDisplayedTime =
1028 mRawPictureCallbackTime - mShutterCallbackTime;
1029 mPictureDisplayedToJpegCallbackTime =
1030 mJpegPictureCallbackTime - mRawPictureCallbackTime;
1032 Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
1033 + mPictureDisplayedToJpegCallbackTime + "ms");
1035 mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden.
1036 if (!mIsImageCaptureIntent) {
1040 long now = System.currentTimeMillis();
1041 mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
1042 Log.v(TAG, "mJpegCallbackFinishTime = " + mJpegCallbackFinishTime + "ms");
1043 mJpegPictureCallbackTime = 0;
1045 final ExifInterface exif = Exif.getExif(originalJpegData);
1047 if (mShouldResizeTo16x9) {
1048 final ResizeBundle dataBundle = new ResizeBundle();
1049 dataBundle.jpegData = originalJpegData;
1050 dataBundle.targetAspectRatio = ResolutionUtil.NEXUS_5_LARGE_16_BY_9_ASPECT_RATIO;
1051 dataBundle.exif = exif;
1052 new AsyncTask<ResizeBundle, Void, ResizeBundle>() {
1055 protected ResizeBundle doInBackground(ResizeBundle... resizeBundles) {
1056 return cropJpegDataToAspectRatio(resizeBundles[0]);
1060 protected void onPostExecute(ResizeBundle result) {
1061 saveFinalPhoto(result.jpegData, result.exif, camera);
1063 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dataBundle);
1066 saveFinalPhoto(originalJpegData, exif, camera);
1070 void saveFinalPhoto(final byte[] jpegData, final ExifInterface exif, CameraProxy camera) {
1072 int orientation = Exif.getOrientation(exif);
1074 float zoomValue = 0f;
1075 if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
1076 int zoomIndex = mCameraSettings.getCurrentZoomIndex();
1077 List<Integer> zoomRatios = mCameraCapabilities.getZoomRatioList();
1078 if (zoomRatios != null && zoomIndex < zoomRatios.size()) {
1079 zoomValue = 0.01f * zoomRatios.get(zoomIndex);
1083 boolean hdrOn = CameraCapabilities.SceneMode.HDR == mSceneMode;
1084 String flashSetting =
1085 mActivity.getSettingsManager().getString(mAppController.getCameraScope(),
1086 Keys.KEY_FLASH_MODE);
1087 boolean gridLinesOn = Keys.areGridLinesOn(mActivity.getSettingsManager());
1088 UsageStatistics.instance().photoCaptureDoneEvent(
1089 eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
1090 mNamedImages.mQueue.lastElement().title + ".jpg", exif,
1091 isCameraFrontFacing(), hdrOn, zoomValue, flashSetting, gridLinesOn,
1092 (float) mTimerDuration, mShutterTouchCoordinate, mVolumeButtonClickedFlag);
1093 mShutterTouchCoordinate = null;
1094 mVolumeButtonClickedFlag = false;
1096 if (!mIsImageCaptureIntent) {
1097 // Calculate the width and the height of the jpeg.
1098 Integer exifWidth = exif.getTagIntValue(ExifInterface.TAG_PIXEL_X_DIMENSION);
1099 Integer exifHeight = exif.getTagIntValue(ExifInterface.TAG_PIXEL_Y_DIMENSION);
1101 if (mShouldResizeTo16x9 && exifWidth != null && exifHeight != null) {
1103 height = exifHeight;
1106 s = mCameraSettings.getCurrentPhotoSize();
1107 if ((mJpegRotation + orientation) % 180 == 0) {
1109 height = s.height();
1115 NamedEntity name = mNamedImages.getNextNameEntity();
1116 String title = (name == null) ? null : name.title;
1117 long date = (name == null) ? -1 : name.date;
1119 // Handle debug mode outputs
1120 if (mDebugUri != null) {
1121 // If using a debug uri, save jpeg there.
1122 saveToDebugUri(jpegData);
1124 // Adjust the title of the debug image shown in mediastore.
1125 if (title != null) {
1126 title = DEBUG_IMAGE_PREFIX + title;
1130 if (title == null) {
1131 Log.e(TAG, "Unbalanced name/data pair");
1134 date = mCaptureStartTime;
1136 if (mHeading >= 0) {
1137 // heading direction has been updated by the sensor.
1138 ExifTag directionRefTag = exif.buildTag(
1139 ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
1140 ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION);
1141 ExifTag directionTag = exif.buildTag(
1142 ExifInterface.TAG_GPS_IMG_DIRECTION,
1143 new Rational(mHeading, 1));
1144 exif.setTag(directionRefTag);
1145 exif.setTag(directionTag);
1147 getServices().getMediaSaver().addImage(
1148 jpegData, title, date, mLocation, width, height,
1149 orientation, exif, mOnMediaSavedListener, mContentResolver);
1151 // Animate capture with real jpeg data instead of a preview
1153 mUI.animateCapture(jpegData, orientation, mMirror);
1155 mJpegImageData = jpegData;
1156 if (!mQuickCapture) {
1157 mUI.showCapturedImageForReview(jpegData, orientation, mMirror);
1163 // Send the taken photo to remote shutter listeners, if any are
1165 getServices().getRemoteShutterListener().onPictureTaken(jpegData);
1167 // Check this in advance of each shot so we don't add to shutter
1168 // latency. It's true that someone else could write to the SD card
1169 // in the mean time and fill it, but that could have happened
1170 // between the shutter press and saving the JPEG too.
1171 mActivity.updateStorageSpaceAndHint(null);
1175 private final class AutoFocusCallback implements CameraAFCallback {
1177 public void onAutoFocus(boolean focused, CameraProxy camera) {
1178 SessionStatsCollector.instance().autofocusResult(focused);
1183 mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
1184 Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms focused = "+focused);
1185 setCameraState(IDLE);
1186 mFocusManager.onAutoFocus(focused, false);
1190 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1191 private final class AutoFocusMoveCallback
1192 implements CameraAFMoveCallback {
1194 public void onAutoFocusMoving(
1195 boolean moving, CameraProxy camera) {
1196 mFocusManager.onAutoFocusMoving(moving);
1197 SessionStatsCollector.instance().autofocusMoving(moving);
1202 * This class is just a thread-safe queue for name,date holder objects.
1204 public static class NamedImages {
1205 private final Vector<NamedEntity> mQueue;
1207 public NamedImages() {
1208 mQueue = new Vector<NamedEntity>();
1211 public void nameNewImage(long date) {
1212 NamedEntity r = new NamedEntity();
1213 r.title = CameraUtil.createJpegName(date);
1218 public NamedEntity getNextNameEntity() {
1219 synchronized (mQueue) {
1220 if (!mQueue.isEmpty()) {
1221 return mQueue.remove(0);
1227 public static class NamedEntity {
1228 public String title;
1233 private void setCameraState(int state) {
1234 mCameraState = state;
1236 case PREVIEW_STOPPED:
1237 case SNAPSHOT_IN_PROGRESS:
1238 case SWITCHING_CAMERA:
1239 // TODO: Tell app UI to disable swipe
1241 case PhotoController.IDLE:
1242 // TODO: Tell app UI to enable swipe
1247 private void animateAfterShutter() {
1248 // Only animate when in full screen capture mode
1249 // i.e. If monkey/a user swipes to the gallery during picture taking,
1250 // don't show animation
1251 if (!mIsImageCaptureIntent) {
1257 public boolean capture() {
1258 // If we are already in the middle of taking a snapshot or the image
1259 // save request is full then ignore.
1260 if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
1261 || mCameraState == SWITCHING_CAMERA || !mAppController.isShutterEnabled()) {
1264 mCaptureStartTime = System.currentTimeMillis();
1266 mPostViewPictureCallbackTime = 0;
1267 mJpegImageData = null;
1269 final boolean animateBefore = (mSceneMode == CameraCapabilities.SceneMode.HDR);
1271 if (animateBefore) {
1272 animateAfterShutter();
1275 // Set rotation and gps data.
1278 // We need to be consistent with the framework orientation (i.e. the
1279 // orientation of the UI.) when the auto-rotate screen setting is on.
1280 if (mActivity.isAutoRotateScreen()) {
1281 orientation = (360 - mDisplayRotation) % 360;
1283 orientation = mOrientation;
1285 Characteristics info =
1286 mActivity.getCameraProvider().getCharacteristics(mCameraId);
1287 mJpegRotation = CameraUtil.getJpegRotation(info, orientation);
1288 mCameraSettings.setPhotoRotationDegrees(mJpegRotation);
1289 Location loc = mActivity.getLocationManager().getCurrentLocation();
1290 CameraUtil.setGpsParameters(mCameraSettings, loc);
1291 mCameraDevice.applySettings(mCameraSettings);
1293 // We don't want user to press the button again while taking a
1294 // multi-second HDR photo.
1295 mAppController.setShutterEnabled(false);
1296 mCameraDevice.takePicture(mHandler,
1297 new ShutterCallback(!animateBefore),
1298 mRawPictureCallback, mPostViewPictureCallback,
1299 new JpegPictureCallback(loc));
1301 mNamedImages.nameNewImage(mCaptureStartTime);
1303 mFaceDetectionStarted = false;
1304 setCameraState(SNAPSHOT_IN_PROGRESS);
1309 public void setFocusParameters() {
1310 setCameraParameters(UPDATE_PARAM_PREFERENCE);
1313 private void updateSceneMode() {
1314 // If scene mode is set, we cannot set flash mode, white balance, and
1315 // focus mode, instead, we read it from driver
1316 if (CameraCapabilities.SceneMode.AUTO != mSceneMode) {
1317 overrideCameraSettings(mCameraSettings.getCurrentFlashMode(),
1318 mCameraSettings.getCurrentFocusMode());
1322 private void overrideCameraSettings(CameraCapabilities.FlashMode flashMode,
1323 CameraCapabilities.FocusMode focusMode) {
1324 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
1325 SettingsManager settingsManager = mActivity.getSettingsManager();
1326 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FLASH_MODE,
1327 stringifier.stringify(flashMode));
1328 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FOCUS_MODE,
1329 stringifier.stringify(focusMode));
1333 public void onOrientationChanged(int orientation) {
1334 // We keep the last known orientation. So if the user first orient
1335 // the camera then point the camera to floor or sky, we still have
1336 // the correct orientation.
1337 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
1340 mOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
1344 public void onCameraAvailable(CameraProxy cameraProxy) {
1348 mCameraDevice = cameraProxy;
1350 initializeCapabilities();
1352 // Reset zoom value index.
1354 if (mFocusManager == null) {
1355 initializeFocusManager();
1357 mFocusManager.updateCapabilities(mCameraCapabilities);
1359 // Do camera parameter dependent initialization.
1360 mCameraSettings = mCameraDevice.getSettings();
1361 setCameraParameters(UPDATE_PARAM_ALL);
1362 // Set a listener which updates camera parameters based
1363 // on changed settings.
1364 SettingsManager settingsManager = mActivity.getSettingsManager();
1365 settingsManager.addListener(this);
1366 mCameraPreviewParamsReady = true;
1374 public void onCaptureCancelled() {
1375 mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent());
1380 public void onCaptureRetake() {
1384 mUI.hidePostCaptureAlert();
1385 mUI.hideIntentReviewImageView();
1390 public void onCaptureDone() {
1395 byte[] data = mJpegImageData;
1397 if (mCropValue == null) {
1398 // First handle the no crop case -- just return the value. If the
1399 // caller specifies a "save uri" then write the data to its
1400 // stream. Otherwise, pass back a scaled down version of the bitmap
1401 // directly in the extras.
1402 if (mSaveUri != null) {
1403 OutputStream outputStream = null;
1405 outputStream = mContentResolver.openOutputStream(mSaveUri);
1406 outputStream.write(data);
1407 outputStream.close();
1409 mActivity.setResultEx(Activity.RESULT_OK);
1411 } catch (IOException ex) {
1414 CameraUtil.closeSilently(outputStream);
1417 ExifInterface exif = Exif.getExif(data);
1418 int orientation = Exif.getOrientation(exif);
1419 Bitmap bitmap = CameraUtil.makeBitmap(data, 50 * 1024);
1420 bitmap = CameraUtil.rotate(bitmap, orientation);
1421 mActivity.setResultEx(Activity.RESULT_OK,
1422 new Intent("inline-data").putExtra("data", bitmap));
1426 // Save the image to a temp file and invoke the cropper
1428 FileOutputStream tempStream = null;
1430 File path = mActivity.getFileStreamPath(sTempCropFilename);
1432 tempStream = mActivity.openFileOutput(sTempCropFilename, 0);
1433 tempStream.write(data);
1435 tempUri = Uri.fromFile(path);
1436 } catch (FileNotFoundException ex) {
1437 mActivity.setResultEx(Activity.RESULT_CANCELED);
1440 } catch (IOException ex) {
1441 mActivity.setResultEx(Activity.RESULT_CANCELED);
1445 CameraUtil.closeSilently(tempStream);
1448 Bundle newExtras = new Bundle();
1449 if (mCropValue.equals("circle")) {
1450 newExtras.putString("circleCrop", "true");
1452 if (mSaveUri != null) {
1453 newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
1455 newExtras.putBoolean(CameraUtil.KEY_RETURN_DATA, true);
1457 if (mActivity.isSecureCamera()) {
1458 newExtras.putBoolean(CameraUtil.KEY_SHOW_WHEN_LOCKED, true);
1461 // TODO: Share this constant.
1462 final String CROP_ACTION = "com.android.camera.action.CROP";
1463 Intent cropIntent = new Intent(CROP_ACTION);
1465 cropIntent.setData(tempUri);
1466 cropIntent.putExtras(newExtras);
1468 mActivity.startActivityForResult(cropIntent, REQUEST_CROP);
1473 public void onShutterCoordinate(TouchCoordinate coord) {
1474 mShutterTouchCoordinate = coord;
1478 public void onShutterButtonFocus(boolean pressed) {
1479 // Do nothing. We don't support half-press to focus anymore.
1483 public void onShutterButtonClick() {
1484 if (mPaused || (mCameraState == SWITCHING_CAMERA)
1485 || (mCameraState == PREVIEW_STOPPED)) {
1486 mVolumeButtonClickedFlag = false;
1490 // Do not take the picture if there is not enough storage.
1491 if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1492 Log.i(TAG, "Not enough space or storage not ready. remaining="
1493 + mActivity.getStorageSpaceBytes());
1494 mVolumeButtonClickedFlag = false;
1497 Log.d(TAG, "onShutterButtonClick: mCameraState=" + mCameraState +
1498 " mVolumeButtonClickedFlag=" + mVolumeButtonClickedFlag);
1500 int countDownDuration = mActivity.getSettingsManager()
1501 .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);
1502 mTimerDuration = countDownDuration;
1503 if (countDownDuration > 0) {
1504 // Start count down.
1505 mAppController.getCameraAppUI().transitionToCancel();
1506 mAppController.getCameraAppUI().hideModeOptions();
1507 mUI.startCountdown(countDownDuration);
1514 private void focusAndCapture() {
1515 if (mSceneMode == CameraCapabilities.SceneMode.HDR) {
1516 mUI.setSwipingEnabled(false);
1518 // If the user wants to do a snapshot while the previous one is still
1519 // in progress, remember the fact and do it after we finish the previous
1520 // one and re-start the preview. Snapshot in progress also includes the
1521 // state that autofocus is focusing and a picture will be taken when
1522 // focus callback arrives.
1523 if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)) {
1524 if (!mIsImageCaptureIntent) {
1525 mSnapshotOnIdle = true;
1530 mSnapshotOnIdle = false;
1531 mFocusManager.focusAndCapture(mCameraSettings.getCurrentFocusMode());
1535 public void onRemainingSecondsChanged(int remainingSeconds) {
1536 mCountdownSoundPlayer.onRemainingSecondsChanged(remainingSeconds);
1540 public void onCountDownFinished() {
1541 if (mIsImageCaptureIntent) {
1542 mAppController.getCameraAppUI().transitionToIntentReviewLayout();
1544 mAppController.getCameraAppUI().transitionToCapture();
1546 mAppController.getCameraAppUI().showModeOptions();
1553 private void onResumeTasks() {
1557 Log.v(TAG, "Executing onResumeTasks.");
1559 mCountdownSoundPlayer.loadSounds();
1560 if (mFocusManager != null) {
1561 // If camera is not open when resume is called, focus manager will
1562 // not be initialized yet, in which case it will start listening to
1563 // preview area size change later in the initialization.
1564 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1566 mAppController.addPreviewAreaSizeChangedListener(mUI);
1568 CameraProvider camProvider = mActivity.getCameraProvider();
1569 if (camProvider == null) {
1570 // No camera provider, the Activity is destroyed already.
1573 camProvider.requestCamera(mCameraId);
1575 mJpegPictureCallbackTime = 0;
1578 mOnResumeTime = SystemClock.uptimeMillis();
1579 checkDisplayRotation();
1581 // If first time initialization is not finished, put it in the
1583 if (!mFirstTimeInitialized) {
1584 mHandler.sendEmptyMessage(MSG_FIRST_TIME_INIT);
1586 initializeSecondTime();
1589 Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
1590 if (gsensor != null) {
1591 mSensorManager.registerListener(this, gsensor, SensorManager.SENSOR_DELAY_NORMAL);
1594 Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
1595 if (msensor != null) {
1596 mSensorManager.registerListener(this, msensor, SensorManager.SENSOR_DELAY_NORMAL);
1599 getServices().getRemoteShutterListener().onModuleReady(this);
1600 SessionStatsCollector.instance().sessionActive(true);
1604 * @return Whether the currently active camera is front-facing.
1606 private boolean isCameraFrontFacing() {
1607 return mAppController.getCameraProvider().getCharacteristics(mCameraId)
1612 * The focus manager is the first UI related element to get initialized, and
1613 * it requires the RenderOverlay, so initialize it here
1615 private void initializeFocusManager() {
1616 // Create FocusManager object. startPreview needs it.
1617 // if mFocusManager not null, reuse it
1618 // otherwise create a new instance
1619 if (mFocusManager != null) {
1620 mFocusManager.removeMessages();
1622 mMirror = isCameraFrontFacing();
1623 String[] defaultFocusModesStrings = mActivity.getResources().getStringArray(
1624 R.array.pref_camera_focusmode_default_array);
1625 ArrayList<CameraCapabilities.FocusMode> defaultFocusModes = new ArrayList<>();
1626 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
1627 for (String modeString : defaultFocusModesStrings) {
1628 CameraCapabilities.FocusMode mode = stringifier.focusModeFromString(modeString);
1630 defaultFocusModes.add(mode);
1634 new FocusOverlayManager(mAppController, defaultFocusModes,
1635 mCameraCapabilities, this, mMirror, mActivity.getMainLooper(),
1637 MotionManager motionManager = getServices().getMotionManager();
1638 if (motionManager != null) {
1639 motionManager.addListener(mFocusManager);
1642 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1646 * @return Whether we are resuming from within the lockscreen.
1648 private boolean isResumeFromLockscreen() {
1649 String action = mActivity.getIntent().getAction();
1650 return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action)
1651 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action));
1655 public void resume() {
1658 // Add delay on resume from lock screen only, in order to to speed up
1659 // the onResume --> onPause --> onResume cycle from lock screen.
1660 // Don't do always because letting go of thread can cause delay.
1661 if (isResumeFromLockscreen()) {
1662 Log.v(TAG, "On resume, from lock screen.");
1663 // Note: onPauseAfterSuper() will delete this runnable, so we will
1664 // at most have 1 copy queued up.
1665 mHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC);
1667 Log.v(TAG, "On resume.");
1673 public void pause() {
1675 mHandler.removeCallbacks(mResumeTaskRunnable);
1676 getServices().getRemoteShutterListener().onModuleExit();
1677 SessionStatsCollector.instance().sessionActive(false);
1679 Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
1680 if (gsensor != null) {
1681 mSensorManager.unregisterListener(this, gsensor);
1684 Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
1685 if (msensor != null) {
1686 mSensorManager.unregisterListener(this, msensor);
1689 // Reset the focus first. Camera CTS does not guarantee that
1690 // cancelAutoFocus is allowed after preview stops.
1691 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1692 mCameraDevice.cancelAutoFocus();
1695 // If the camera has not been opened asynchronously yet,
1696 // and startPreview hasn't been called, then this is a no-op.
1697 // (e.g. onResume -> onPause -> onResume).
1700 mCountdownSoundPlayer.release();
1702 mNamedImages = null;
1703 // If we are in an image capture intent and has taken
1704 // a picture, we just clear it in onPause.
1705 mJpegImageData = null;
1707 // Remove the messages and runnables in the queue.
1708 mHandler.removeCallbacksAndMessages(null);
1711 mActivity.enableKeepScreenOn(false);
1714 mPendingSwitchCameraId = -1;
1715 if (mFocusManager != null) {
1716 mFocusManager.removeMessages();
1718 getServices().getMemoryManager().removeListener(this);
1719 mAppController.removePreviewAreaSizeChangedListener(mFocusManager);
1720 mAppController.removePreviewAreaSizeChangedListener(mUI);
1722 SettingsManager settingsManager = mActivity.getSettingsManager();
1723 settingsManager.removeListener(this);
1727 public void destroy() {
1728 // TODO: implement this.
1732 public void onLayoutOrientationChanged(boolean isLandscape) {
1733 setDisplayOrientation();
1737 public void updateCameraOrientation() {
1738 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
1739 setDisplayOrientation();
1743 private boolean canTakePicture() {
1744 return isCameraIdle()
1745 && (mActivity.getStorageSpaceBytes() > Storage.LOW_STORAGE_THRESHOLD_BYTES);
1749 public void autoFocus() {
1750 Log.v(TAG,"Starting auto focus");
1751 mFocusStartTime = System.currentTimeMillis();
1752 mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
1753 SessionStatsCollector.instance().autofocusManualTrigger();
1754 setCameraState(FOCUSING);
1758 public void cancelAutoFocus() {
1759 mCameraDevice.cancelAutoFocus();
1760 setCameraState(IDLE);
1761 setCameraParameters(UPDATE_PARAM_PREFERENCE);
1765 public void onSingleTapUp(View view, int x, int y) {
1766 if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
1767 || mCameraState == SNAPSHOT_IN_PROGRESS
1768 || mCameraState == SWITCHING_CAMERA
1769 || mCameraState == PREVIEW_STOPPED) {
1773 // Check if metering area or focus area is supported.
1774 if (!mFocusAreaSupported && !mMeteringAreaSupported) {
1777 mFocusManager.onSingleTapUp(x, y);
1781 public boolean onBackPressed() {
1782 return mUI.onBackPressed();
1786 public boolean onKeyDown(int keyCode, KeyEvent event) {
1788 case KeyEvent.KEYCODE_VOLUME_UP:
1789 case KeyEvent.KEYCODE_VOLUME_DOWN:
1790 case KeyEvent.KEYCODE_FOCUS:
1791 if (/* TODO: mActivity.isInCameraApp() && */mFirstTimeInitialized &&
1792 !mActivity.getCameraAppUI().isInIntentReview()) {
1793 if (event.getRepeatCount() == 0) {
1794 onShutterButtonFocus(true);
1799 case KeyEvent.KEYCODE_CAMERA:
1800 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1801 onShutterButtonClick();
1804 case KeyEvent.KEYCODE_DPAD_CENTER:
1805 // If we get a dpad center event without any focused view, move
1806 // the focus to the shutter button and press it.
1807 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1808 // Start auto-focus immediately to reduce shutter lag. After
1809 // the shutter button gets the focus, onShutterButtonFocus()
1810 // will be called again but it is fine.
1811 onShutterButtonFocus(true);
1819 public boolean onKeyUp(int keyCode, KeyEvent event) {
1821 case KeyEvent.KEYCODE_VOLUME_UP:
1822 case KeyEvent.KEYCODE_VOLUME_DOWN:
1823 if (/* mActivity.isInCameraApp() && */mFirstTimeInitialized &&
1824 !mActivity.getCameraAppUI().isInIntentReview()) {
1825 if (mUI.isCountingDown()) {
1828 mVolumeButtonClickedFlag = true;
1829 onShutterButtonClick();
1834 case KeyEvent.KEYCODE_FOCUS:
1835 if (mFirstTimeInitialized) {
1836 onShutterButtonFocus(false);
1843 private void closeCamera() {
1844 if (mCameraDevice != null) {
1845 stopFaceDetection();
1846 mCameraDevice.setZoomChangeListener(null);
1847 mCameraDevice.setFaceDetectionCallback(null, null);
1848 mCameraDevice.setErrorCallback(null, null);
1850 mFaceDetectionStarted = false;
1851 mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
1852 mCameraDevice = null;
1853 setCameraState(PREVIEW_STOPPED);
1854 mFocusManager.onCameraReleased();
1858 private void setDisplayOrientation() {
1859 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
1860 Characteristics info =
1861 mActivity.getCameraProvider().getCharacteristics(mCameraId);
1862 mDisplayOrientation = CameraUtil.getDisplayOrientation(mDisplayRotation, info);
1863 mCameraDisplayOrientation = mDisplayOrientation;
1864 mUI.setDisplayOrientation(mDisplayOrientation);
1865 if (mFocusManager != null) {
1866 mFocusManager.setDisplayOrientation(mDisplayOrientation);
1868 // Change the camera display orientation
1869 if (mCameraDevice != null) {
1870 mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
1874 /** Only called by UI thread. */
1875 private void setupPreview() {
1876 mFocusManager.resetTouchFocus();
1881 * Returns whether we can/should start the preview or not.
1883 private boolean checkPreviewPreconditions() {
1888 if (mCameraDevice == null) {
1889 Log.w(TAG, "startPreview: camera device not ready yet.");
1893 SurfaceTexture st = mActivity.getCameraAppUI().getSurfaceTexture();
1895 Log.w(TAG, "startPreview: surfaceTexture is not ready.");
1899 if (!mCameraPreviewParamsReady) {
1900 Log.w(TAG, "startPreview: parameters for preview is not ready.");
1907 * The start/stop preview should only run on the UI thread.
1909 private void startPreview() {
1910 if (!checkPreviewPreconditions()) {
1914 mCameraDevice.setErrorCallback(mHandler, mErrorCallback);
1915 setDisplayOrientation();
1917 if (!mSnapshotOnIdle) {
1918 // If the focus mode is continuous autofocus, call cancelAutoFocus
1919 // to resume it because it may have been paused by autoFocus call.
1920 if (mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) ==
1921 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
1922 mCameraDevice.cancelAutoFocus();
1924 mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
1926 setCameraParameters(UPDATE_PARAM_ALL);
1927 mCameraDevice.setPreviewTexture(mActivity.getCameraAppUI().getSurfaceTexture());
1929 Log.i(TAG, "startPreview");
1930 mCameraDevice.startPreview();
1932 mFocusManager.onPreviewStarted();
1934 SessionStatsCollector.instance().previewActive(true);
1935 if (mSnapshotOnIdle) {
1936 mHandler.post(mDoSnapRunnable);
1941 public void stopPreview() {
1942 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1943 Log.i(TAG, "stopPreview");
1944 mCameraDevice.stopPreview();
1945 mFaceDetectionStarted = false;
1947 setCameraState(PREVIEW_STOPPED);
1948 if (mFocusManager != null) {
1949 mFocusManager.onPreviewStopped();
1951 SessionStatsCollector.instance().previewActive(false);
1955 public void onSettingChanged(SettingsManager settingsManager, String key) {
1956 if (key.equals(Keys.KEY_FLASH_MODE)) {
1957 updateParametersFlashMode();
1959 if (key.equals(Keys.KEY_CAMERA_HDR)) {
1960 if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
1961 Keys.KEY_CAMERA_HDR)) {
1963 mAppController.getButtonManager().disableButton(ButtonManager.BUTTON_FLASH);
1964 mFlashModeBeforeSceneMode = settingsManager.getString(
1965 mAppController.getCameraScope(), Keys.KEY_FLASH_MODE);
1967 if (mFlashModeBeforeSceneMode != null) {
1968 settingsManager.set(mAppController.getCameraScope(),
1969 Keys.KEY_FLASH_MODE,
1970 mFlashModeBeforeSceneMode);
1971 updateParametersFlashMode();
1972 mFlashModeBeforeSceneMode = null;
1974 mAppController.getButtonManager().enableButton(ButtonManager.BUTTON_FLASH);
1978 if (mCameraDevice != null) {
1979 mCameraDevice.applySettings(mCameraSettings);
1983 private void updateCameraParametersInitialize() {
1984 // Reset preview frame rate to the maximum because it may be lowered by
1985 // video camera application.
1986 int[] fpsRange = CameraUtil.getPhotoPreviewFpsRange(mCameraCapabilities);
1987 if (fpsRange != null && fpsRange.length > 0) {
1988 mCameraSettings.setPreviewFpsRange(fpsRange[0], fpsRange[1]);
1991 mCameraSettings.setRecordingHintEnabled(false);
1993 if (mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_STABILIZATION)) {
1994 mCameraSettings.setVideoStabilization(false);
1998 private void updateCameraParametersZoom() {
2000 if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
2001 mCameraSettings.setZoomIndex(mZoomValue);
2005 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
2006 private void setAutoExposureLockIfSupported() {
2007 if (mAeLockSupported) {
2008 mCameraSettings.setAutoExposureLock(mFocusManager.getAeAwbLock());
2012 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
2013 private void setAutoWhiteBalanceLockIfSupported() {
2014 if (mAwbLockSupported) {
2015 mCameraSettings.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
2019 private void setFocusAreasIfSupported() {
2020 if (mFocusAreaSupported) {
2021 mCameraSettings.setFocusAreas(mFocusManager.getFocusAreas());
2025 private void setMeteringAreasIfSupported() {
2026 if (mMeteringAreaSupported) {
2027 mCameraSettings.setMeteringAreas(mFocusManager.getMeteringAreas());
2031 private void updateCameraParametersPreference() {
2032 setAutoExposureLockIfSupported();
2033 setAutoWhiteBalanceLockIfSupported();
2034 setFocusAreasIfSupported();
2035 setMeteringAreasIfSupported();
2037 // Initialize focus mode.
2038 mFocusManager.overrideFocusMode(null);
2040 .setFocusMode(mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
2041 SessionStatsCollector.instance().autofocusActive(
2042 mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) ==
2043 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE
2046 // Set picture size.
2047 updateParametersPictureSize();
2049 // Set JPEG quality.
2050 updateParametersPictureQuality();
2052 // For the following settings, we need to check if the settings are
2053 // still supported by latest driver, if not, ignore the settings.
2055 // Set exposure compensation
2056 updateParametersExposureCompensation();
2058 // Set the scene mode: also sets flash and white balance.
2059 updateParametersSceneMode();
2061 if (mContinuousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
2062 updateAutoFocusMoveCallback();
2066 private void updateParametersPictureSize() {
2067 SettingsManager settingsManager = mActivity.getSettingsManager();
2068 String pictureSizeKey = isCameraFrontFacing() ? Keys.KEY_PICTURE_SIZE_FRONT
2069 : Keys.KEY_PICTURE_SIZE_BACK;
2070 String pictureSize = settingsManager.getString(SettingsManager.SCOPE_GLOBAL,
2073 List<Size> supported = mCameraCapabilities.getSupportedPhotoSizes();
2074 CameraPictureSizesCacher.updateSizesForCamera(mAppController.getAndroidContext(),
2075 mCameraDevice.getCameraId(), supported);
2076 SettingsUtil.setCameraPictureSize(pictureSize, supported, mCameraSettings,
2077 mCameraDevice.getCameraId());
2079 Size size = SettingsUtil.getPhotoSize(pictureSize, supported,
2080 mCameraDevice.getCameraId());
2081 if (ApiHelper.IS_NEXUS_5) {
2082 if (ResolutionUtil.NEXUS_5_LARGE_16_BY_9.equals(pictureSize)) {
2083 mShouldResizeTo16x9 = true;
2085 mShouldResizeTo16x9 = false;
2089 // Set a preview size that is closest to the viewfinder height and has
2090 // the right aspect ratio.
2091 List<Size> sizes = mCameraCapabilities.getSupportedPreviewSizes();
2092 Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes,
2093 (double) size.width() / size.height());
2094 Size original = mCameraSettings.getCurrentPreviewSize();
2095 if (!optimalSize.equals(original)) {
2096 mCameraSettings.setPreviewSize(optimalSize);
2098 // Zoom related settings will be changed for different preview
2099 // sizes, so set and read the parameters to get latest values
2100 if (mHandler.getLooper() == Looper.myLooper()) {
2101 // On UI thread only, not when camera starts up
2104 mCameraDevice.applySettings(mCameraSettings);
2106 mCameraSettings = mCameraDevice.getSettings();
2109 if (optimalSize.width() != 0 && optimalSize.height() != 0) {
2110 mUI.updatePreviewAspectRatio((float) optimalSize.width()
2111 / (float) optimalSize.height());
2113 Log.i(TAG, "Preview size is " + optimalSize);
2116 private void updateParametersPictureQuality() {
2117 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
2118 CameraProfile.QUALITY_HIGH);
2119 mCameraSettings.setPhotoJpegCompressionQuality(jpegQuality);
2122 private void updateParametersExposureCompensation() {
2123 SettingsManager settingsManager = mActivity.getSettingsManager();
2124 if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
2125 Keys.KEY_EXPOSURE_COMPENSATION_ENABLED)) {
2126 int value = settingsManager.getInteger(mAppController.getCameraScope(),
2128 int max = mCameraCapabilities.getMaxExposureCompensation();
2129 int min = mCameraCapabilities.getMinExposureCompensation();
2130 if (value >= min && value <= max) {
2131 mCameraSettings.setExposureCompensationIndex(value);
2133 Log.w(TAG, "invalid exposure range: " + value);
2136 // If exposure compensation is not enabled, reset the exposure compensation value.
2137 setExposureCompensation(0);
2142 private void updateParametersSceneMode() {
2143 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
2144 SettingsManager settingsManager = mActivity.getSettingsManager();
2146 mSceneMode = stringifier.
2147 sceneModeFromString(settingsManager.getString(mAppController.getCameraScope(),
2148 Keys.KEY_SCENE_MODE));
2149 if (mCameraCapabilities.supports(mSceneMode)) {
2150 if (mCameraSettings.getCurrentSceneMode() != mSceneMode) {
2151 mCameraSettings.setSceneMode(mSceneMode);
2153 // Setting scene mode will change the settings of flash mode,
2154 // white balance, and focus mode. Here we read back the
2155 // parameters, so we can know those settings.
2156 mCameraDevice.applySettings(mCameraSettings);
2157 mCameraSettings = mCameraDevice.getSettings();
2160 mSceneMode = mCameraSettings.getCurrentSceneMode();
2161 if (mSceneMode == null) {
2162 mSceneMode = CameraCapabilities.SceneMode.AUTO;
2166 if (CameraCapabilities.SceneMode.AUTO == mSceneMode) {
2168 updateParametersFlashMode();
2171 mFocusManager.overrideFocusMode(null);
2172 mCameraSettings.setFocusMode(
2173 mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
2175 mFocusManager.overrideFocusMode(mCameraSettings.getCurrentFocusMode());
2179 private void updateParametersFlashMode() {
2180 SettingsManager settingsManager = mActivity.getSettingsManager();
2182 CameraCapabilities.FlashMode flashMode = mCameraCapabilities.getStringifier()
2183 .flashModeFromString(settingsManager.getString(mAppController.getCameraScope(),
2184 Keys.KEY_FLASH_MODE));
2185 if (mCameraCapabilities.supports(flashMode)) {
2186 mCameraSettings.setFlashMode(flashMode);
2190 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
2191 private void updateAutoFocusMoveCallback() {
2192 if (mCameraSettings.getCurrentFocusMode() ==
2193 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
2194 mCameraDevice.setAutoFocusMoveCallback(mHandler,
2195 (CameraAFMoveCallback) mAutoFocusMoveCallback);
2197 mCameraDevice.setAutoFocusMoveCallback(null, null);
2202 * Sets the exposure compensation to the given value and also updates settings.
2204 * @param value exposure compensation value to be set
2206 public void setExposureCompensation(int value) {
2207 int max = mCameraCapabilities.getMaxExposureCompensation();
2208 int min = mCameraCapabilities.getMinExposureCompensation();
2209 if (value >= min && value <= max) {
2210 mCameraSettings.setExposureCompensationIndex(value);
2211 SettingsManager settingsManager = mActivity.getSettingsManager();
2212 settingsManager.set(mAppController.getCameraScope(),
2213 Keys.KEY_EXPOSURE, value);
2215 Log.w(TAG, "invalid exposure range: " + value);
2219 // We separate the parameters into several subsets, so we can update only
2220 // the subsets actually need updating. The PREFERENCE set needs extra
2221 // locking because the preference can be changed from GLThread as well.
2222 private void setCameraParameters(int updateSet) {
2223 if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
2224 updateCameraParametersInitialize();
2227 if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
2228 updateCameraParametersZoom();
2231 if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
2232 updateCameraParametersPreference();
2235 mCameraDevice.applySettings(mCameraSettings);
2238 // If the Camera is idle, update the parameters immediately, otherwise
2239 // accumulate them in mUpdateSet and update later.
2240 private void setCameraParametersWhenIdle(int additionalUpdateSet) {
2241 mUpdateSet |= additionalUpdateSet;
2242 if (mCameraDevice == null) {
2243 // We will update all the parameters when we open the device, so
2244 // we don't need to do anything now.
2247 } else if (isCameraIdle()) {
2248 setCameraParameters(mUpdateSet);
2252 if (!mHandler.hasMessages(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
2253 mHandler.sendEmptyMessageDelayed(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
2259 public boolean isCameraIdle() {
2260 return (mCameraState == IDLE) ||
2261 (mCameraState == PREVIEW_STOPPED) ||
2262 ((mFocusManager != null) && mFocusManager.isFocusCompleted()
2263 && (mCameraState != SWITCHING_CAMERA));
2267 public boolean isImageCaptureIntent() {
2268 String action = mActivity.getIntent().getAction();
2269 return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
2270 || CameraActivity.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
2273 private void setupCaptureParams() {
2274 Bundle myExtras = mActivity.getIntent().getExtras();
2275 if (myExtras != null) {
2276 mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
2277 mCropValue = myExtras.getString("crop");
2281 private void initializeCapabilities() {
2282 mCameraCapabilities = mCameraDevice.getCapabilities();
2283 mFocusAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA);
2284 mMeteringAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.METERING_AREA);
2285 mAeLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_EXPOSURE_LOCK);
2286 mAwbLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_WHITE_BALANCE_LOCK);
2287 mContinuousFocusSupported =
2288 mCameraCapabilities.supports(CameraCapabilities.FocusMode.CONTINUOUS_PICTURE);
2291 // TODO: Remove this
2293 public int onZoomChanged(int index) {
2294 // Not useful to change zoom value when the activity is paused.
2299 if (mCameraSettings == null || mCameraDevice == null) {
2302 // Set zoom parameters asynchronously
2303 mCameraSettings.setZoomRatio((float) mZoomValue);
2304 mCameraDevice.applySettings(mCameraSettings);
2305 CameraSettings settings = mCameraDevice.getSettings();
2306 if (settings != null) {
2307 return settings.getCurrentZoomIndex();
2313 public int getCameraState() {
2314 return mCameraState;
2318 public void onMemoryStateChanged(int state) {
2319 mAppController.setShutterEnabled(state == MemoryManager.STATE_OK);
2323 public void onLowMemory() {
2324 // Not much we can do in the photo module.
2328 public void onAccuracyChanged(Sensor sensor, int accuracy) {
2332 public void onSensorChanged(SensorEvent event) {
2333 int type = event.sensor.getType();
2335 if (type == Sensor.TYPE_ACCELEROMETER) {
2337 } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
2340 // we should not be here.
2343 for (int i = 0; i < 3; i++) {
2344 data[i] = event.values[i];
2346 float[] orientation = new float[3];
2347 SensorManager.getRotationMatrix(mR, null, mGData, mMData);
2348 SensorManager.getOrientation(mR, orientation);
2349 mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
2355 // For debugging only.
2356 public void setDebugUri(Uri uri) {
2360 // For debugging only.
2361 private void saveToDebugUri(byte[] data) {
2362 if (mDebugUri != null) {
2363 OutputStream outputStream = null;
2365 outputStream = mContentResolver.openOutputStream(mDebugUri);
2366 outputStream.write(data);
2367 outputStream.close();
2368 } catch (IOException e) {
2369 Log.e(TAG, "Exception while writing debug jpeg file", e);
2371 CameraUtil.closeSilently(outputStream);
2377 public void onRemoteShutterPress() {
2378 mHandler.post(new Runnable() {
2387 * This class manages the loading/releasing/playing of the sounds needed for
2390 private class CountdownSoundPlayer {
2391 private SoundPool mSoundPool;
2392 private int mBeepOnce;
2393 private int mBeepTwice;
2397 if (mSoundPool == null) {
2398 mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0);
2399 mBeepOnce = mSoundPool.load(mAppController.getAndroidContext(), R.raw.beep_once, 1);
2400 mBeepTwice = mSoundPool.load(mAppController.getAndroidContext(), R.raw.beep_twice, 1);
2404 void onRemainingSecondsChanged(int newVal) {
2405 if (mSoundPool == null) {
2406 Log.e(TAG, "Cannot play sound - they have not been loaded.");
2410 mSoundPool.play(mBeepTwice, 1.0f, 1.0f, 0, 0, 1.0f);
2411 } else if (newVal == 2 || newVal == 3) {
2412 mSoundPool.play(mBeepOnce, 1.0f, 1.0f, 0, 0, 1.0f);
2417 if (mSoundPool != null) {
2418 mSoundPool.release();