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.CameraProfile;
33 import android.net.Uri;
34 import android.os.AsyncTask;
35 import android.os.Build;
36 import android.os.Bundle;
37 import android.os.Handler;
38 import android.os.Looper;
39 import android.os.Message;
40 import android.os.MessageQueue;
41 import android.os.SystemClock;
42 import android.provider.MediaStore;
43 import android.view.KeyEvent;
44 import android.view.OrientationEventListener;
45 import android.view.View;
47 import com.android.camera.PhotoModule.NamedImages.NamedEntity;
48 import com.android.camera.app.AppController;
49 import com.android.camera.app.CameraAppUI;
50 import com.android.camera.app.CameraProvider;
51 import com.android.camera.app.MediaSaver;
52 import com.android.camera.app.MemoryManager;
53 import com.android.camera.app.MemoryManager.MemoryListener;
54 import com.android.camera.app.MotionManager;
55 import com.android.camera.debug.Log;
56 import com.android.camera.exif.ExifInterface;
57 import com.android.camera.exif.ExifTag;
58 import com.android.camera.exif.Rational;
59 import com.android.camera.hardware.HardwareSpec;
60 import com.android.camera.hardware.HardwareSpecImpl;
61 import com.android.camera.module.ModuleController;
62 import com.android.camera.remote.RemoteCameraModule;
63 import com.android.camera.settings.CameraPictureSizesCacher;
64 import com.android.camera.settings.Keys;
65 import com.android.camera.settings.ResolutionUtil;
66 import com.android.camera.settings.SettingsManager;
67 import com.android.camera.settings.SettingsUtil;
68 import com.android.camera.ui.CountDownView;
69 import com.android.camera.ui.TouchCoordinate;
70 import com.android.camera.util.ApiHelper;
71 import com.android.camera.util.CameraUtil;
72 import com.android.camera.util.GcamHelper;
73 import com.android.camera.util.GservicesHelper;
74 import com.android.camera.util.SessionStatsCollector;
75 import com.android.camera.util.UsageStatistics;
76 import com.android.camera.widget.AspectRatioSelector;
77 import com.android.camera2.R;
78 import com.android.ex.camera2.portability.CameraAgent.CameraAFCallback;
79 import com.android.ex.camera2.portability.CameraAgent.CameraAFMoveCallback;
80 import com.android.ex.camera2.portability.CameraAgent.CameraPictureCallback;
81 import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
82 import com.android.ex.camera2.portability.CameraAgent.CameraShutterCallback;
83 import com.android.ex.camera2.portability.CameraAgent.CameraStartPreviewCallback;
84 import com.android.ex.camera2.portability.CameraCapabilities;
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 public 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 float mZoomValue; // The current zoom ratio.
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 SoundPlayer mCountdownSoundPlayer;
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();
391 buttonManager.disableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
393 mAppController.getCameraAppUI().freezeScreenUntilPreviewReady();
395 // Do not post this to avoid this module switch getting interleaved with
396 // other button callbacks.
397 mActivity.onModeSelected(mGcamModeIndex);
399 buttonManager.enableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
404 * Constructs a new photo module.
406 public PhotoModule(AppController app) {
408 mGcamModeIndex = app.getAndroidContext().getResources()
409 .getInteger(R.integer.camera_mode_gcam);
413 public String getPeekAccessibilityString() {
414 return mAppController.getAndroidContext()
415 .getResources().getString(R.string.photo_accessibility_peek);
419 public String getModuleStringIdentifier() {
420 return PHOTO_MODULE_STRING_ID;
424 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
425 mActivity = activity;
426 // TODO: Need to look at the controller interface to see if we can get
427 // rid of passing in the activity directly.
428 mAppController = mActivity;
430 mUI = new PhotoUI(mActivity, this, mActivity.getModuleLayoutRoot());
431 mActivity.setPreviewStatusListener(mUI);
433 SettingsManager settingsManager = mActivity.getSettingsManager();
434 mCameraId = settingsManager.getInteger(mAppController.getModuleScope(),
437 // TODO: Move this to SettingsManager as a part of upgrade procedure.
438 if (!settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
439 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) {
440 // Switch to back camera to set aspect ratio.
441 mCameraId = settingsManager.getIntegerDefault(Keys.KEY_CAMERA_ID);
444 mContentResolver = mActivity.getContentResolver();
446 // Surface texture is from camera screen nail and startPreview needs it.
447 // This must be done before startPreview.
448 mIsImageCaptureIntent = isImageCaptureIntent();
450 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
451 mSensorManager = (SensorManager) (mActivity.getSystemService(Context.SENSOR_SERVICE));
452 mUI.setCountdownFinishedListener(this);
453 mCountdownSoundPlayer = new SoundPlayer(mAppController.getAndroidContext());
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 mAppController.setShutterEnabled(true);
489 setCameraState(IDLE);
490 startFaceDetection();
495 * Prompt the user to pick to record location and choose aspect ratio for the
496 * very first run of camera only.
498 private void settingsFirstRun() {
499 final SettingsManager settingsManager = mActivity.getSettingsManager();
501 if (mActivity.isSecureCamera() || isImageCaptureIntent()) {
505 boolean locationPrompt = !settingsManager.isSet(SettingsManager.SCOPE_GLOBAL,
506 Keys.KEY_RECORD_LOCATION);
507 boolean aspectRatioPrompt = !settingsManager.getBoolean(
508 SettingsManager.SCOPE_GLOBAL, Keys.KEY_USER_SELECTED_ASPECT_RATIO);
509 if (!locationPrompt && !aspectRatioPrompt) {
513 // Check if the back camera exists
514 int backCameraId = mAppController.getCameraProvider().getFirstBackCameraId();
515 if (backCameraId == -1) {
516 // If there is no back camera, do not show the prompt.
520 if (locationPrompt) {
521 // Show both location and aspect ratio selection dialog.
522 mUI.showLocationAndAspectRatioDialog(new LocationDialogCallback(){
524 public void onLocationTaggingSelected(boolean selected) {
525 Keys.setLocation(mActivity.getSettingsManager(), selected,
526 mActivity.getLocationManager());
528 }, createAspectRatioDialogCallback());
530 // App upgrade. Only show aspect ratio selection.
531 boolean wasShown = mUI.showAspectRatioDialog(createAspectRatioDialogCallback());
533 // If the dialog was not shown, set this flag to true so that we
534 // never have to check for it again. It means that we don't need
535 // to show the dialog on this device.
536 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
537 Keys.KEY_USER_SELECTED_ASPECT_RATIO, true);
542 private AspectRatioDialogCallback createAspectRatioDialogCallback() {
543 Size currentSize = mCameraSettings.getCurrentPhotoSize();
544 float aspectRatio = (float) currentSize.width() / (float) currentSize.height();
545 if (aspectRatio < 1f) {
546 aspectRatio = 1 / aspectRatio;
548 final AspectRatioSelector.AspectRatio currentAspectRatio;
549 if (Math.abs(aspectRatio - 4f / 3f) <= 0.1f) {
550 currentAspectRatio = AspectRatioSelector.AspectRatio.ASPECT_RATIO_4x3;
551 } else if (Math.abs(aspectRatio - 16f / 9f) <= 0.1f) {
552 currentAspectRatio = AspectRatioSelector.AspectRatio.ASPECT_RATIO_16x9;
554 // TODO: Log error and not show dialog.
558 List<Size> sizes = mCameraCapabilities.getSupportedPhotoSizes();
559 List<Size> pictureSizes = ResolutionUtil
560 .getDisplayableSizesFromSupported(sizes, true);
562 // This logic below finds the largest resolution for each aspect ratio.
563 // TODO: Move this somewhere that can be shared with SettingsActivity
564 int aspectRatio4x3Resolution = 0;
565 int aspectRatio16x9Resolution = 0;
566 Size largestSize4x3 = new Size(0, 0);
567 Size largestSize16x9 = new Size(0, 0);
568 for (Size size : pictureSizes) {
569 float pictureAspectRatio = (float) size.width() / (float) size.height();
570 pictureAspectRatio = pictureAspectRatio < 1 ?
571 1f / pictureAspectRatio : pictureAspectRatio;
572 int resolution = size.width() * size.height();
573 if (Math.abs(pictureAspectRatio - 4f / 3f) < 0.1f) {
574 if (resolution > aspectRatio4x3Resolution) {
575 aspectRatio4x3Resolution = resolution;
576 largestSize4x3 = size;
578 } else if (Math.abs(pictureAspectRatio - 16f / 9f) < 0.1f) {
579 if (resolution > aspectRatio16x9Resolution) {
580 aspectRatio16x9Resolution = resolution;
581 largestSize16x9 = size;
586 // Use the largest 4x3 and 16x9 sizes as candidates for picture size selection.
587 final Size size4x3ToSelect = largestSize4x3;
588 final Size size16x9ToSelect = largestSize16x9;
590 AspectRatioDialogCallback callback = new AspectRatioDialogCallback() {
593 public AspectRatioSelector.AspectRatio getCurrentAspectRatio() {
594 return currentAspectRatio;
598 public void onAspectRatioSelected(AspectRatioSelector.AspectRatio newAspectRatio,
599 Runnable dialogHandlingFinishedRunnable) {
600 if (newAspectRatio == AspectRatioSelector.AspectRatio.ASPECT_RATIO_4x3) {
601 String largestSize4x3Text = SettingsUtil.sizeToSetting(size4x3ToSelect);
602 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
603 Keys.KEY_PICTURE_SIZE_BACK,
605 } else if (newAspectRatio == AspectRatioSelector.AspectRatio.ASPECT_RATIO_16x9) {
606 String largestSize16x9Text = SettingsUtil.sizeToSetting(size16x9ToSelect);
607 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
608 Keys.KEY_PICTURE_SIZE_BACK,
609 largestSize16x9Text);
611 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
612 Keys.KEY_USER_SELECTED_ASPECT_RATIO, true);
613 String aspectRatio = mActivity.getSettingsManager().getString(
614 SettingsManager.SCOPE_GLOBAL,
615 Keys.KEY_USER_SELECTED_ASPECT_RATIO);
616 Log.e(TAG, "aspect ratio after setting it to true=" + aspectRatio);
617 if (newAspectRatio != currentAspectRatio) {
620 mUI.setRunnableForNextFrame(dialogHandlingFinishedRunnable);
622 mHandler.post(dialogHandlingFinishedRunnable);
630 public void onPreviewUIReady() {
635 public void onPreviewUIDestroyed() {
636 if (mCameraDevice == null) {
639 mCameraDevice.setPreviewTexture(null);
644 public void startPreCaptureAnimation() {
645 mAppController.startPreCaptureAnimation();
648 private void onCameraOpened() {
650 initializeControlByIntent();
653 private void switchCamera() {
659 mAppController.freezeScreenUntilPreviewReady();
660 SettingsManager settingsManager = mActivity.getSettingsManager();
662 Log.i(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
664 mCameraId = mPendingSwitchCameraId;
666 settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID, mCameraId);
669 if (mFocusManager != null) {
670 mFocusManager.removeMessages();
673 mMirror = isCameraFrontFacing();
674 mFocusManager.setMirror(mMirror);
675 // Start switch camera animation. Post a message because
676 // onFrameAvailable from the old camera may already exist.
680 * Uses the {@link CameraProvider} to open the currently-selected camera
681 * device, using {@link GservicesHelper} to choose between API-1 and API-2.
683 private void requestCameraOpen() {
684 Log.v(TAG, "requestCameraOpen");
685 mActivity.getCameraProvider().requestCamera(mCameraId,
686 GservicesHelper.useCamera2ApiThroughPortabilityLayer(mActivity));
689 private final ButtonManager.ButtonCallback mCameraCallback =
690 new ButtonManager.ButtonCallback() {
692 public void onStateChanged(int state) {
693 // At the time this callback is fired, the camera id
694 // has be set to the desired camera.
696 if (mPaused || mAppController.getCameraProvider().waitingForCamera()) {
699 // If switching to back camera, and HDR+ is still on,
700 // switch back to gcam, otherwise handle callback normally.
701 SettingsManager settingsManager = mActivity.getSettingsManager();
702 if (Keys.isCameraBackFacing(settingsManager,
703 mAppController.getModuleScope())) {
704 if (Keys.requestsReturnToHdrPlus(settingsManager,
705 mAppController.getModuleScope())) {
706 switchToGcamCapture();
711 mPendingSwitchCameraId = state;
713 Log.d(TAG, "Start to switch camera. cameraId=" + state);
714 // We need to keep a preview frame for the animation before
715 // releasing the camera. This will trigger
716 // onPreviewTextureCopied.
717 // TODO: Need to animate the camera switch
722 private final ButtonManager.ButtonCallback mHdrPlusCallback =
723 new ButtonManager.ButtonCallback() {
725 public void onStateChanged(int state) {
726 SettingsManager settingsManager = mActivity.getSettingsManager();
727 if (GcamHelper.hasGcamAsSeparateModule()) {
728 // Set the camera setting to default backfacing.
729 settingsManager.setToDefault(mAppController.getModuleScope(),
731 switchToGcamCapture();
733 if (Keys.isHdrOn(settingsManager)) {
734 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_SCENE_MODE,
735 mCameraCapabilities.getStringifier().stringify(
736 CameraCapabilities.SceneMode.HDR));
738 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_SCENE_MODE,
739 mCameraCapabilities.getStringifier().stringify(
740 CameraCapabilities.SceneMode.AUTO));
742 updateParametersSceneMode();
743 mCameraDevice.applySettings(mCameraSettings);
749 private final View.OnClickListener mCancelCallback = new View.OnClickListener() {
751 public void onClick(View v) {
752 onCaptureCancelled();
756 private final View.OnClickListener mDoneCallback = new View.OnClickListener() {
758 public void onClick(View v) {
763 private final View.OnClickListener mRetakeCallback = new View.OnClickListener() {
765 public void onClick(View v) {
766 mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
772 public void hardResetSettings(SettingsManager settingsManager) {
773 // PhotoModule should hard reset HDR+ to off,
774 // and HDR to off if HDR+ is supported.
775 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false);
776 if (GcamHelper.hasGcamAsSeparateModule()) {
777 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR, false);
782 public HardwareSpec getHardwareSpec() {
783 return (mCameraSettings != null ?
784 new HardwareSpecImpl(getCameraProvider(), mCameraCapabilities) : null);
788 public CameraAppUI.BottomBarUISpec getBottomBarSpec() {
789 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
791 bottomBarSpec.enableCamera = true;
792 bottomBarSpec.cameraCallback = mCameraCallback;
793 bottomBarSpec.enableFlash = !mAppController.getSettingsManager()
794 .getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR);
795 bottomBarSpec.enableHdr = true;
796 bottomBarSpec.hdrCallback = mHdrPlusCallback;
797 bottomBarSpec.enableGridLines = true;
798 if (mCameraCapabilities != null) {
799 bottomBarSpec.enableExposureCompensation = true;
800 bottomBarSpec.exposureCompensationSetCallback =
801 new CameraAppUI.BottomBarUISpec.ExposureCompensationSetCallback() {
803 public void setExposure(int value) {
804 setExposureCompensation(value);
807 bottomBarSpec.minExposureCompensation =
808 mCameraCapabilities.getMinExposureCompensation();
809 bottomBarSpec.maxExposureCompensation =
810 mCameraCapabilities.getMaxExposureCompensation();
811 bottomBarSpec.exposureCompensationStep =
812 mCameraCapabilities.getExposureCompensationStep();
815 bottomBarSpec.enableSelfTimer = true;
816 bottomBarSpec.showSelfTimer = true;
818 if (isImageCaptureIntent()) {
819 bottomBarSpec.showCancel = true;
820 bottomBarSpec.cancelCallback = mCancelCallback;
821 bottomBarSpec.showDone = true;
822 bottomBarSpec.doneCallback = mDoneCallback;
823 bottomBarSpec.showRetake = true;
824 bottomBarSpec.retakeCallback = mRetakeCallback;
827 return bottomBarSpec;
830 // either open a new camera or switch cameras
831 private void openCameraCommon() {
832 mUI.onCameraOpened(mCameraCapabilities, mCameraSettings);
833 if (mIsImageCaptureIntent) {
834 // Set hdr plus to default: off.
835 SettingsManager settingsManager = mActivity.getSettingsManager();
836 settingsManager.setToDefault(SettingsManager.SCOPE_GLOBAL,
837 Keys.KEY_CAMERA_HDR_PLUS);
843 public void updatePreviewAspectRatio(float aspectRatio) {
844 mAppController.updatePreviewAspectRatio(aspectRatio);
847 private void resetExposureCompensation() {
848 SettingsManager settingsManager = mActivity.getSettingsManager();
849 if (settingsManager == null) {
850 Log.e(TAG, "Settings manager is null!");
853 settingsManager.setToDefault(mAppController.getCameraScope(),
857 // Snapshots can only be taken after this is called. It should be called
858 // once only. We could have done these things in onCreate() but we want to
859 // make preview screen appear as soon as possible.
860 private void initializeFirstTime() {
861 if (mFirstTimeInitialized || mPaused) {
865 mUI.initializeFirstTime();
867 // We set the listener only when both service and shutterbutton
869 getServices().getMemoryManager().addListener(this);
871 mNamedImages = new NamedImages();
873 mFirstTimeInitialized = true;
876 mActivity.updateStorageSpaceAndHint(null);
879 // If the activity is paused and resumed, this method will be called in
881 private void initializeSecondTime() {
882 getServices().getMemoryManager().addListener(this);
883 mNamedImages = new NamedImages();
884 mUI.initializeSecondTime(mCameraCapabilities, mCameraSettings);
887 private void addIdleHandler() {
888 MessageQueue queue = Looper.myQueue();
889 queue.addIdleHandler(new MessageQueue.IdleHandler() {
891 public boolean queueIdle() {
892 Storage.ensureOSXCompatible();
899 public void startFaceDetection() {
900 if (mFaceDetectionStarted) {
903 if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) {
904 mFaceDetectionStarted = true;
905 mUI.onStartFaceDetection(mDisplayOrientation, isCameraFrontFacing());
906 mCameraDevice.setFaceDetectionCallback(mHandler, mUI);
907 mCameraDevice.startFaceDetection();
908 SessionStatsCollector.instance().faceScanActive(true);
913 public void stopFaceDetection() {
914 if (!mFaceDetectionStarted) {
917 if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) {
918 mFaceDetectionStarted = false;
919 mCameraDevice.setFaceDetectionCallback(null, null);
920 mCameraDevice.stopFaceDetection();
922 SessionStatsCollector.instance().faceScanActive(false);
926 private final class ShutterCallback
927 implements CameraShutterCallback {
929 private final boolean mNeedsAnimation;
931 public ShutterCallback(boolean needsAnimation) {
932 mNeedsAnimation = needsAnimation;
936 public void onShutter(CameraProxy camera) {
937 mShutterCallbackTime = System.currentTimeMillis();
938 mShutterLag = mShutterCallbackTime - mCaptureStartTime;
939 Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
940 if (mNeedsAnimation) {
941 mActivity.runOnUiThread(new Runnable() {
944 animateAfterShutter();
951 private final class PostViewPictureCallback
952 implements CameraPictureCallback {
954 public void onPictureTaken(byte[] data, CameraProxy camera) {
955 mPostViewPictureCallbackTime = System.currentTimeMillis();
956 Log.v(TAG, "mShutterToPostViewCallbackTime = "
957 + (mPostViewPictureCallbackTime - mShutterCallbackTime)
962 private final class RawPictureCallback
963 implements CameraPictureCallback {
965 public void onPictureTaken(byte[] rawData, CameraProxy camera) {
966 mRawPictureCallbackTime = System.currentTimeMillis();
967 Log.v(TAG, "mShutterToRawCallbackTime = "
968 + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
972 private static class ResizeBundle {
974 float targetAspectRatio;
979 * @return Cropped image if the target aspect ratio is larger than the jpeg
980 * aspect ratio on the long axis. The original jpeg otherwise.
982 private ResizeBundle cropJpegDataToAspectRatio(ResizeBundle dataBundle) {
984 final byte[] jpegData = dataBundle.jpegData;
985 final ExifInterface exif = dataBundle.exif;
986 float targetAspectRatio = dataBundle.targetAspectRatio;
988 Bitmap original = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
989 int originalWidth = original.getWidth();
990 int originalHeight = original.getHeight();
994 if (originalWidth > originalHeight) {
995 newHeight = (int) (originalWidth / targetAspectRatio);
996 newWidth = originalWidth;
998 newWidth = (int) (originalHeight / targetAspectRatio);
999 newHeight = originalHeight;
1001 int xOffset = (originalWidth - newWidth)/2;
1002 int yOffset = (originalHeight - newHeight)/2;
1004 // For some reason L needs this to work.
1005 // This code is only run on the Nexus 5.
1006 // TODO: Determine why this is needed.
1007 if (Build.VERSION.SDK_INT >= 21 || Build.VERSION.CODENAME.equals("L")) {
1008 Log.v(TAG,"xOffset = " + xOffset);
1009 Log.v(TAG,"yOffset = " + yOffset);
1012 Log.v(TAG,"new xOffset = " + xOffset);
1013 Log.v(TAG,"new yOffset = " + yOffset);
1016 if (xOffset < 0 || yOffset < 0) {
1020 Bitmap resized = Bitmap.createBitmap(original,xOffset,yOffset,newWidth, newHeight);
1021 exif.setTagValue(ExifInterface.TAG_PIXEL_X_DIMENSION, new Integer(newWidth));
1022 exif.setTagValue(ExifInterface.TAG_PIXEL_Y_DIMENSION, new Integer(newHeight));
1024 ByteArrayOutputStream stream = new ByteArrayOutputStream();
1026 resized.compress(Bitmap.CompressFormat.JPEG, 90, stream);
1027 dataBundle.jpegData = stream.toByteArray();
1031 private final class JpegPictureCallback
1032 implements CameraPictureCallback {
1035 public JpegPictureCallback(Location loc) {
1040 public void onPictureTaken(final byte[] originalJpegData, final CameraProxy camera) {
1041 mAppController.setShutterEnabled(true);
1045 if (mIsImageCaptureIntent) {
1048 if (mSceneMode == CameraCapabilities.SceneMode.HDR) {
1049 mUI.setSwipingEnabled(true);
1052 mJpegPictureCallbackTime = System.currentTimeMillis();
1053 // If postview callback has arrived, the captured image is displayed
1054 // in postview callback. If not, the captured image is displayed in
1055 // raw picture callback.
1056 if (mPostViewPictureCallbackTime != 0) {
1057 mShutterToPictureDisplayedTime =
1058 mPostViewPictureCallbackTime - mShutterCallbackTime;
1059 mPictureDisplayedToJpegCallbackTime =
1060 mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
1062 mShutterToPictureDisplayedTime =
1063 mRawPictureCallbackTime - mShutterCallbackTime;
1064 mPictureDisplayedToJpegCallbackTime =
1065 mJpegPictureCallbackTime - mRawPictureCallbackTime;
1067 Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
1068 + mPictureDisplayedToJpegCallbackTime + "ms");
1070 mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden.
1071 if (!mIsImageCaptureIntent) {
1075 long now = System.currentTimeMillis();
1076 mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
1077 Log.v(TAG, "mJpegCallbackFinishTime = " + mJpegCallbackFinishTime + "ms");
1078 mJpegPictureCallbackTime = 0;
1080 final ExifInterface exif = Exif.getExif(originalJpegData);
1082 if (mShouldResizeTo16x9) {
1083 final ResizeBundle dataBundle = new ResizeBundle();
1084 dataBundle.jpegData = originalJpegData;
1085 dataBundle.targetAspectRatio = ResolutionUtil.NEXUS_5_LARGE_16_BY_9_ASPECT_RATIO;
1086 dataBundle.exif = exif;
1087 new AsyncTask<ResizeBundle, Void, ResizeBundle>() {
1090 protected ResizeBundle doInBackground(ResizeBundle... resizeBundles) {
1091 return cropJpegDataToAspectRatio(resizeBundles[0]);
1095 protected void onPostExecute(ResizeBundle result) {
1096 saveFinalPhoto(result.jpegData, result.exif, camera);
1098 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dataBundle);
1101 saveFinalPhoto(originalJpegData, exif, camera);
1105 void saveFinalPhoto(final byte[] jpegData, final ExifInterface exif, CameraProxy camera) {
1107 int orientation = Exif.getOrientation(exif);
1109 float zoomValue = 1.0f;
1110 if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
1111 zoomValue = mCameraSettings.getCurrentZoomRatio();
1113 boolean hdrOn = CameraCapabilities.SceneMode.HDR == mSceneMode;
1114 String flashSetting =
1115 mActivity.getSettingsManager().getString(mAppController.getCameraScope(),
1116 Keys.KEY_FLASH_MODE);
1117 boolean gridLinesOn = Keys.areGridLinesOn(mActivity.getSettingsManager());
1118 UsageStatistics.instance().photoCaptureDoneEvent(
1119 eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
1120 mNamedImages.mQueue.lastElement().title + ".jpg", exif,
1121 isCameraFrontFacing(), hdrOn, zoomValue, flashSetting, gridLinesOn,
1122 (float) mTimerDuration, mShutterTouchCoordinate, mVolumeButtonClickedFlag);
1123 mShutterTouchCoordinate = null;
1124 mVolumeButtonClickedFlag = false;
1126 if (!mIsImageCaptureIntent) {
1127 // Calculate the width and the height of the jpeg.
1128 Integer exifWidth = exif.getTagIntValue(ExifInterface.TAG_PIXEL_X_DIMENSION);
1129 Integer exifHeight = exif.getTagIntValue(ExifInterface.TAG_PIXEL_Y_DIMENSION);
1131 if (mShouldResizeTo16x9 && exifWidth != null && exifHeight != null) {
1133 height = exifHeight;
1136 s = mCameraSettings.getCurrentPhotoSize();
1137 if ((mJpegRotation + orientation) % 180 == 0) {
1139 height = s.height();
1145 NamedEntity name = mNamedImages.getNextNameEntity();
1146 String title = (name == null) ? null : name.title;
1147 long date = (name == null) ? -1 : name.date;
1149 // Handle debug mode outputs
1150 if (mDebugUri != null) {
1151 // If using a debug uri, save jpeg there.
1152 saveToDebugUri(jpegData);
1154 // Adjust the title of the debug image shown in mediastore.
1155 if (title != null) {
1156 title = DEBUG_IMAGE_PREFIX + title;
1160 if (title == null) {
1161 Log.e(TAG, "Unbalanced name/data pair");
1164 date = mCaptureStartTime;
1166 if (mHeading >= 0) {
1167 // heading direction has been updated by the sensor.
1168 ExifTag directionRefTag = exif.buildTag(
1169 ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
1170 ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION);
1171 ExifTag directionTag = exif.buildTag(
1172 ExifInterface.TAG_GPS_IMG_DIRECTION,
1173 new Rational(mHeading, 1));
1174 exif.setTag(directionRefTag);
1175 exif.setTag(directionTag);
1177 getServices().getMediaSaver().addImage(
1178 jpegData, title, date, mLocation, width, height,
1179 orientation, exif, mOnMediaSavedListener, mContentResolver);
1181 // Animate capture with real jpeg data instead of a preview
1183 mUI.animateCapture(jpegData, orientation, mMirror);
1185 mJpegImageData = jpegData;
1186 if (!mQuickCapture) {
1187 mUI.showCapturedImageForReview(jpegData, orientation, mMirror);
1193 // Send the taken photo to remote shutter listeners, if any are
1195 getServices().getRemoteShutterListener().onPictureTaken(jpegData);
1197 // Check this in advance of each shot so we don't add to shutter
1198 // latency. It's true that someone else could write to the SD card
1199 // in the mean time and fill it, but that could have happened
1200 // between the shutter press and saving the JPEG too.
1201 mActivity.updateStorageSpaceAndHint(null);
1205 private final class AutoFocusCallback implements CameraAFCallback {
1207 public void onAutoFocus(boolean focused, CameraProxy camera) {
1208 SessionStatsCollector.instance().autofocusResult(focused);
1213 mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
1214 Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms focused = "+focused);
1215 setCameraState(IDLE);
1216 mFocusManager.onAutoFocus(focused, false);
1220 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
1221 private final class AutoFocusMoveCallback
1222 implements CameraAFMoveCallback {
1224 public void onAutoFocusMoving(
1225 boolean moving, CameraProxy camera) {
1226 mFocusManager.onAutoFocusMoving(moving);
1227 SessionStatsCollector.instance().autofocusMoving(moving);
1232 * This class is just a thread-safe queue for name,date holder objects.
1234 public static class NamedImages {
1235 private final Vector<NamedEntity> mQueue;
1237 public NamedImages() {
1238 mQueue = new Vector<NamedEntity>();
1241 public void nameNewImage(long date) {
1242 NamedEntity r = new NamedEntity();
1243 r.title = CameraUtil.createJpegName(date);
1248 public NamedEntity getNextNameEntity() {
1249 synchronized (mQueue) {
1250 if (!mQueue.isEmpty()) {
1251 return mQueue.remove(0);
1257 public static class NamedEntity {
1258 public String title;
1263 private void setCameraState(int state) {
1264 mCameraState = state;
1266 case PREVIEW_STOPPED:
1267 case SNAPSHOT_IN_PROGRESS:
1268 case SWITCHING_CAMERA:
1269 // TODO: Tell app UI to disable swipe
1271 case PhotoController.IDLE:
1272 // TODO: Tell app UI to enable swipe
1277 private void animateAfterShutter() {
1278 // Only animate when in full screen capture mode
1279 // i.e. If monkey/a user swipes to the gallery during picture taking,
1280 // don't show animation
1281 if (!mIsImageCaptureIntent) {
1287 public boolean capture() {
1288 // If we are already in the middle of taking a snapshot or the image
1289 // save request is full then ignore.
1290 if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
1291 || mCameraState == SWITCHING_CAMERA || !mAppController.isShutterEnabled()) {
1294 mCaptureStartTime = System.currentTimeMillis();
1296 mPostViewPictureCallbackTime = 0;
1297 mJpegImageData = null;
1299 final boolean animateBefore = (mSceneMode == CameraCapabilities.SceneMode.HDR);
1301 if (animateBefore) {
1302 animateAfterShutter();
1305 Location loc = mActivity.getLocationManager().getCurrentLocation();
1306 CameraUtil.setGpsParameters(mCameraSettings, loc);
1307 mCameraDevice.applySettings(mCameraSettings);
1309 // Set JPEG orientation. Even if screen UI is locked in portrait, camera orientation should
1310 // still match device orientation (e.g., users should always get landscape photos while
1311 // capturing by putting device in landscape.)
1312 int orientation = mActivity.isAutoRotateScreen() ? mDisplayRotation : mOrientation;
1313 Characteristics info = mActivity.getCameraProvider().getCharacteristics(mCameraId);
1314 mJpegRotation = info.getJpegOrientation(orientation);
1315 mCameraDevice.setJpegOrientation(mJpegRotation);
1317 // We don't want user to press the button again while taking a
1318 // multi-second HDR photo.
1319 mAppController.setShutterEnabled(false);
1320 mCameraDevice.takePicture(mHandler,
1321 new ShutterCallback(!animateBefore),
1322 mRawPictureCallback, mPostViewPictureCallback,
1323 new JpegPictureCallback(loc));
1325 mNamedImages.nameNewImage(mCaptureStartTime);
1327 mFaceDetectionStarted = false;
1328 setCameraState(SNAPSHOT_IN_PROGRESS);
1333 public void setFocusParameters() {
1334 setCameraParameters(UPDATE_PARAM_PREFERENCE);
1337 private void updateSceneMode() {
1338 // If scene mode is set, we cannot set flash mode, white balance, and
1339 // focus mode, instead, we read it from driver
1340 if (CameraCapabilities.SceneMode.AUTO != mSceneMode) {
1341 overrideCameraSettings(mCameraSettings.getCurrentFlashMode(),
1342 mCameraSettings.getCurrentFocusMode());
1346 private void overrideCameraSettings(CameraCapabilities.FlashMode flashMode,
1347 CameraCapabilities.FocusMode focusMode) {
1348 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
1349 SettingsManager settingsManager = mActivity.getSettingsManager();
1350 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FLASH_MODE,
1351 stringifier.stringify(flashMode));
1352 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FOCUS_MODE,
1353 stringifier.stringify(focusMode));
1357 public void onOrientationChanged(int orientation) {
1358 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
1362 // TODO: Document orientation compute logic and unify them in OrientationManagerImpl.
1364 // Flip to counter-clockwise orientation.
1365 mOrientation = (360 - orientation) % 360;
1369 public void onCameraAvailable(CameraProxy cameraProxy) {
1370 Log.v(TAG, "onCameraAvailable");
1374 mCameraDevice = cameraProxy;
1376 initializeCapabilities();
1378 // Reset zoom value index.
1380 if (mFocusManager == null) {
1381 initializeFocusManager();
1383 mFocusManager.updateCapabilities(mCameraCapabilities);
1385 // Do camera parameter dependent initialization.
1386 mCameraSettings = mCameraDevice.getSettings();
1387 setCameraParameters(UPDATE_PARAM_ALL);
1388 // Set a listener which updates camera parameters based
1389 // on changed settings.
1390 SettingsManager settingsManager = mActivity.getSettingsManager();
1391 settingsManager.addListener(this);
1392 mCameraPreviewParamsReady = true;
1400 public void onCaptureCancelled() {
1401 mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent());
1406 public void onCaptureRetake() {
1410 mUI.hidePostCaptureAlert();
1411 mUI.hideIntentReviewImageView();
1416 public void onCaptureDone() {
1421 byte[] data = mJpegImageData;
1423 if (mCropValue == null) {
1424 // First handle the no crop case -- just return the value. If the
1425 // caller specifies a "save uri" then write the data to its
1426 // stream. Otherwise, pass back a scaled down version of the bitmap
1427 // directly in the extras.
1428 if (mSaveUri != null) {
1429 OutputStream outputStream = null;
1431 outputStream = mContentResolver.openOutputStream(mSaveUri);
1432 outputStream.write(data);
1433 outputStream.close();
1435 Log.v(TAG, "saved result to URI: " + mSaveUri);
1436 mActivity.setResultEx(Activity.RESULT_OK);
1438 } catch (IOException ex) {
1439 Log.w(TAG, "exception saving result to URI: " + mSaveUri, ex);
1442 CameraUtil.closeSilently(outputStream);
1445 ExifInterface exif = Exif.getExif(data);
1446 int orientation = Exif.getOrientation(exif);
1447 Bitmap bitmap = CameraUtil.makeBitmap(data, 50 * 1024);
1448 bitmap = CameraUtil.rotate(bitmap, orientation);
1449 Log.v(TAG, "inlined bitmap into capture intent result");
1450 mActivity.setResultEx(Activity.RESULT_OK,
1451 new Intent("inline-data").putExtra("data", bitmap));
1455 // Save the image to a temp file and invoke the cropper
1457 FileOutputStream tempStream = null;
1459 File path = mActivity.getFileStreamPath(sTempCropFilename);
1461 tempStream = mActivity.openFileOutput(sTempCropFilename, 0);
1462 tempStream.write(data);
1464 tempUri = Uri.fromFile(path);
1465 Log.v(TAG, "wrote temp file for cropping to: " + sTempCropFilename);
1466 } catch (FileNotFoundException ex) {
1467 Log.w(TAG, "error writing temp cropping file to: " + sTempCropFilename, ex);
1468 mActivity.setResultEx(Activity.RESULT_CANCELED);
1471 } catch (IOException ex) {
1472 Log.w(TAG, "error writing temp cropping file to: " + sTempCropFilename, ex);
1473 mActivity.setResultEx(Activity.RESULT_CANCELED);
1477 CameraUtil.closeSilently(tempStream);
1480 Bundle newExtras = new Bundle();
1481 if (mCropValue.equals("circle")) {
1482 newExtras.putString("circleCrop", "true");
1484 if (mSaveUri != null) {
1485 Log.v(TAG, "setting output of cropped file to: " + mSaveUri);
1486 newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
1488 newExtras.putBoolean(CameraUtil.KEY_RETURN_DATA, true);
1490 if (mActivity.isSecureCamera()) {
1491 newExtras.putBoolean(CameraUtil.KEY_SHOW_WHEN_LOCKED, true);
1494 // TODO: Share this constant.
1495 final String CROP_ACTION = "com.android.camera.action.CROP";
1496 Intent cropIntent = new Intent(CROP_ACTION);
1498 cropIntent.setData(tempUri);
1499 cropIntent.putExtras(newExtras);
1500 Log.v(TAG, "starting CROP intent for capture");
1501 mActivity.startActivityForResult(cropIntent, REQUEST_CROP);
1506 public void onShutterCoordinate(TouchCoordinate coord) {
1507 mShutterTouchCoordinate = coord;
1511 public void onShutterButtonFocus(boolean pressed) {
1512 // Do nothing. We don't support half-press to focus anymore.
1516 public void onShutterButtonClick() {
1517 if (mPaused || (mCameraState == SWITCHING_CAMERA)
1518 || (mCameraState == PREVIEW_STOPPED)) {
1519 mVolumeButtonClickedFlag = false;
1523 // Do not take the picture if there is not enough storage.
1524 if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1525 Log.i(TAG, "Not enough space or storage not ready. remaining="
1526 + mActivity.getStorageSpaceBytes());
1527 mVolumeButtonClickedFlag = false;
1530 Log.d(TAG, "onShutterButtonClick: mCameraState=" + mCameraState +
1531 " mVolumeButtonClickedFlag=" + mVolumeButtonClickedFlag);
1533 int countDownDuration = mActivity.getSettingsManager()
1534 .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);
1535 mTimerDuration = countDownDuration;
1536 if (countDownDuration > 0) {
1537 // Start count down.
1538 mAppController.getCameraAppUI().transitionToCancel();
1539 mAppController.getCameraAppUI().hideModeOptions();
1540 mUI.startCountdown(countDownDuration);
1547 private void focusAndCapture() {
1548 if (mSceneMode == CameraCapabilities.SceneMode.HDR) {
1549 mUI.setSwipingEnabled(false);
1551 // If the user wants to do a snapshot while the previous one is still
1552 // in progress, remember the fact and do it after we finish the previous
1553 // one and re-start the preview. Snapshot in progress also includes the
1554 // state that autofocus is focusing and a picture will be taken when
1555 // focus callback arrives.
1556 if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)) {
1557 if (!mIsImageCaptureIntent) {
1558 mSnapshotOnIdle = true;
1563 mSnapshotOnIdle = false;
1564 mFocusManager.focusAndCapture(mCameraSettings.getCurrentFocusMode());
1568 public void onRemainingSecondsChanged(int remainingSeconds) {
1569 if (remainingSeconds == 1) {
1570 mCountdownSoundPlayer.play(R.raw.beep_twice, 0.6f);
1571 } else if (remainingSeconds == 2 || remainingSeconds == 3) {
1572 mCountdownSoundPlayer.play(R.raw.beep_once, 0.6f);
1577 public void onCountDownFinished() {
1578 if (mIsImageCaptureIntent) {
1579 mAppController.getCameraAppUI().transitionToIntentReviewLayout();
1581 mAppController.getCameraAppUI().transitionToCapture();
1583 mAppController.getCameraAppUI().showModeOptions();
1590 private void onResumeTasks() {
1594 Log.v(TAG, "Executing onResumeTasks.");
1596 mCountdownSoundPlayer.loadSound(R.raw.beep_once);
1597 mCountdownSoundPlayer.loadSound(R.raw.beep_twice);
1598 if (mFocusManager != null) {
1599 // If camera is not open when resume is called, focus manager will
1600 // not be initialized yet, in which case it will start listening to
1601 // preview area size change later in the initialization.
1602 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1604 mAppController.addPreviewAreaSizeChangedListener(mUI);
1606 CameraProvider camProvider = mActivity.getCameraProvider();
1607 if (camProvider == null) {
1608 // No camera provider, the Activity is destroyed already.
1611 requestCameraOpen();
1613 mJpegPictureCallbackTime = 0;
1616 mOnResumeTime = SystemClock.uptimeMillis();
1617 checkDisplayRotation();
1619 // If first time initialization is not finished, put it in the
1621 if (!mFirstTimeInitialized) {
1622 mHandler.sendEmptyMessage(MSG_FIRST_TIME_INIT);
1624 initializeSecondTime();
1627 Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
1628 if (gsensor != null) {
1629 mSensorManager.registerListener(this, gsensor, SensorManager.SENSOR_DELAY_NORMAL);
1632 Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
1633 if (msensor != null) {
1634 mSensorManager.registerListener(this, msensor, SensorManager.SENSOR_DELAY_NORMAL);
1637 getServices().getRemoteShutterListener().onModuleReady(this);
1638 SessionStatsCollector.instance().sessionActive(true);
1642 * @return Whether the currently active camera is front-facing.
1644 private boolean isCameraFrontFacing() {
1645 return mAppController.getCameraProvider().getCharacteristics(mCameraId)
1650 * The focus manager is the first UI related element to get initialized, and
1651 * it requires the RenderOverlay, so initialize it here
1653 private void initializeFocusManager() {
1654 // Create FocusManager object. startPreview needs it.
1655 // if mFocusManager not null, reuse it
1656 // otherwise create a new instance
1657 if (mFocusManager != null) {
1658 mFocusManager.removeMessages();
1660 mMirror = isCameraFrontFacing();
1661 String[] defaultFocusModesStrings = mActivity.getResources().getStringArray(
1662 R.array.pref_camera_focusmode_default_array);
1663 ArrayList<CameraCapabilities.FocusMode> defaultFocusModes =
1664 new ArrayList<CameraCapabilities.FocusMode>();
1665 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
1666 for (String modeString : defaultFocusModesStrings) {
1667 CameraCapabilities.FocusMode mode = stringifier.focusModeFromString(modeString);
1669 defaultFocusModes.add(mode);
1673 new FocusOverlayManager(mAppController, defaultFocusModes,
1674 mCameraCapabilities, this, mMirror, mActivity.getMainLooper(),
1676 MotionManager motionManager = getServices().getMotionManager();
1677 if (motionManager != null) {
1678 motionManager.addListener(mFocusManager);
1681 mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
1685 * @return Whether we are resuming from within the lockscreen.
1687 private boolean isResumeFromLockscreen() {
1688 String action = mActivity.getIntent().getAction();
1689 return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action)
1690 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action));
1694 public void resume() {
1697 // Add delay on resume from lock screen only, in order to to speed up
1698 // the onResume --> onPause --> onResume cycle from lock screen.
1699 // Don't do always because letting go of thread can cause delay.
1700 if (isResumeFromLockscreen()) {
1701 Log.v(TAG, "On resume, from lock screen.");
1702 // Note: onPauseAfterSuper() will delete this runnable, so we will
1703 // at most have 1 copy queued up.
1704 mHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC);
1706 Log.v(TAG, "On resume.");
1712 public void pause() {
1714 mHandler.removeCallbacks(mResumeTaskRunnable);
1715 getServices().getRemoteShutterListener().onModuleExit();
1716 SessionStatsCollector.instance().sessionActive(false);
1718 Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
1719 if (gsensor != null) {
1720 mSensorManager.unregisterListener(this, gsensor);
1723 Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
1724 if (msensor != null) {
1725 mSensorManager.unregisterListener(this, msensor);
1728 // Reset the focus first. Camera CTS does not guarantee that
1729 // cancelAutoFocus is allowed after preview stops.
1730 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1731 mCameraDevice.cancelAutoFocus();
1734 // If the camera has not been opened asynchronously yet,
1735 // and startPreview hasn't been called, then this is a no-op.
1736 // (e.g. onResume -> onPause -> onResume).
1739 mCountdownSoundPlayer.release();
1741 mNamedImages = null;
1742 // If we are in an image capture intent and has taken
1743 // a picture, we just clear it in onPause.
1744 mJpegImageData = null;
1746 // Remove the messages and runnables in the queue.
1747 mHandler.removeCallbacksAndMessages(null);
1750 mActivity.enableKeepScreenOn(false);
1753 mPendingSwitchCameraId = -1;
1754 if (mFocusManager != null) {
1755 mFocusManager.removeMessages();
1757 getServices().getMemoryManager().removeListener(this);
1758 mAppController.removePreviewAreaSizeChangedListener(mFocusManager);
1759 mAppController.removePreviewAreaSizeChangedListener(mUI);
1761 SettingsManager settingsManager = mActivity.getSettingsManager();
1762 settingsManager.removeListener(this);
1766 public void destroy() {
1767 // TODO: implement this.
1771 public void onLayoutOrientationChanged(boolean isLandscape) {
1772 setDisplayOrientation();
1776 public void updateCameraOrientation() {
1777 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) {
1778 setDisplayOrientation();
1782 private boolean canTakePicture() {
1783 return isCameraIdle()
1784 && (mActivity.getStorageSpaceBytes() > Storage.LOW_STORAGE_THRESHOLD_BYTES);
1788 public void autoFocus() {
1789 Log.v(TAG,"Starting auto focus");
1790 mFocusStartTime = System.currentTimeMillis();
1791 mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
1792 SessionStatsCollector.instance().autofocusManualTrigger();
1793 setCameraState(FOCUSING);
1797 public void cancelAutoFocus() {
1798 mCameraDevice.cancelAutoFocus();
1799 setCameraState(IDLE);
1800 setCameraParameters(UPDATE_PARAM_PREFERENCE);
1804 public void onSingleTapUp(View view, int x, int y) {
1805 if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
1806 || mCameraState == SNAPSHOT_IN_PROGRESS
1807 || mCameraState == SWITCHING_CAMERA
1808 || mCameraState == PREVIEW_STOPPED) {
1812 // Check if metering area or focus area is supported.
1813 if (!mFocusAreaSupported && !mMeteringAreaSupported) {
1816 mFocusManager.onSingleTapUp(x, y);
1820 public boolean onBackPressed() {
1821 return mUI.onBackPressed();
1825 public boolean onKeyDown(int keyCode, KeyEvent event) {
1827 case KeyEvent.KEYCODE_VOLUME_UP:
1828 case KeyEvent.KEYCODE_VOLUME_DOWN:
1829 case KeyEvent.KEYCODE_FOCUS:
1830 if (/* TODO: mActivity.isInCameraApp() && */mFirstTimeInitialized &&
1831 !mActivity.getCameraAppUI().isInIntentReview()) {
1832 if (event.getRepeatCount() == 0) {
1833 onShutterButtonFocus(true);
1838 case KeyEvent.KEYCODE_CAMERA:
1839 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1840 onShutterButtonClick();
1843 case KeyEvent.KEYCODE_DPAD_CENTER:
1844 // If we get a dpad center event without any focused view, move
1845 // the focus to the shutter button and press it.
1846 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1847 // Start auto-focus immediately to reduce shutter lag. After
1848 // the shutter button gets the focus, onShutterButtonFocus()
1849 // will be called again but it is fine.
1850 onShutterButtonFocus(true);
1858 public boolean onKeyUp(int keyCode, KeyEvent event) {
1860 case KeyEvent.KEYCODE_VOLUME_UP:
1861 case KeyEvent.KEYCODE_VOLUME_DOWN:
1862 if (/* mActivity.isInCameraApp() && */mFirstTimeInitialized &&
1863 !mActivity.getCameraAppUI().isInIntentReview()) {
1864 if (mUI.isCountingDown()) {
1867 mVolumeButtonClickedFlag = true;
1868 onShutterButtonClick();
1873 case KeyEvent.KEYCODE_FOCUS:
1874 if (mFirstTimeInitialized) {
1875 onShutterButtonFocus(false);
1882 private void closeCamera() {
1883 if (mCameraDevice != null) {
1884 stopFaceDetection();
1885 mCameraDevice.setZoomChangeListener(null);
1886 mCameraDevice.setFaceDetectionCallback(null, null);
1887 mCameraDevice.setErrorCallback(null, null);
1889 mFaceDetectionStarted = false;
1890 mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
1891 mCameraDevice = null;
1892 setCameraState(PREVIEW_STOPPED);
1893 mFocusManager.onCameraReleased();
1897 private void setDisplayOrientation() {
1898 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity);
1899 Characteristics info =
1900 mActivity.getCameraProvider().getCharacteristics(mCameraId);
1901 mDisplayOrientation = info.getPreviewOrientation(mDisplayRotation);
1902 mCameraDisplayOrientation = mDisplayOrientation;
1903 mUI.setDisplayOrientation(mDisplayOrientation);
1904 if (mFocusManager != null) {
1905 mFocusManager.setDisplayOrientation(mDisplayOrientation);
1907 // Change the camera display orientation
1908 if (mCameraDevice != null) {
1909 mCameraDevice.setDisplayOrientation(mDisplayRotation);
1913 /** Only called by UI thread. */
1914 private void setupPreview() {
1915 mFocusManager.resetTouchFocus();
1920 * Returns whether we can/should start the preview or not.
1922 private boolean checkPreviewPreconditions() {
1927 if (mCameraDevice == null) {
1928 Log.w(TAG, "startPreview: camera device not ready yet.");
1932 SurfaceTexture st = mActivity.getCameraAppUI().getSurfaceTexture();
1934 Log.w(TAG, "startPreview: surfaceTexture is not ready.");
1938 if (!mCameraPreviewParamsReady) {
1939 Log.w(TAG, "startPreview: parameters for preview is not ready.");
1946 * The start/stop preview should only run on the UI thread.
1948 private void startPreview() {
1949 if (!checkPreviewPreconditions()) {
1953 mCameraDevice.setErrorCallback(mHandler, mErrorCallback);
1954 setDisplayOrientation();
1956 if (!mSnapshotOnIdle) {
1957 // If the focus mode is continuous autofocus, call cancelAutoFocus
1958 // to resume it because it may have been paused by autoFocus call.
1959 if (mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) ==
1960 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
1961 mCameraDevice.cancelAutoFocus();
1963 mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
1965 setCameraParameters(UPDATE_PARAM_ALL);
1966 mCameraDevice.setPreviewTexture(mActivity.getCameraAppUI().getSurfaceTexture());
1968 Log.i(TAG, "startPreview");
1969 mCameraDevice.startPreviewWithCallback(mHandler, new CameraStartPreviewCallback() {
1971 public void onPreviewStarted() {
1972 mFocusManager.onPreviewStarted();
1973 PhotoModule.this.onPreviewStarted();
1977 SessionStatsCollector.instance().previewActive(true);
1978 if (mSnapshotOnIdle) {
1979 mHandler.post(mDoSnapRunnable);
1984 public void stopPreview() {
1985 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1986 Log.i(TAG, "stopPreview");
1987 mCameraDevice.stopPreview();
1988 mFaceDetectionStarted = false;
1990 setCameraState(PREVIEW_STOPPED);
1991 if (mFocusManager != null) {
1992 mFocusManager.onPreviewStopped();
1994 SessionStatsCollector.instance().previewActive(false);
1998 public void onSettingChanged(SettingsManager settingsManager, String key) {
1999 if (key.equals(Keys.KEY_FLASH_MODE)) {
2000 updateParametersFlashMode();
2002 if (key.equals(Keys.KEY_CAMERA_HDR)) {
2003 if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
2004 Keys.KEY_CAMERA_HDR)) {
2006 mAppController.getButtonManager().disableButton(ButtonManager.BUTTON_FLASH);
2007 mFlashModeBeforeSceneMode = settingsManager.getString(
2008 mAppController.getCameraScope(), Keys.KEY_FLASH_MODE);
2010 if (mFlashModeBeforeSceneMode != null) {
2011 settingsManager.set(mAppController.getCameraScope(),
2012 Keys.KEY_FLASH_MODE,
2013 mFlashModeBeforeSceneMode);
2014 updateParametersFlashMode();
2015 mFlashModeBeforeSceneMode = null;
2017 mAppController.getButtonManager().enableButton(ButtonManager.BUTTON_FLASH);
2021 if (mCameraDevice != null) {
2022 mCameraDevice.applySettings(mCameraSettings);
2026 private void updateCameraParametersInitialize() {
2027 // Reset preview frame rate to the maximum because it may be lowered by
2028 // video camera application.
2029 int[] fpsRange = CameraUtil.getPhotoPreviewFpsRange(mCameraCapabilities);
2030 if (fpsRange != null && fpsRange.length > 0) {
2031 mCameraSettings.setPreviewFpsRange(fpsRange[0], fpsRange[1]);
2034 mCameraSettings.setRecordingHintEnabled(false);
2036 if (mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_STABILIZATION)) {
2037 mCameraSettings.setVideoStabilization(false);
2041 private void updateCameraParametersZoom() {
2043 if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
2044 mCameraSettings.setZoomRatio(mZoomValue);
2048 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
2049 private void setAutoExposureLockIfSupported() {
2050 if (mAeLockSupported) {
2051 mCameraSettings.setAutoExposureLock(mFocusManager.getAeAwbLock());
2055 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
2056 private void setAutoWhiteBalanceLockIfSupported() {
2057 if (mAwbLockSupported) {
2058 mCameraSettings.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
2062 private void setFocusAreasIfSupported() {
2063 if (mFocusAreaSupported) {
2064 mCameraSettings.setFocusAreas(mFocusManager.getFocusAreas());
2068 private void setMeteringAreasIfSupported() {
2069 if (mMeteringAreaSupported) {
2070 mCameraSettings.setMeteringAreas(mFocusManager.getMeteringAreas());
2074 private void updateCameraParametersPreference() {
2075 setAutoExposureLockIfSupported();
2076 setAutoWhiteBalanceLockIfSupported();
2077 setFocusAreasIfSupported();
2078 setMeteringAreasIfSupported();
2080 // Initialize focus mode.
2081 mFocusManager.overrideFocusMode(null);
2083 .setFocusMode(mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
2084 SessionStatsCollector.instance().autofocusActive(
2085 mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) ==
2086 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE
2089 // Set picture size.
2090 updateParametersPictureSize();
2092 // Set JPEG quality.
2093 updateParametersPictureQuality();
2095 // For the following settings, we need to check if the settings are
2096 // still supported by latest driver, if not, ignore the settings.
2098 // Set exposure compensation
2099 updateParametersExposureCompensation();
2101 // Set the scene mode: also sets flash and white balance.
2102 updateParametersSceneMode();
2104 if (mContinuousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
2105 updateAutoFocusMoveCallback();
2109 private void updateParametersPictureSize() {
2110 SettingsManager settingsManager = mActivity.getSettingsManager();
2111 String pictureSizeKey = isCameraFrontFacing() ? Keys.KEY_PICTURE_SIZE_FRONT
2112 : Keys.KEY_PICTURE_SIZE_BACK;
2113 String pictureSize = settingsManager.getString(SettingsManager.SCOPE_GLOBAL,
2116 List<Size> supported = mCameraCapabilities.getSupportedPhotoSizes();
2117 CameraPictureSizesCacher.updateSizesForCamera(mAppController.getAndroidContext(),
2118 mCameraDevice.getCameraId(), supported);
2119 SettingsUtil.setCameraPictureSize(pictureSize, supported, mCameraSettings,
2120 mCameraDevice.getCameraId());
2122 Size size = SettingsUtil.getPhotoSize(pictureSize, supported,
2123 mCameraDevice.getCameraId());
2124 if (ApiHelper.IS_NEXUS_5) {
2125 if (ResolutionUtil.NEXUS_5_LARGE_16_BY_9.equals(pictureSize)) {
2126 mShouldResizeTo16x9 = true;
2128 mShouldResizeTo16x9 = false;
2132 // Set a preview size that is closest to the viewfinder height and has
2133 // the right aspect ratio.
2134 List<Size> sizes = mCameraCapabilities.getSupportedPreviewSizes();
2135 Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes,
2136 (double) size.width() / size.height());
2137 Size original = mCameraSettings.getCurrentPreviewSize();
2138 if (!optimalSize.equals(original)) {
2139 Log.v(TAG, "setting preview size");
2140 mCameraSettings.setPreviewSize(optimalSize);
2142 // Zoom related settings will be changed for different preview
2143 // sizes, so set and read the parameters to get latest values
2144 if (mHandler.getLooper() == Looper.myLooper()) {
2145 // On UI thread only, not when camera starts up
2148 mCameraDevice.applySettings(mCameraSettings);
2150 mCameraSettings = mCameraDevice.getSettings();
2153 if (optimalSize.width() != 0 && optimalSize.height() != 0) {
2154 Log.v(TAG, "updating aspect ratio");
2155 mUI.updatePreviewAspectRatio((float) optimalSize.width()
2156 / (float) optimalSize.height());
2158 Log.d(TAG, "Preview size is " + optimalSize);
2161 private void updateParametersPictureQuality() {
2162 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
2163 CameraProfile.QUALITY_HIGH);
2164 mCameraSettings.setPhotoJpegCompressionQuality(jpegQuality);
2167 private void updateParametersExposureCompensation() {
2168 SettingsManager settingsManager = mActivity.getSettingsManager();
2169 if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
2170 Keys.KEY_EXPOSURE_COMPENSATION_ENABLED)) {
2171 int value = settingsManager.getInteger(mAppController.getCameraScope(),
2173 int max = mCameraCapabilities.getMaxExposureCompensation();
2174 int min = mCameraCapabilities.getMinExposureCompensation();
2175 if (value >= min && value <= max) {
2176 mCameraSettings.setExposureCompensationIndex(value);
2178 Log.w(TAG, "invalid exposure range: " + value);
2181 // If exposure compensation is not enabled, reset the exposure compensation value.
2182 setExposureCompensation(0);
2187 private void updateParametersSceneMode() {
2188 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
2189 SettingsManager settingsManager = mActivity.getSettingsManager();
2191 mSceneMode = stringifier.
2192 sceneModeFromString(settingsManager.getString(mAppController.getCameraScope(),
2193 Keys.KEY_SCENE_MODE));
2194 if (mCameraCapabilities.supports(mSceneMode)) {
2195 if (mCameraSettings.getCurrentSceneMode() != mSceneMode) {
2196 mCameraSettings.setSceneMode(mSceneMode);
2198 // Setting scene mode will change the settings of flash mode,
2199 // white balance, and focus mode. Here we read back the
2200 // parameters, so we can know those settings.
2201 mCameraDevice.applySettings(mCameraSettings);
2202 mCameraSettings = mCameraDevice.getSettings();
2205 mSceneMode = mCameraSettings.getCurrentSceneMode();
2206 if (mSceneMode == null) {
2207 mSceneMode = CameraCapabilities.SceneMode.AUTO;
2211 if (CameraCapabilities.SceneMode.AUTO == mSceneMode) {
2213 updateParametersFlashMode();
2216 mFocusManager.overrideFocusMode(null);
2217 mCameraSettings.setFocusMode(
2218 mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
2220 mFocusManager.overrideFocusMode(mCameraSettings.getCurrentFocusMode());
2224 private void updateParametersFlashMode() {
2225 SettingsManager settingsManager = mActivity.getSettingsManager();
2227 CameraCapabilities.FlashMode flashMode = mCameraCapabilities.getStringifier()
2228 .flashModeFromString(settingsManager.getString(mAppController.getCameraScope(),
2229 Keys.KEY_FLASH_MODE));
2230 if (mCameraCapabilities.supports(flashMode)) {
2231 mCameraSettings.setFlashMode(flashMode);
2235 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
2236 private void updateAutoFocusMoveCallback() {
2237 if (mCameraSettings.getCurrentFocusMode() ==
2238 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
2239 mCameraDevice.setAutoFocusMoveCallback(mHandler,
2240 (CameraAFMoveCallback) mAutoFocusMoveCallback);
2242 mCameraDevice.setAutoFocusMoveCallback(null, null);
2247 * Sets the exposure compensation to the given value and also updates settings.
2249 * @param value exposure compensation value to be set
2251 public void setExposureCompensation(int value) {
2252 int max = mCameraCapabilities.getMaxExposureCompensation();
2253 int min = mCameraCapabilities.getMinExposureCompensation();
2254 if (value >= min && value <= max) {
2255 mCameraSettings.setExposureCompensationIndex(value);
2256 SettingsManager settingsManager = mActivity.getSettingsManager();
2257 settingsManager.set(mAppController.getCameraScope(),
2258 Keys.KEY_EXPOSURE, value);
2260 Log.w(TAG, "invalid exposure range: " + value);
2264 // We separate the parameters into several subsets, so we can update only
2265 // the subsets actually need updating. The PREFERENCE set needs extra
2266 // locking because the preference can be changed from GLThread as well.
2267 private void setCameraParameters(int updateSet) {
2268 if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
2269 updateCameraParametersInitialize();
2272 if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
2273 updateCameraParametersZoom();
2276 if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
2277 updateCameraParametersPreference();
2280 mCameraDevice.applySettings(mCameraSettings);
2283 // If the Camera is idle, update the parameters immediately, otherwise
2284 // accumulate them in mUpdateSet and update later.
2285 private void setCameraParametersWhenIdle(int additionalUpdateSet) {
2286 mUpdateSet |= additionalUpdateSet;
2287 if (mCameraDevice == null) {
2288 // We will update all the parameters when we open the device, so
2289 // we don't need to do anything now.
2292 } else if (isCameraIdle()) {
2293 setCameraParameters(mUpdateSet);
2297 if (!mHandler.hasMessages(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
2298 mHandler.sendEmptyMessageDelayed(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
2304 public boolean isCameraIdle() {
2305 return (mCameraState == IDLE) ||
2306 (mCameraState == PREVIEW_STOPPED) ||
2307 ((mFocusManager != null) && mFocusManager.isFocusCompleted()
2308 && (mCameraState != SWITCHING_CAMERA));
2312 public boolean isImageCaptureIntent() {
2313 String action = mActivity.getIntent().getAction();
2314 return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
2315 || CameraActivity.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
2318 private void setupCaptureParams() {
2319 Bundle myExtras = mActivity.getIntent().getExtras();
2320 if (myExtras != null) {
2321 mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
2322 mCropValue = myExtras.getString("crop");
2326 private void initializeCapabilities() {
2327 mCameraCapabilities = mCameraDevice.getCapabilities();
2328 mFocusAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA);
2329 mMeteringAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.METERING_AREA);
2330 mAeLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_EXPOSURE_LOCK);
2331 mAwbLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_WHITE_BALANCE_LOCK);
2332 mContinuousFocusSupported =
2333 mCameraCapabilities.supports(CameraCapabilities.FocusMode.CONTINUOUS_PICTURE);
2337 public void onZoomChanged(float ratio) {
2338 // Not useful to change zoom value when the activity is paused.
2343 if (mCameraSettings == null || mCameraDevice == null) {
2346 // Set zoom parameters asynchronously
2347 mCameraSettings.setZoomRatio(mZoomValue);
2348 mCameraDevice.applySettings(mCameraSettings);
2352 public int getCameraState() {
2353 return mCameraState;
2357 public void onMemoryStateChanged(int state) {
2358 mAppController.setShutterEnabled(state == MemoryManager.STATE_OK);
2362 public void onLowMemory() {
2363 // Not much we can do in the photo module.
2367 public void onAccuracyChanged(Sensor sensor, int accuracy) {
2371 public void onSensorChanged(SensorEvent event) {
2372 int type = event.sensor.getType();
2374 if (type == Sensor.TYPE_ACCELEROMETER) {
2376 } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
2379 // we should not be here.
2382 for (int i = 0; i < 3; i++) {
2383 data[i] = event.values[i];
2385 float[] orientation = new float[3];
2386 SensorManager.getRotationMatrix(mR, null, mGData, mMData);
2387 SensorManager.getOrientation(mR, orientation);
2388 mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
2394 // For debugging only.
2395 public void setDebugUri(Uri uri) {
2399 // For debugging only.
2400 private void saveToDebugUri(byte[] data) {
2401 if (mDebugUri != null) {
2402 OutputStream outputStream = null;
2404 outputStream = mContentResolver.openOutputStream(mDebugUri);
2405 outputStream.write(data);
2406 outputStream.close();
2407 } catch (IOException e) {
2408 Log.e(TAG, "Exception while writing debug jpeg file", e);
2410 CameraUtil.closeSilently(outputStream);
2416 public void onRemoteShutterPress() {
2417 mHandler.post(new Runnable() {