PhotoModule.java revision 4de9b72f79fe256766f25497bff44cb5533b7508
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17package com.android.camera; 18 19import android.annotation.TargetApi; 20import android.app.Activity; 21import android.content.ContentResolver; 22import android.content.Context; 23import android.content.Intent; 24import android.graphics.Bitmap; 25import android.graphics.BitmapFactory; 26import android.graphics.SurfaceTexture; 27import android.hardware.Sensor; 28import android.hardware.SensorEvent; 29import android.hardware.SensorEventListener; 30import android.hardware.SensorManager; 31import android.location.Location; 32import android.media.AudioManager; 33import android.media.CameraProfile; 34import android.media.SoundPool; 35import android.net.Uri; 36import android.os.AsyncTask; 37import android.os.Build; 38import android.os.Bundle; 39import android.os.Handler; 40import android.os.Looper; 41import android.os.Message; 42import android.os.MessageQueue; 43import android.os.SystemClock; 44import android.provider.MediaStore; 45import android.view.KeyEvent; 46import android.view.OrientationEventListener; 47import android.view.View; 48 49import com.android.camera.PhotoModule.NamedImages.NamedEntity; 50import com.android.camera.app.AppController; 51import com.android.camera.app.CameraAppUI; 52import com.android.camera.app.CameraProvider; 53import com.android.camera.app.MediaSaver; 54import com.android.camera.app.MemoryManager; 55import com.android.camera.app.MemoryManager.MemoryListener; 56import com.android.camera.app.MotionManager; 57import com.android.camera.debug.Log; 58import com.android.camera.exif.ExifInterface; 59import com.android.camera.exif.ExifTag; 60import com.android.camera.exif.Rational; 61import com.android.camera.hardware.HardwareSpec; 62import com.android.camera.hardware.HardwareSpecImpl; 63import com.android.camera.module.ModuleController; 64import com.android.camera.remote.RemoteCameraModule; 65import com.android.camera.settings.CameraPictureSizesCacher; 66import com.android.camera.settings.Keys; 67import com.android.camera.settings.ResolutionUtil; 68import com.android.camera.settings.SettingsManager; 69import com.android.camera.settings.SettingsUtil; 70import com.android.camera.ui.CountDownView; 71import com.android.camera.ui.TouchCoordinate; 72import com.android.camera.util.ApiHelper; 73import com.android.camera.util.CameraUtil; 74import com.android.camera.util.GcamHelper; 75import com.android.camera.util.GservicesHelper; 76import com.android.camera.util.SessionStatsCollector; 77import com.android.camera.util.UsageStatistics; 78import com.android.camera.widget.AspectRatioSelector; 79import com.android.camera2.R; 80import com.android.ex.camera2.portability.CameraAgent; 81import com.android.ex.camera2.portability.CameraAgent.CameraAFCallback; 82import com.android.ex.camera2.portability.CameraAgent.CameraAFMoveCallback; 83import com.android.ex.camera2.portability.CameraAgent.CameraPictureCallback; 84import com.android.ex.camera2.portability.CameraAgent.CameraProxy; 85import com.android.ex.camera2.portability.CameraAgent.CameraShutterCallback; 86import com.android.ex.camera2.portability.CameraCapabilities; 87import com.android.ex.camera2.portability.CameraDeviceInfo.Characteristics; 88import com.android.ex.camera2.portability.CameraSettings; 89import com.android.ex.camera2.portability.Size; 90import com.google.common.logging.eventprotos; 91 92import java.io.ByteArrayOutputStream; 93import java.io.File; 94import java.io.FileNotFoundException; 95import java.io.FileOutputStream; 96import java.io.IOException; 97import java.io.OutputStream; 98import java.lang.ref.WeakReference; 99import java.util.ArrayList; 100import java.util.List; 101import java.util.Vector; 102 103public class PhotoModule 104 extends CameraModule 105 implements PhotoController, 106 ModuleController, 107 MemoryListener, 108 FocusOverlayManager.Listener, 109 SensorEventListener, 110 SettingsManager.OnSettingChangedListener, 111 RemoteCameraModule, 112 CountDownView.OnCountDownStatusListener { 113 114 public static final String PHOTO_MODULE_STRING_ID = "PhotoModule"; 115 116 private static final Log.Tag TAG = new Log.Tag(PHOTO_MODULE_STRING_ID); 117 118 // We number the request code from 1000 to avoid collision with Gallery. 119 private static final int REQUEST_CROP = 1000; 120 121 // Messages defined for the UI thread handler. 122 private static final int MSG_FIRST_TIME_INIT = 1; 123 private static final int MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE = 2; 124 125 // The subset of parameters we need to update in setCameraParameters(). 126 private static final int UPDATE_PARAM_INITIALIZE = 1; 127 private static final int UPDATE_PARAM_ZOOM = 2; 128 private static final int UPDATE_PARAM_PREFERENCE = 4; 129 private static final int UPDATE_PARAM_ALL = -1; 130 131 private static final String DEBUG_IMAGE_PREFIX = "DEBUG_"; 132 133 private CameraActivity mActivity; 134 private CameraProxy mCameraDevice; 135 private int mCameraId; 136 private CameraCapabilities mCameraCapabilities; 137 private CameraSettings mCameraSettings; 138 private boolean mPaused; 139 140 private PhotoUI mUI; 141 142 // The activity is going to switch to the specified camera id. This is 143 // needed because texture copy is done in GL thread. -1 means camera is not 144 // switching. 145 protected int mPendingSwitchCameraId = -1; 146 147 // When setCameraParametersWhenIdle() is called, we accumulate the subsets 148 // needed to be updated in mUpdateSet. 149 private int mUpdateSet; 150 151 private float mZoomValue; // The current zoom ratio. 152 private int mTimerDuration; 153 /** Set when a volume button is clicked to take photo */ 154 private boolean mVolumeButtonClickedFlag = false; 155 156 private boolean mFocusAreaSupported; 157 private boolean mMeteringAreaSupported; 158 private boolean mAeLockSupported; 159 private boolean mAwbLockSupported; 160 private boolean mContinuousFocusSupported; 161 162 /* 163 * If true, attempts to start the preview will be denied. This ensures that 164 * we never call startPreview multiple times when making changes to 165 * settings. 166 */ 167 private boolean mStartPreviewLock = false; 168 169 // The degrees of the device rotated clockwise from its natural orientation. 170 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN; 171 172 private static final String sTempCropFilename = "crop-temp"; 173 174 private boolean mFaceDetectionStarted = false; 175 176 // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true. 177 private String mCropValue; 178 private Uri mSaveUri; 179 180 private Uri mDebugUri; 181 182 // We use a queue to generated names of the images to be used later 183 // when the image is ready to be saved. 184 private NamedImages mNamedImages; 185 186 private final Runnable mDoSnapRunnable = new Runnable() { 187 @Override 188 public void run() { 189 onShutterButtonClick(); 190 } 191 }; 192 193 /** 194 * An unpublished intent flag requesting to return as soon as capturing is 195 * completed. TODO: consider publishing by moving into MediaStore. 196 */ 197 private static final String EXTRA_QUICK_CAPTURE = 198 "android.intent.extra.quickCapture"; 199 200 // The display rotation in degrees. This is only valid when mCameraState is 201 // not PREVIEW_STOPPED. 202 private int mDisplayRotation; 203 // The value for android.hardware.Camera.setDisplayOrientation. 204 private int mCameraDisplayOrientation; 205 // The value for UI components like indicators. 206 private int mDisplayOrientation; 207 // The value for cameradevice.CameraSettings.setPhotoRotationDegrees. 208 private int mJpegRotation; 209 // Indicates whether we are using front camera 210 private boolean mMirror; 211 private boolean mFirstTimeInitialized; 212 private boolean mIsImageCaptureIntent; 213 214 private int mCameraState = PREVIEW_STOPPED; 215 private boolean mSnapshotOnIdle = false; 216 217 private ContentResolver mContentResolver; 218 219 private AppController mAppController; 220 221 private final PostViewPictureCallback mPostViewPictureCallback = 222 new PostViewPictureCallback(); 223 private final RawPictureCallback mRawPictureCallback = 224 new RawPictureCallback(); 225 private final AutoFocusCallback mAutoFocusCallback = 226 new AutoFocusCallback(); 227 private final Object mAutoFocusMoveCallback = 228 ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK 229 ? new AutoFocusMoveCallback() 230 : null; 231 232 private final CameraErrorCallback mErrorCallback = new CameraErrorCallback(); 233 234 private long mFocusStartTime; 235 private long mShutterCallbackTime; 236 private long mPostViewPictureCallbackTime; 237 private long mRawPictureCallbackTime; 238 private long mJpegPictureCallbackTime; 239 private long mOnResumeTime; 240 private byte[] mJpegImageData; 241 /** Touch coordinate for shutter button press. */ 242 private TouchCoordinate mShutterTouchCoordinate; 243 244 245 // These latency time are for the CameraLatency test. 246 public long mAutoFocusTime; 247 public long mShutterLag; 248 public long mShutterToPictureDisplayedTime; 249 public long mPictureDisplayedToJpegCallbackTime; 250 public long mJpegCallbackFinishTime; 251 public long mCaptureStartTime; 252 253 // This handles everything about focus. 254 private FocusOverlayManager mFocusManager; 255 256 private final int mGcamModeIndex; 257 private SoundPlayer mCountdownSoundPlayer; 258 259 private CameraCapabilities.SceneMode mSceneMode; 260 261 private final Handler mHandler = new MainHandler(this); 262 263 private boolean mQuickCapture; 264 private SensorManager mSensorManager; 265 private final float[] mGData = new float[3]; 266 private final float[] mMData = new float[3]; 267 private final float[] mR = new float[16]; 268 private int mHeading = -1; 269 270 /** True if all the parameters needed to start preview is ready. */ 271 private boolean mCameraPreviewParamsReady = false; 272 273 private final MediaSaver.OnMediaSavedListener mOnMediaSavedListener = 274 new MediaSaver.OnMediaSavedListener() { 275 @Override 276 public void onMediaSaved(Uri uri) { 277 if (uri != null) { 278 mActivity.notifyNewMedia(uri); 279 } 280 } 281 }; 282 private boolean mShouldResizeTo16x9 = false; 283 284 /** 285 * We keep the flash setting before entering scene modes (HDR) 286 * and restore it after HDR is off. 287 */ 288 private String mFlashModeBeforeSceneMode; 289 290 /** 291 * This callback gets called when user select whether or not to 292 * turn on geo-tagging. 293 */ 294 public interface LocationDialogCallback { 295 /** 296 * Gets called after user selected/unselected geo-tagging feature. 297 * 298 * @param selected whether or not geo-tagging feature is selected 299 */ 300 public void onLocationTaggingSelected(boolean selected); 301 } 302 303 /** 304 * This callback defines the text that is shown in the aspect ratio selection 305 * dialog, provides the current aspect ratio, and gets notified when user changes 306 * aspect ratio selection in the dialog. 307 */ 308 public interface AspectRatioDialogCallback { 309 /** 310 * Returns current aspect ratio that is being used to set as default. 311 */ 312 public AspectRatioSelector.AspectRatio getCurrentAspectRatio(); 313 314 /** 315 * Gets notified when user has made the aspect ratio selection. 316 * 317 * @param newAspectRatio aspect ratio that user has selected 318 * @param dialogHandlingFinishedRunnable runnable to run when the operations 319 * needed to handle changes from dialog 320 * are finished. 321 */ 322 public void onAspectRatioSelected(AspectRatioSelector.AspectRatio newAspectRatio, 323 Runnable dialogHandlingFinishedRunnable); 324 } 325 326 private void checkDisplayRotation() { 327 // Set the display orientation if display rotation has changed. 328 // Sometimes this happens when the device is held upside 329 // down and camera app is opened. Rotation animation will 330 // take some time and the rotation value we have got may be 331 // wrong. Framework does not have a callback for this now. 332 if (CameraUtil.getDisplayRotation(mActivity) != mDisplayRotation) { 333 setDisplayOrientation(); 334 } 335 if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) { 336 mHandler.postDelayed(new Runnable() { 337 @Override 338 public void run() { 339 checkDisplayRotation(); 340 } 341 }, 100); 342 } 343 } 344 345 /** 346 * This Handler is used to post message back onto the main thread of the 347 * application 348 */ 349 private static class MainHandler extends Handler { 350 private final WeakReference<PhotoModule> mModule; 351 352 public MainHandler(PhotoModule module) { 353 super(Looper.getMainLooper()); 354 mModule = new WeakReference<PhotoModule>(module); 355 } 356 357 @Override 358 public void handleMessage(Message msg) { 359 PhotoModule module = mModule.get(); 360 if (module == null) { 361 return; 362 } 363 switch (msg.what) { 364 case MSG_FIRST_TIME_INIT: { 365 module.initializeFirstTime(); 366 break; 367 } 368 369 case MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE: { 370 module.setCameraParametersWhenIdle(0); 371 break; 372 } 373 } 374 } 375 } 376 377 private void switchToGcamCapture() { 378 if (mActivity != null && mGcamModeIndex != 0) { 379 SettingsManager settingsManager = mActivity.getSettingsManager(); 380 settingsManager.set(SettingsManager.SCOPE_GLOBAL, 381 Keys.KEY_CAMERA_HDR_PLUS, true); 382 383 // Disable the HDR+ button to prevent callbacks from being 384 // queued before the correct callback is attached to the button 385 // in the new module. The new module will set the enabled/disabled 386 // of this button when the module's preferred camera becomes available. 387 ButtonManager buttonManager = mActivity.getButtonManager(); 388 389 buttonManager.disableButtonClick(ButtonManager.BUTTON_HDR_PLUS); 390 391 mAppController.getCameraAppUI().freezeScreenUntilPreviewReady(); 392 393 // Do not post this to avoid this module switch getting interleaved with 394 // other button callbacks. 395 mActivity.onModeSelected(mGcamModeIndex); 396 397 buttonManager.enableButtonClick(ButtonManager.BUTTON_HDR_PLUS); 398 } 399 } 400 401 /** 402 * Constructs a new photo module. 403 */ 404 public PhotoModule(AppController app) { 405 super(app); 406 mGcamModeIndex = app.getAndroidContext().getResources() 407 .getInteger(R.integer.camera_mode_gcam); 408 } 409 410 @Override 411 public String getPeekAccessibilityString() { 412 return mAppController.getAndroidContext() 413 .getResources().getString(R.string.photo_accessibility_peek); 414 } 415 416 @Override 417 public String getModuleStringIdentifier() { 418 return PHOTO_MODULE_STRING_ID; 419 } 420 421 @Override 422 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) { 423 mActivity = activity; 424 // TODO: Need to look at the controller interface to see if we can get 425 // rid of passing in the activity directly. 426 mAppController = mActivity; 427 428 mUI = new PhotoUI(mActivity, this, mActivity.getModuleLayoutRoot()); 429 mActivity.setPreviewStatusListener(mUI); 430 431 SettingsManager settingsManager = mActivity.getSettingsManager(); 432 mCameraId = settingsManager.getInteger(mAppController.getModuleScope(), 433 Keys.KEY_CAMERA_ID); 434 435 // TODO: Move this to SettingsManager as a part of upgrade procedure. 436 if (!settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 437 Keys.KEY_USER_SELECTED_ASPECT_RATIO)) { 438 // Switch to back camera to set aspect ratio. 439 mCameraId = settingsManager.getIntegerDefault(Keys.KEY_CAMERA_ID); 440 } 441 442 mContentResolver = mActivity.getContentResolver(); 443 444 // Surface texture is from camera screen nail and startPreview needs it. 445 // This must be done before startPreview. 446 mIsImageCaptureIntent = isImageCaptureIntent(); 447 448 mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false); 449 mSensorManager = (SensorManager) (mActivity.getSystemService(Context.SENSOR_SERVICE)); 450 mUI.setCountdownFinishedListener(this); 451 mCountdownSoundPlayer = new SoundPlayer(mAppController.getAndroidContext()); 452 453 // TODO: Make this a part of app controller API. 454 View cancelButton = mActivity.findViewById(R.id.shutter_cancel_button); 455 cancelButton.setOnClickListener(new View.OnClickListener() { 456 @Override 457 public void onClick(View view) { 458 cancelCountDown(); 459 } 460 }); 461 } 462 463 private void cancelCountDown() { 464 if (mUI.isCountingDown()) { 465 // Cancel on-going countdown. 466 mUI.cancelCountDown(); 467 } 468 mAppController.getCameraAppUI().transitionToCapture(); 469 mAppController.getCameraAppUI().showModeOptions(); 470 } 471 472 @Override 473 public boolean isUsingBottomBar() { 474 return true; 475 } 476 477 private void initializeControlByIntent() { 478 if (mIsImageCaptureIntent) { 479 mActivity.getCameraAppUI().transitionToIntentCaptureLayout(); 480 setupCaptureParams(); 481 } 482 } 483 484 private void onPreviewStarted() { 485 mAppController.onPreviewStarted(); 486 mAppController.setShutterEnabled(true); 487 setCameraState(IDLE); 488 startFaceDetection(); 489 settingsFirstRun(); 490 } 491 492 /** 493 * Prompt the user to pick to record location and choose aspect ratio for the 494 * very first run of camera only. 495 */ 496 private void settingsFirstRun() { 497 final SettingsManager settingsManager = mActivity.getSettingsManager(); 498 499 if (mActivity.isSecureCamera() || isImageCaptureIntent()) { 500 return; 501 } 502 503 boolean locationPrompt = !settingsManager.isSet(SettingsManager.SCOPE_GLOBAL, 504 Keys.KEY_RECORD_LOCATION); 505 boolean aspectRatioPrompt = !settingsManager.getBoolean( 506 SettingsManager.SCOPE_GLOBAL, Keys.KEY_USER_SELECTED_ASPECT_RATIO); 507 if (!locationPrompt && !aspectRatioPrompt) { 508 return; 509 } 510 511 // Check if the back camera exists 512 int backCameraId = mAppController.getCameraProvider().getFirstBackCameraId(); 513 if (backCameraId == -1) { 514 // If there is no back camera, do not show the prompt. 515 return; 516 } 517 518 if (locationPrompt) { 519 // Show both location and aspect ratio selection dialog. 520 mUI.showLocationAndAspectRatioDialog(new LocationDialogCallback(){ 521 @Override 522 public void onLocationTaggingSelected(boolean selected) { 523 Keys.setLocation(mActivity.getSettingsManager(), selected, 524 mActivity.getLocationManager()); 525 } 526 }, createAspectRatioDialogCallback()); 527 } else { 528 // App upgrade. Only show aspect ratio selection. 529 boolean wasShown = mUI.showAspectRatioDialog(createAspectRatioDialogCallback()); 530 if (!wasShown) { 531 // If the dialog was not shown, set this flag to true so that we 532 // never have to check for it again. It means that we don't need 533 // to show the dialog on this device. 534 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL, 535 Keys.KEY_USER_SELECTED_ASPECT_RATIO, true); 536 } 537 } 538 } 539 540 private AspectRatioDialogCallback createAspectRatioDialogCallback() { 541 Size currentSize = mCameraSettings.getCurrentPhotoSize(); 542 float aspectRatio = (float) currentSize.width() / (float) currentSize.height(); 543 if (aspectRatio < 1f) { 544 aspectRatio = 1 / aspectRatio; 545 } 546 final AspectRatioSelector.AspectRatio currentAspectRatio; 547 if (Math.abs(aspectRatio - 4f / 3f) <= 0.1f) { 548 currentAspectRatio = AspectRatioSelector.AspectRatio.ASPECT_RATIO_4x3; 549 } else if (Math.abs(aspectRatio - 16f / 9f) <= 0.1f) { 550 currentAspectRatio = AspectRatioSelector.AspectRatio.ASPECT_RATIO_16x9; 551 } else { 552 // TODO: Log error and not show dialog. 553 return null; 554 } 555 556 List<Size> sizes = mCameraCapabilities.getSupportedPhotoSizes(); 557 List<Size> pictureSizes = ResolutionUtil 558 .getDisplayableSizesFromSupported(sizes, true); 559 560 // This logic below finds the largest resolution for each aspect ratio. 561 // TODO: Move this somewhere that can be shared with SettingsActivity 562 int aspectRatio4x3Resolution = 0; 563 int aspectRatio16x9Resolution = 0; 564 Size largestSize4x3 = new Size(0, 0); 565 Size largestSize16x9 = new Size(0, 0); 566 for (Size size : pictureSizes) { 567 float pictureAspectRatio = (float) size.width() / (float) size.height(); 568 pictureAspectRatio = pictureAspectRatio < 1 ? 569 1f / pictureAspectRatio : pictureAspectRatio; 570 int resolution = size.width() * size.height(); 571 if (Math.abs(pictureAspectRatio - 4f / 3f) < 0.1f) { 572 if (resolution > aspectRatio4x3Resolution) { 573 aspectRatio4x3Resolution = resolution; 574 largestSize4x3 = size; 575 } 576 } else if (Math.abs(pictureAspectRatio - 16f / 9f) < 0.1f) { 577 if (resolution > aspectRatio16x9Resolution) { 578 aspectRatio16x9Resolution = resolution; 579 largestSize16x9 = size; 580 } 581 } 582 } 583 584 // Use the largest 4x3 and 16x9 sizes as candidates for picture size selection. 585 final Size size4x3ToSelect = largestSize4x3; 586 final Size size16x9ToSelect = largestSize16x9; 587 588 AspectRatioDialogCallback callback = new AspectRatioDialogCallback() { 589 590 @Override 591 public AspectRatioSelector.AspectRatio getCurrentAspectRatio() { 592 return currentAspectRatio; 593 } 594 595 @Override 596 public void onAspectRatioSelected(AspectRatioSelector.AspectRatio newAspectRatio, 597 Runnable dialogHandlingFinishedRunnable) { 598 if (newAspectRatio == AspectRatioSelector.AspectRatio.ASPECT_RATIO_4x3) { 599 String largestSize4x3Text = SettingsUtil.sizeToSetting(size4x3ToSelect); 600 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL, 601 Keys.KEY_PICTURE_SIZE_BACK, 602 largestSize4x3Text); 603 } else if (newAspectRatio == AspectRatioSelector.AspectRatio.ASPECT_RATIO_16x9) { 604 String largestSize16x9Text = SettingsUtil.sizeToSetting(size16x9ToSelect); 605 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL, 606 Keys.KEY_PICTURE_SIZE_BACK, 607 largestSize16x9Text); 608 } 609 mActivity.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL, 610 Keys.KEY_USER_SELECTED_ASPECT_RATIO, true); 611 String aspectRatio = mActivity.getSettingsManager().getString( 612 SettingsManager.SCOPE_GLOBAL, 613 Keys.KEY_USER_SELECTED_ASPECT_RATIO); 614 Log.e(TAG, "aspect ratio after setting it to true=" + aspectRatio); 615 if (newAspectRatio != currentAspectRatio) { 616 stopPreview(); 617 startPreview(); 618 mUI.setRunnableForNextFrame(dialogHandlingFinishedRunnable); 619 } else { 620 mHandler.post(dialogHandlingFinishedRunnable); 621 } 622 } 623 }; 624 return callback; 625 } 626 627 @Override 628 public void onPreviewUIReady() { 629 startPreview(); 630 } 631 632 @Override 633 public void onPreviewUIDestroyed() { 634 if (mCameraDevice == null) { 635 return; 636 } 637 mCameraDevice.setPreviewTexture(null); 638 stopPreview(); 639 } 640 641 @Override 642 public void startPreCaptureAnimation() { 643 mAppController.startPreCaptureAnimation(); 644 } 645 646 private void onCameraOpened() { 647 openCameraCommon(); 648 initializeControlByIntent(); 649 } 650 651 private void switchCamera() { 652 if (mPaused) { 653 return; 654 } 655 cancelCountDown(); 656 657 mAppController.freezeScreenUntilPreviewReady(); 658 SettingsManager settingsManager = mActivity.getSettingsManager(); 659 660 Log.i(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId); 661 closeCamera(); 662 mCameraId = mPendingSwitchCameraId; 663 664 settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID, mCameraId); 665 requestCameraOpen(); 666 mUI.clearFaces(); 667 if (mFocusManager != null) { 668 mFocusManager.removeMessages(); 669 } 670 671 mMirror = isCameraFrontFacing(); 672 mFocusManager.setMirror(mMirror); 673 // Start switch camera animation. Post a message because 674 // onFrameAvailable from the old camera may already exist. 675 } 676 677 /** 678 * Uses the {@link CameraProvider} to open the currently-selected camera 679 * device, using {@link GservicesHelper} to choose between API-1 and API-2. 680 */ 681 private void requestCameraOpen() { 682 Log.v(TAG, "requestCameraOpen"); 683 mActivity.getCameraProvider().requestCamera(mCameraId, 684 GservicesHelper.useCamera2ApiThroughPortabilityLayer(mActivity)); 685 } 686 687 private final ButtonManager.ButtonCallback mCameraCallback = 688 new ButtonManager.ButtonCallback() { 689 @Override 690 public void onStateChanged(int state) { 691 // At the time this callback is fired, the camera id 692 // has be set to the desired camera. 693 694 if (mPaused || mAppController.getCameraProvider().waitingForCamera()) { 695 return; 696 } 697 // If switching to back camera, and HDR+ is still on, 698 // switch back to gcam, otherwise handle callback normally. 699 SettingsManager settingsManager = mActivity.getSettingsManager(); 700 if (Keys.isCameraBackFacing(settingsManager, 701 mAppController.getModuleScope())) { 702 if (Keys.requestsReturnToHdrPlus(settingsManager, 703 mAppController.getModuleScope())) { 704 switchToGcamCapture(); 705 return; 706 } 707 } 708 709 mPendingSwitchCameraId = state; 710 711 Log.d(TAG, "Start to switch camera. cameraId=" + state); 712 // We need to keep a preview frame for the animation before 713 // releasing the camera. This will trigger 714 // onPreviewTextureCopied. 715 // TODO: Need to animate the camera switch 716 switchCamera(); 717 } 718 }; 719 720 private final ButtonManager.ButtonCallback mHdrPlusCallback = 721 new ButtonManager.ButtonCallback() { 722 @Override 723 public void onStateChanged(int state) { 724 SettingsManager settingsManager = mActivity.getSettingsManager(); 725 if (GcamHelper.hasGcamAsSeparateModule()) { 726 // Set the camera setting to default backfacing. 727 settingsManager.setToDefault(mAppController.getModuleScope(), 728 Keys.KEY_CAMERA_ID); 729 switchToGcamCapture(); 730 } else { 731 if (Keys.isHdrOn(settingsManager)) { 732 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_SCENE_MODE, 733 mCameraCapabilities.getStringifier().stringify( 734 CameraCapabilities.SceneMode.HDR)); 735 } else { 736 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_SCENE_MODE, 737 mCameraCapabilities.getStringifier().stringify( 738 CameraCapabilities.SceneMode.AUTO)); 739 } 740 updateParametersSceneMode(); 741 if (mCameraDevice != null) { 742 mCameraDevice.applySettings(mCameraSettings); 743 } 744 updateSceneMode(); 745 } 746 } 747 }; 748 749 private final View.OnClickListener mCancelCallback = new View.OnClickListener() { 750 @Override 751 public void onClick(View v) { 752 onCaptureCancelled(); 753 } 754 }; 755 756 private final View.OnClickListener mDoneCallback = new View.OnClickListener() { 757 @Override 758 public void onClick(View v) { 759 onCaptureDone(); 760 } 761 }; 762 763 private final View.OnClickListener mRetakeCallback = new View.OnClickListener() { 764 @Override 765 public void onClick(View v) { 766 mActivity.getCameraAppUI().transitionToIntentCaptureLayout(); 767 onCaptureRetake(); 768 } 769 }; 770 771 @Override 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); 778 } 779 } 780 781 @Override 782 public HardwareSpec getHardwareSpec() { 783 return (mCameraSettings != null ? 784 new HardwareSpecImpl(getCameraProvider(), mCameraCapabilities) : null); 785 } 786 787 @Override 788 public CameraAppUI.BottomBarUISpec getBottomBarSpec() { 789 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec(); 790 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() { 802 @Override 803 public void setExposure(int value) { 804 setExposureCompensation(value); 805 } 806 }; 807 bottomBarSpec.minExposureCompensation = 808 mCameraCapabilities.getMinExposureCompensation(); 809 bottomBarSpec.maxExposureCompensation = 810 mCameraCapabilities.getMaxExposureCompensation(); 811 bottomBarSpec.exposureCompensationStep = 812 mCameraCapabilities.getExposureCompensationStep(); 813 } 814 815 bottomBarSpec.enableSelfTimer = true; 816 bottomBarSpec.showSelfTimer = true; 817 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; 825 } 826 827 return bottomBarSpec; 828 } 829 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); 838 } 839 updateSceneMode(); 840 } 841 842 @Override 843 public void updatePreviewAspectRatio(float aspectRatio) { 844 mAppController.updatePreviewAspectRatio(aspectRatio); 845 } 846 847 private void resetExposureCompensation() { 848 SettingsManager settingsManager = mActivity.getSettingsManager(); 849 if (settingsManager == null) { 850 Log.e(TAG, "Settings manager is null!"); 851 return; 852 } 853 settingsManager.setToDefault(mAppController.getCameraScope(), 854 Keys.KEY_EXPOSURE); 855 } 856 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) { 862 return; 863 } 864 865 mUI.initializeFirstTime(); 866 867 // We set the listener only when both service and shutterbutton 868 // are initialized. 869 getServices().getMemoryManager().addListener(this); 870 871 mNamedImages = new NamedImages(); 872 873 mFirstTimeInitialized = true; 874 addIdleHandler(); 875 876 mActivity.updateStorageSpaceAndHint(null); 877 } 878 879 // If the activity is paused and resumed, this method will be called in 880 // onResume. 881 private void initializeSecondTime() { 882 getServices().getMemoryManager().addListener(this); 883 mNamedImages = new NamedImages(); 884 mUI.initializeSecondTime(mCameraCapabilities, mCameraSettings); 885 } 886 887 private void addIdleHandler() { 888 MessageQueue queue = Looper.myQueue(); 889 queue.addIdleHandler(new MessageQueue.IdleHandler() { 890 @Override 891 public boolean queueIdle() { 892 Storage.ensureOSXCompatible(); 893 return false; 894 } 895 }); 896 } 897 898 @Override 899 public void startFaceDetection() { 900 if (mFaceDetectionStarted || mCameraDevice == null) { 901 return; 902 } 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); 909 } 910 } 911 912 @Override 913 public void stopFaceDetection() { 914 if (!mFaceDetectionStarted || mCameraDevice == null) { 915 return; 916 } 917 if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) { 918 mFaceDetectionStarted = false; 919 mCameraDevice.setFaceDetectionCallback(null, null); 920 mCameraDevice.stopFaceDetection(); 921 mUI.clearFaces(); 922 SessionStatsCollector.instance().faceScanActive(false); 923 } 924 } 925 926 private final class ShutterCallback 927 implements CameraShutterCallback { 928 929 private final boolean mNeedsAnimation; 930 931 public ShutterCallback(boolean needsAnimation) { 932 mNeedsAnimation = needsAnimation; 933 } 934 935 @Override 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() { 942 @Override 943 public void run() { 944 animateAfterShutter(); 945 } 946 }); 947 } 948 } 949 } 950 951 private final class PostViewPictureCallback 952 implements CameraPictureCallback { 953 @Override 954 public void onPictureTaken(byte[] data, CameraProxy camera) { 955 mPostViewPictureCallbackTime = System.currentTimeMillis(); 956 Log.v(TAG, "mShutterToPostViewCallbackTime = " 957 + (mPostViewPictureCallbackTime - mShutterCallbackTime) 958 + "ms"); 959 } 960 } 961 962 private final class RawPictureCallback 963 implements CameraPictureCallback { 964 @Override 965 public void onPictureTaken(byte[] rawData, CameraProxy camera) { 966 mRawPictureCallbackTime = System.currentTimeMillis(); 967 Log.v(TAG, "mShutterToRawCallbackTime = " 968 + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms"); 969 } 970 } 971 972 private static class ResizeBundle { 973 byte[] jpegData; 974 float targetAspectRatio; 975 ExifInterface exif; 976 } 977 978 /** 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. 981 */ 982 private ResizeBundle cropJpegDataToAspectRatio(ResizeBundle dataBundle) { 983 984 final byte[] jpegData = dataBundle.jpegData; 985 final ExifInterface exif = dataBundle.exif; 986 float targetAspectRatio = dataBundle.targetAspectRatio; 987 988 Bitmap original = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); 989 int originalWidth = original.getWidth(); 990 int originalHeight = original.getHeight(); 991 int newWidth; 992 int newHeight; 993 994 if (originalWidth > originalHeight) { 995 newHeight = (int) (originalWidth / targetAspectRatio); 996 newWidth = originalWidth; 997 } else { 998 newWidth = (int) (originalHeight / targetAspectRatio); 999 newHeight = originalHeight; 1000 } 1001 int xOffset = (originalWidth - newWidth)/2; 1002 int yOffset = (originalHeight - newHeight)/2; 1003 1004 if (xOffset < 0 || yOffset < 0) { 1005 return dataBundle; 1006 } 1007 1008 Bitmap resized = Bitmap.createBitmap(original,xOffset,yOffset,newWidth, newHeight); 1009 exif.setTagValue(ExifInterface.TAG_PIXEL_X_DIMENSION, new Integer(newWidth)); 1010 exif.setTagValue(ExifInterface.TAG_PIXEL_Y_DIMENSION, new Integer(newHeight)); 1011 1012 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 1013 1014 resized.compress(Bitmap.CompressFormat.JPEG, 90, stream); 1015 dataBundle.jpegData = stream.toByteArray(); 1016 return dataBundle; 1017 } 1018 1019 private final class JpegPictureCallback 1020 implements CameraPictureCallback { 1021 Location mLocation; 1022 1023 public JpegPictureCallback(Location loc) { 1024 mLocation = loc; 1025 } 1026 1027 @Override 1028 public void onPictureTaken(final byte[] originalJpegData, final CameraProxy camera) { 1029 mAppController.setShutterEnabled(true); 1030 if (mPaused) { 1031 return; 1032 } 1033 if (mIsImageCaptureIntent) { 1034 stopPreview(); 1035 } 1036 if (mSceneMode == CameraCapabilities.SceneMode.HDR) { 1037 mUI.setSwipingEnabled(true); 1038 } 1039 1040 mJpegPictureCallbackTime = System.currentTimeMillis(); 1041 // If postview callback has arrived, the captured image is displayed 1042 // in postview callback. If not, the captured image is displayed in 1043 // raw picture callback. 1044 if (mPostViewPictureCallbackTime != 0) { 1045 mShutterToPictureDisplayedTime = 1046 mPostViewPictureCallbackTime - mShutterCallbackTime; 1047 mPictureDisplayedToJpegCallbackTime = 1048 mJpegPictureCallbackTime - mPostViewPictureCallbackTime; 1049 } else { 1050 mShutterToPictureDisplayedTime = 1051 mRawPictureCallbackTime - mShutterCallbackTime; 1052 mPictureDisplayedToJpegCallbackTime = 1053 mJpegPictureCallbackTime - mRawPictureCallbackTime; 1054 } 1055 Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = " 1056 + mPictureDisplayedToJpegCallbackTime + "ms"); 1057 1058 mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden. 1059 if (!mIsImageCaptureIntent) { 1060 setupPreview(); 1061 } 1062 1063 long now = System.currentTimeMillis(); 1064 mJpegCallbackFinishTime = now - mJpegPictureCallbackTime; 1065 Log.v(TAG, "mJpegCallbackFinishTime = " + mJpegCallbackFinishTime + "ms"); 1066 mJpegPictureCallbackTime = 0; 1067 1068 final ExifInterface exif = Exif.getExif(originalJpegData); 1069 1070 if (mShouldResizeTo16x9) { 1071 final ResizeBundle dataBundle = new ResizeBundle(); 1072 dataBundle.jpegData = originalJpegData; 1073 dataBundle.targetAspectRatio = ResolutionUtil.NEXUS_5_LARGE_16_BY_9_ASPECT_RATIO; 1074 dataBundle.exif = exif; 1075 new AsyncTask<ResizeBundle, Void, ResizeBundle>() { 1076 1077 @Override 1078 protected ResizeBundle doInBackground(ResizeBundle... resizeBundles) { 1079 return cropJpegDataToAspectRatio(resizeBundles[0]); 1080 } 1081 1082 @Override 1083 protected void onPostExecute(ResizeBundle result) { 1084 saveFinalPhoto(result.jpegData, result.exif, camera); 1085 } 1086 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dataBundle); 1087 1088 } else { 1089 saveFinalPhoto(originalJpegData, exif, camera); 1090 } 1091 } 1092 1093 void saveFinalPhoto(final byte[] jpegData, final ExifInterface exif, CameraProxy camera) { 1094 1095 int orientation = Exif.getOrientation(exif); 1096 1097 float zoomValue = 1.0f; 1098 if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) { 1099 zoomValue = mCameraSettings.getCurrentZoomRatio(); 1100 } 1101 boolean hdrOn = CameraCapabilities.SceneMode.HDR == mSceneMode; 1102 String flashSetting = 1103 mActivity.getSettingsManager().getString(mAppController.getCameraScope(), 1104 Keys.KEY_FLASH_MODE); 1105 boolean gridLinesOn = Keys.areGridLinesOn(mActivity.getSettingsManager()); 1106 UsageStatistics.instance().photoCaptureDoneEvent( 1107 eventprotos.NavigationChange.Mode.PHOTO_CAPTURE, 1108 mNamedImages.mQueue.lastElement().title + ".jpg", exif, 1109 isCameraFrontFacing(), hdrOn, zoomValue, flashSetting, gridLinesOn, 1110 (float) mTimerDuration, mShutterTouchCoordinate, mVolumeButtonClickedFlag); 1111 mShutterTouchCoordinate = null; 1112 mVolumeButtonClickedFlag = false; 1113 1114 if (!mIsImageCaptureIntent) { 1115 // Calculate the width and the height of the jpeg. 1116 Integer exifWidth = exif.getTagIntValue(ExifInterface.TAG_PIXEL_X_DIMENSION); 1117 Integer exifHeight = exif.getTagIntValue(ExifInterface.TAG_PIXEL_Y_DIMENSION); 1118 int width, height; 1119 if (mShouldResizeTo16x9 && exifWidth != null && exifHeight != null) { 1120 width = exifWidth; 1121 height = exifHeight; 1122 } else { 1123 Size s; 1124 s = mCameraSettings.getCurrentPhotoSize(); 1125 if ((mJpegRotation + orientation) % 180 == 0) { 1126 width = s.width(); 1127 height = s.height(); 1128 } else { 1129 width = s.height(); 1130 height = s.width(); 1131 } 1132 } 1133 NamedEntity name = mNamedImages.getNextNameEntity(); 1134 String title = (name == null) ? null : name.title; 1135 long date = (name == null) ? -1 : name.date; 1136 1137 // Handle debug mode outputs 1138 if (mDebugUri != null) { 1139 // If using a debug uri, save jpeg there. 1140 saveToDebugUri(jpegData); 1141 1142 // Adjust the title of the debug image shown in mediastore. 1143 if (title != null) { 1144 title = DEBUG_IMAGE_PREFIX + title; 1145 } 1146 } 1147 1148 if (title == null) { 1149 Log.e(TAG, "Unbalanced name/data pair"); 1150 } else { 1151 if (date == -1) { 1152 date = mCaptureStartTime; 1153 } 1154 if (mHeading >= 0) { 1155 // heading direction has been updated by the sensor. 1156 ExifTag directionRefTag = exif.buildTag( 1157 ExifInterface.TAG_GPS_IMG_DIRECTION_REF, 1158 ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION); 1159 ExifTag directionTag = exif.buildTag( 1160 ExifInterface.TAG_GPS_IMG_DIRECTION, 1161 new Rational(mHeading, 1)); 1162 exif.setTag(directionRefTag); 1163 exif.setTag(directionTag); 1164 } 1165 getServices().getMediaSaver().addImage( 1166 jpegData, title, date, mLocation, width, height, 1167 orientation, exif, mOnMediaSavedListener, mContentResolver); 1168 } 1169 // Animate capture with real jpeg data instead of a preview 1170 // frame. 1171 mUI.animateCapture(jpegData, orientation, mMirror); 1172 } else { 1173 mJpegImageData = jpegData; 1174 if (!mQuickCapture) { 1175 mUI.showCapturedImageForReview(jpegData, orientation, mMirror); 1176 } else { 1177 onCaptureDone(); 1178 } 1179 } 1180 1181 // Send the taken photo to remote shutter listeners, if any are 1182 // registered. 1183 getServices().getRemoteShutterListener().onPictureTaken(jpegData); 1184 1185 // Check this in advance of each shot so we don't add to shutter 1186 // latency. It's true that someone else could write to the SD card 1187 // in the mean time and fill it, but that could have happened 1188 // between the shutter press and saving the JPEG too. 1189 mActivity.updateStorageSpaceAndHint(null); 1190 } 1191 } 1192 1193 private final class AutoFocusCallback implements CameraAFCallback { 1194 @Override 1195 public void onAutoFocus(boolean focused, CameraProxy camera) { 1196 SessionStatsCollector.instance().autofocusResult(focused); 1197 if (mPaused) { 1198 return; 1199 } 1200 1201 mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime; 1202 Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms focused = "+focused); 1203 setCameraState(IDLE); 1204 mFocusManager.onAutoFocus(focused, false); 1205 } 1206 } 1207 1208 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 1209 private final class AutoFocusMoveCallback 1210 implements CameraAFMoveCallback { 1211 @Override 1212 public void onAutoFocusMoving( 1213 boolean moving, CameraProxy camera) { 1214 mFocusManager.onAutoFocusMoving(moving); 1215 SessionStatsCollector.instance().autofocusMoving(moving); 1216 } 1217 } 1218 1219 /** 1220 * This class is just a thread-safe queue for name,date holder objects. 1221 */ 1222 public static class NamedImages { 1223 private final Vector<NamedEntity> mQueue; 1224 1225 public NamedImages() { 1226 mQueue = new Vector<NamedEntity>(); 1227 } 1228 1229 public void nameNewImage(long date) { 1230 NamedEntity r = new NamedEntity(); 1231 r.title = CameraUtil.createJpegName(date); 1232 r.date = date; 1233 mQueue.add(r); 1234 } 1235 1236 public NamedEntity getNextNameEntity() { 1237 synchronized (mQueue) { 1238 if (!mQueue.isEmpty()) { 1239 return mQueue.remove(0); 1240 } 1241 } 1242 return null; 1243 } 1244 1245 public static class NamedEntity { 1246 public String title; 1247 public long date; 1248 } 1249 } 1250 1251 private void setCameraState(int state) { 1252 mCameraState = state; 1253 switch (state) { 1254 case PREVIEW_STOPPED: 1255 case SNAPSHOT_IN_PROGRESS: 1256 case SWITCHING_CAMERA: 1257 // TODO: Tell app UI to disable swipe 1258 break; 1259 case PhotoController.IDLE: 1260 // TODO: Tell app UI to enable swipe 1261 break; 1262 } 1263 } 1264 1265 private void animateAfterShutter() { 1266 // Only animate when in full screen capture mode 1267 // i.e. If monkey/a user swipes to the gallery during picture taking, 1268 // don't show animation 1269 if (!mIsImageCaptureIntent) { 1270 mUI.animateFlash(); 1271 } 1272 } 1273 1274 @Override 1275 public boolean capture() { 1276 // If we are already in the middle of taking a snapshot or the image 1277 // save request is full then ignore. 1278 if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS 1279 || mCameraState == SWITCHING_CAMERA || !mAppController.isShutterEnabled()) { 1280 return false; 1281 } 1282 mCaptureStartTime = System.currentTimeMillis(); 1283 1284 mPostViewPictureCallbackTime = 0; 1285 mJpegImageData = null; 1286 1287 final boolean animateBefore = (mSceneMode == CameraCapabilities.SceneMode.HDR); 1288 1289 if (animateBefore) { 1290 animateAfterShutter(); 1291 } 1292 1293 Location loc = mActivity.getLocationManager().getCurrentLocation(); 1294 CameraUtil.setGpsParameters(mCameraSettings, loc); 1295 mCameraDevice.applySettings(mCameraSettings); 1296 1297 // Set JPEG orientation. Even if screen UI is locked in portrait, camera orientation should 1298 // still match device orientation (e.g., users should always get landscape photos while 1299 // capturing by putting device in landscape.) 1300 int orientation = mActivity.isAutoRotateScreen() ? mDisplayRotation : mOrientation; 1301 Characteristics info = mActivity.getCameraProvider().getCharacteristics(mCameraId); 1302 mJpegRotation = info.getJpegOrientation(orientation); 1303 mCameraDevice.setJpegOrientation(mJpegRotation); 1304 1305 // We don't want user to press the button again while taking a 1306 // multi-second HDR photo. 1307 mAppController.setShutterEnabled(false); 1308 mCameraDevice.takePicture(mHandler, 1309 new ShutterCallback(!animateBefore), 1310 mRawPictureCallback, mPostViewPictureCallback, 1311 new JpegPictureCallback(loc)); 1312 1313 mNamedImages.nameNewImage(mCaptureStartTime); 1314 1315 mFaceDetectionStarted = false; 1316 setCameraState(SNAPSHOT_IN_PROGRESS); 1317 return true; 1318 } 1319 1320 @Override 1321 public void setFocusParameters() { 1322 setCameraParameters(UPDATE_PARAM_PREFERENCE); 1323 } 1324 1325 private void updateSceneMode() { 1326 // If scene mode is set, we cannot set flash mode, white balance, and 1327 // focus mode, instead, we read it from driver 1328 if (CameraCapabilities.SceneMode.AUTO != mSceneMode) { 1329 overrideCameraSettings(mCameraSettings.getCurrentFlashMode(), 1330 mCameraSettings.getCurrentFocusMode()); 1331 } 1332 } 1333 1334 private void overrideCameraSettings(CameraCapabilities.FlashMode flashMode, 1335 CameraCapabilities.FocusMode focusMode) { 1336 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier(); 1337 SettingsManager settingsManager = mActivity.getSettingsManager(); 1338 if (!CameraCapabilities.FlashMode.NO_FLASH.equals(flashMode)) { 1339 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FLASH_MODE, 1340 stringifier.stringify(flashMode)); 1341 } 1342 settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FOCUS_MODE, 1343 stringifier.stringify(focusMode)); 1344 } 1345 1346 @Override 1347 public void onOrientationChanged(int orientation) { 1348 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) { 1349 return; 1350 } 1351 1352 // TODO: Document orientation compute logic and unify them in OrientationManagerImpl. 1353 // b/17443789 1354 // Flip to counter-clockwise orientation. 1355 mOrientation = (360 - orientation) % 360; 1356 } 1357 1358 @Override 1359 public void onCameraAvailable(CameraProxy cameraProxy) { 1360 Log.v(TAG, "onCameraAvailable"); 1361 if (mPaused) { 1362 return; 1363 } 1364 mCameraDevice = cameraProxy; 1365 1366 initializeCapabilities(); 1367 1368 // Reset zoom value index. 1369 mZoomValue = 1.0f; 1370 if (mFocusManager == null) { 1371 initializeFocusManager(); 1372 } 1373 mFocusManager.updateCapabilities(mCameraCapabilities); 1374 1375 // Do camera parameter dependent initialization. 1376 mCameraSettings = mCameraDevice.getSettings(); 1377 // HACK: The call to setCameraParameters(UPDATE_PARAM_ALL) may 1378 // eventually recurse back into startPreview(). 1379 // To avoid calling startPreview() twice, first acquire 1380 // mStartPreviewLock. 1381 mStartPreviewLock = true; 1382 try { 1383 setCameraParameters(UPDATE_PARAM_ALL); 1384 // Set a listener which updates camera parameters based 1385 // on changed settings. 1386 SettingsManager settingsManager = mActivity.getSettingsManager(); 1387 settingsManager.addListener(this); 1388 mCameraPreviewParamsReady = true; 1389 } finally { 1390 mStartPreviewLock = false; 1391 } 1392 1393 startPreview(); 1394 1395 onCameraOpened(); 1396 } 1397 1398 @Override 1399 public void onCaptureCancelled() { 1400 mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent()); 1401 mActivity.finish(); 1402 } 1403 1404 @Override 1405 public void onCaptureRetake() { 1406 if (mPaused) { 1407 return; 1408 } 1409 mUI.hidePostCaptureAlert(); 1410 mUI.hideIntentReviewImageView(); 1411 setupPreview(); 1412 } 1413 1414 @Override 1415 public void onCaptureDone() { 1416 if (mPaused) { 1417 return; 1418 } 1419 1420 byte[] data = mJpegImageData; 1421 1422 if (mCropValue == null) { 1423 // First handle the no crop case -- just return the value. If the 1424 // caller specifies a "save uri" then write the data to its 1425 // stream. Otherwise, pass back a scaled down version of the bitmap 1426 // directly in the extras. 1427 if (mSaveUri != null) { 1428 OutputStream outputStream = null; 1429 try { 1430 outputStream = mContentResolver.openOutputStream(mSaveUri); 1431 outputStream.write(data); 1432 outputStream.close(); 1433 1434 Log.v(TAG, "saved result to URI: " + mSaveUri); 1435 mActivity.setResultEx(Activity.RESULT_OK); 1436 mActivity.finish(); 1437 } catch (IOException ex) { 1438 Log.w(TAG, "exception saving result to URI: " + mSaveUri, ex); 1439 // ignore exception 1440 } finally { 1441 CameraUtil.closeSilently(outputStream); 1442 } 1443 } else { 1444 ExifInterface exif = Exif.getExif(data); 1445 int orientation = Exif.getOrientation(exif); 1446 Bitmap bitmap = CameraUtil.makeBitmap(data, 50 * 1024); 1447 bitmap = CameraUtil.rotate(bitmap, orientation); 1448 Log.v(TAG, "inlined bitmap into capture intent result"); 1449 mActivity.setResultEx(Activity.RESULT_OK, 1450 new Intent("inline-data").putExtra("data", bitmap)); 1451 mActivity.finish(); 1452 } 1453 } else { 1454 // Save the image to a temp file and invoke the cropper 1455 Uri tempUri = null; 1456 FileOutputStream tempStream = null; 1457 try { 1458 File path = mActivity.getFileStreamPath(sTempCropFilename); 1459 path.delete(); 1460 tempStream = mActivity.openFileOutput(sTempCropFilename, 0); 1461 tempStream.write(data); 1462 tempStream.close(); 1463 tempUri = Uri.fromFile(path); 1464 Log.v(TAG, "wrote temp file for cropping to: " + sTempCropFilename); 1465 } catch (FileNotFoundException ex) { 1466 Log.w(TAG, "error writing temp cropping file to: " + sTempCropFilename, ex); 1467 mActivity.setResultEx(Activity.RESULT_CANCELED); 1468 mActivity.finish(); 1469 return; 1470 } catch (IOException ex) { 1471 Log.w(TAG, "error writing temp cropping file to: " + sTempCropFilename, ex); 1472 mActivity.setResultEx(Activity.RESULT_CANCELED); 1473 mActivity.finish(); 1474 return; 1475 } finally { 1476 CameraUtil.closeSilently(tempStream); 1477 } 1478 1479 Bundle newExtras = new Bundle(); 1480 if (mCropValue.equals("circle")) { 1481 newExtras.putString("circleCrop", "true"); 1482 } 1483 if (mSaveUri != null) { 1484 Log.v(TAG, "setting output of cropped file to: " + mSaveUri); 1485 newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri); 1486 } else { 1487 newExtras.putBoolean(CameraUtil.KEY_RETURN_DATA, true); 1488 } 1489 if (mActivity.isSecureCamera()) { 1490 newExtras.putBoolean(CameraUtil.KEY_SHOW_WHEN_LOCKED, true); 1491 } 1492 1493 // TODO: Share this constant. 1494 final String CROP_ACTION = "com.android.camera.action.CROP"; 1495 Intent cropIntent = new Intent(CROP_ACTION); 1496 1497 cropIntent.setData(tempUri); 1498 cropIntent.putExtras(newExtras); 1499 Log.v(TAG, "starting CROP intent for capture"); 1500 mActivity.startActivityForResult(cropIntent, REQUEST_CROP); 1501 } 1502 } 1503 1504 @Override 1505 public void onShutterCoordinate(TouchCoordinate coord) { 1506 mShutterTouchCoordinate = coord; 1507 } 1508 1509 @Override 1510 public void onShutterButtonFocus(boolean pressed) { 1511 // Do nothing. We don't support half-press to focus anymore. 1512 } 1513 1514 @Override 1515 public void onShutterButtonClick() { 1516 if (mPaused || (mCameraState == SWITCHING_CAMERA) 1517 || (mCameraState == PREVIEW_STOPPED)) { 1518 mVolumeButtonClickedFlag = false; 1519 return; 1520 } 1521 1522 // Do not take the picture if there is not enough storage. 1523 if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) { 1524 Log.i(TAG, "Not enough space or storage not ready. remaining=" 1525 + mActivity.getStorageSpaceBytes()); 1526 mVolumeButtonClickedFlag = false; 1527 return; 1528 } 1529 Log.d(TAG, "onShutterButtonClick: mCameraState=" + mCameraState + 1530 " mVolumeButtonClickedFlag=" + mVolumeButtonClickedFlag); 1531 1532 int countDownDuration = mActivity.getSettingsManager() 1533 .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION); 1534 mTimerDuration = countDownDuration; 1535 if (countDownDuration > 0) { 1536 // Start count down. 1537 mAppController.getCameraAppUI().transitionToCancel(); 1538 mAppController.getCameraAppUI().hideModeOptions(); 1539 mUI.startCountdown(countDownDuration); 1540 return; 1541 } else { 1542 focusAndCapture(); 1543 } 1544 } 1545 1546 private void focusAndCapture() { 1547 if (mSceneMode == CameraCapabilities.SceneMode.HDR) { 1548 mUI.setSwipingEnabled(false); 1549 } 1550 // If the user wants to do a snapshot while the previous one is still 1551 // in progress, remember the fact and do it after we finish the previous 1552 // one and re-start the preview. Snapshot in progress also includes the 1553 // state that autofocus is focusing and a picture will be taken when 1554 // focus callback arrives. 1555 if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)) { 1556 if (!mIsImageCaptureIntent) { 1557 mSnapshotOnIdle = true; 1558 } 1559 return; 1560 } 1561 1562 mSnapshotOnIdle = false; 1563 mFocusManager.focusAndCapture(mCameraSettings.getCurrentFocusMode()); 1564 } 1565 1566 @Override 1567 public void onRemainingSecondsChanged(int remainingSeconds) { 1568 if (remainingSeconds == 1) { 1569 mCountdownSoundPlayer.play(R.raw.timer_final_second, 0.6f); 1570 } else if (remainingSeconds == 2 || remainingSeconds == 3) { 1571 mCountdownSoundPlayer.play(R.raw.timer_increment, 0.6f); 1572 } 1573 } 1574 1575 @Override 1576 public void onCountDownFinished() { 1577 if (mIsImageCaptureIntent) { 1578 mAppController.getCameraAppUI().transitionToIntentReviewLayout(); 1579 } else { 1580 mAppController.getCameraAppUI().transitionToCapture(); 1581 } 1582 mAppController.getCameraAppUI().showModeOptions(); 1583 if (mPaused) { 1584 return; 1585 } 1586 focusAndCapture(); 1587 } 1588 1589 @Override 1590 public void resume() { 1591 mPaused = false; 1592 1593 mCountdownSoundPlayer.loadSound(R.raw.timer_final_second); 1594 mCountdownSoundPlayer.loadSound(R.raw.timer_increment); 1595 if (mFocusManager != null) { 1596 // If camera is not open when resume is called, focus manager will 1597 // not be initialized yet, in which case it will start listening to 1598 // preview area size change later in the initialization. 1599 mAppController.addPreviewAreaSizeChangedListener(mFocusManager); 1600 } 1601 mAppController.addPreviewAreaSizeChangedListener(mUI); 1602 1603 CameraProvider camProvider = mActivity.getCameraProvider(); 1604 if (camProvider == null) { 1605 // No camera provider, the Activity is destroyed already. 1606 return; 1607 } 1608 requestCameraOpen(); 1609 1610 mJpegPictureCallbackTime = 0; 1611 mZoomValue = 1.0f; 1612 1613 mOnResumeTime = SystemClock.uptimeMillis(); 1614 checkDisplayRotation(); 1615 1616 // If first time initialization is not finished, put it in the 1617 // message queue. 1618 if (!mFirstTimeInitialized) { 1619 mHandler.sendEmptyMessage(MSG_FIRST_TIME_INIT); 1620 } else { 1621 initializeSecondTime(); 1622 } 1623 1624 Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 1625 if (gsensor != null) { 1626 mSensorManager.registerListener(this, gsensor, SensorManager.SENSOR_DELAY_NORMAL); 1627 } 1628 1629 Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); 1630 if (msensor != null) { 1631 mSensorManager.registerListener(this, msensor, SensorManager.SENSOR_DELAY_NORMAL); 1632 } 1633 1634 getServices().getRemoteShutterListener().onModuleReady(this); 1635 SessionStatsCollector.instance().sessionActive(true); 1636 } 1637 1638 /** 1639 * @return Whether the currently active camera is front-facing. 1640 */ 1641 private boolean isCameraFrontFacing() { 1642 return mAppController.getCameraProvider().getCharacteristics(mCameraId) 1643 .isFacingFront(); 1644 } 1645 1646 /** 1647 * The focus manager is the first UI related element to get initialized, and 1648 * it requires the RenderOverlay, so initialize it here 1649 */ 1650 private void initializeFocusManager() { 1651 // Create FocusManager object. startPreview needs it. 1652 // if mFocusManager not null, reuse it 1653 // otherwise create a new instance 1654 if (mFocusManager != null) { 1655 mFocusManager.removeMessages(); 1656 } else { 1657 mMirror = isCameraFrontFacing(); 1658 String[] defaultFocusModesStrings = mActivity.getResources().getStringArray( 1659 R.array.pref_camera_focusmode_default_array); 1660 ArrayList<CameraCapabilities.FocusMode> defaultFocusModes = 1661 new ArrayList<CameraCapabilities.FocusMode>(); 1662 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier(); 1663 for (String modeString : defaultFocusModesStrings) { 1664 CameraCapabilities.FocusMode mode = stringifier.focusModeFromString(modeString); 1665 if (mode != null) { 1666 defaultFocusModes.add(mode); 1667 } 1668 } 1669 mFocusManager = 1670 new FocusOverlayManager(mAppController, defaultFocusModes, 1671 mCameraCapabilities, this, mMirror, mActivity.getMainLooper(), 1672 mUI.getFocusUI()); 1673 MotionManager motionManager = getServices().getMotionManager(); 1674 if (motionManager != null) { 1675 motionManager.addListener(mFocusManager); 1676 } 1677 } 1678 mAppController.addPreviewAreaSizeChangedListener(mFocusManager); 1679 } 1680 1681 /** 1682 * @return Whether we are resuming from within the lockscreen. 1683 */ 1684 private boolean isResumeFromLockscreen() { 1685 String action = mActivity.getIntent().getAction(); 1686 return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action) 1687 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)); 1688 } 1689 1690 @Override 1691 public void pause() { 1692 mPaused = true; 1693 getServices().getRemoteShutterListener().onModuleExit(); 1694 SessionStatsCollector.instance().sessionActive(false); 1695 1696 Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 1697 if (gsensor != null) { 1698 mSensorManager.unregisterListener(this, gsensor); 1699 } 1700 1701 Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); 1702 if (msensor != null) { 1703 mSensorManager.unregisterListener(this, msensor); 1704 } 1705 1706 // Reset the focus first. Camera CTS does not guarantee that 1707 // cancelAutoFocus is allowed after preview stops. 1708 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) { 1709 mCameraDevice.cancelAutoFocus(); 1710 } 1711 1712 // If the camera has not been opened asynchronously yet, 1713 // and startPreview hasn't been called, then this is a no-op. 1714 // (e.g. onResume -> onPause -> onResume). 1715 stopPreview(); 1716 cancelCountDown(); 1717 mCountdownSoundPlayer.release(); 1718 1719 mNamedImages = null; 1720 // If we are in an image capture intent and has taken 1721 // a picture, we just clear it in onPause. 1722 mJpegImageData = null; 1723 1724 // Remove the messages and runnables in the queue. 1725 mHandler.removeCallbacksAndMessages(null); 1726 1727 closeCamera(); 1728 mActivity.enableKeepScreenOn(false); 1729 mUI.onPause(); 1730 1731 mPendingSwitchCameraId = -1; 1732 if (mFocusManager != null) { 1733 mFocusManager.removeMessages(); 1734 } 1735 getServices().getMemoryManager().removeListener(this); 1736 mAppController.removePreviewAreaSizeChangedListener(mFocusManager); 1737 mAppController.removePreviewAreaSizeChangedListener(mUI); 1738 1739 SettingsManager settingsManager = mActivity.getSettingsManager(); 1740 settingsManager.removeListener(this); 1741 } 1742 1743 @Override 1744 public void destroy() { 1745 // TODO: implement this. 1746 } 1747 1748 @Override 1749 public void onLayoutOrientationChanged(boolean isLandscape) { 1750 setDisplayOrientation(); 1751 } 1752 1753 @Override 1754 public void updateCameraOrientation() { 1755 if (mDisplayRotation != CameraUtil.getDisplayRotation(mActivity)) { 1756 setDisplayOrientation(); 1757 } 1758 } 1759 1760 private boolean canTakePicture() { 1761 return isCameraIdle() 1762 && (mActivity.getStorageSpaceBytes() > Storage.LOW_STORAGE_THRESHOLD_BYTES); 1763 } 1764 1765 @Override 1766 public void autoFocus() { 1767 if (mCameraDevice == null) { 1768 return; 1769 } 1770 Log.v(TAG,"Starting auto focus"); 1771 mFocusStartTime = System.currentTimeMillis(); 1772 mCameraDevice.autoFocus(mHandler, mAutoFocusCallback); 1773 SessionStatsCollector.instance().autofocusManualTrigger(); 1774 setCameraState(FOCUSING); 1775 } 1776 1777 @Override 1778 public void cancelAutoFocus() { 1779 if (mCameraDevice == null) { 1780 return; 1781 } 1782 mCameraDevice.cancelAutoFocus(); 1783 setCameraState(IDLE); 1784 setCameraParameters(UPDATE_PARAM_PREFERENCE); 1785 } 1786 1787 @Override 1788 public void onSingleTapUp(View view, int x, int y) { 1789 if (mPaused || mCameraDevice == null || !mFirstTimeInitialized 1790 || mCameraState == SNAPSHOT_IN_PROGRESS 1791 || mCameraState == SWITCHING_CAMERA 1792 || mCameraState == PREVIEW_STOPPED) { 1793 return; 1794 } 1795 1796 // Check if metering area or focus area is supported. 1797 if (!mFocusAreaSupported && !mMeteringAreaSupported) { 1798 return; 1799 } 1800 mFocusManager.onSingleTapUp(x, y); 1801 } 1802 1803 @Override 1804 public boolean onBackPressed() { 1805 return mUI.onBackPressed(); 1806 } 1807 1808 @Override 1809 public boolean onKeyDown(int keyCode, KeyEvent event) { 1810 switch (keyCode) { 1811 case KeyEvent.KEYCODE_VOLUME_UP: 1812 case KeyEvent.KEYCODE_VOLUME_DOWN: 1813 case KeyEvent.KEYCODE_FOCUS: 1814 if (/* TODO: mActivity.isInCameraApp() && */mFirstTimeInitialized && 1815 !mActivity.getCameraAppUI().isInIntentReview()) { 1816 if (event.getRepeatCount() == 0) { 1817 onShutterButtonFocus(true); 1818 } 1819 return true; 1820 } 1821 return false; 1822 case KeyEvent.KEYCODE_CAMERA: 1823 if (mFirstTimeInitialized && event.getRepeatCount() == 0) { 1824 onShutterButtonClick(); 1825 } 1826 return true; 1827 case KeyEvent.KEYCODE_DPAD_CENTER: 1828 // If we get a dpad center event without any focused view, move 1829 // the focus to the shutter button and press it. 1830 if (mFirstTimeInitialized && event.getRepeatCount() == 0) { 1831 // Start auto-focus immediately to reduce shutter lag. After 1832 // the shutter button gets the focus, onShutterButtonFocus() 1833 // will be called again but it is fine. 1834 onShutterButtonFocus(true); 1835 } 1836 return true; 1837 } 1838 return false; 1839 } 1840 1841 @Override 1842 public boolean onKeyUp(int keyCode, KeyEvent event) { 1843 switch (keyCode) { 1844 case KeyEvent.KEYCODE_VOLUME_UP: 1845 case KeyEvent.KEYCODE_VOLUME_DOWN: 1846 if (/* mActivity.isInCameraApp() && */mFirstTimeInitialized && 1847 !mActivity.getCameraAppUI().isInIntentReview()) { 1848 if (mUI.isCountingDown()) { 1849 cancelCountDown(); 1850 } else { 1851 mVolumeButtonClickedFlag = true; 1852 onShutterButtonClick(); 1853 } 1854 return true; 1855 } 1856 return false; 1857 case KeyEvent.KEYCODE_FOCUS: 1858 if (mFirstTimeInitialized) { 1859 onShutterButtonFocus(false); 1860 } 1861 return true; 1862 } 1863 return false; 1864 } 1865 1866 private void closeCamera() { 1867 if (mCameraDevice != null) { 1868 stopFaceDetection(); 1869 mCameraDevice.setZoomChangeListener(null); 1870 mCameraDevice.setFaceDetectionCallback(null, null); 1871 mCameraDevice.setErrorCallback(null, null); 1872 1873 mFaceDetectionStarted = false; 1874 mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId()); 1875 mCameraDevice = null; 1876 setCameraState(PREVIEW_STOPPED); 1877 mFocusManager.onCameraReleased(); 1878 } 1879 } 1880 1881 private void setDisplayOrientation() { 1882 mDisplayRotation = CameraUtil.getDisplayRotation(mActivity); 1883 Characteristics info = 1884 mActivity.getCameraProvider().getCharacteristics(mCameraId); 1885 mDisplayOrientation = info.getPreviewOrientation(mDisplayRotation); 1886 mCameraDisplayOrientation = mDisplayOrientation; 1887 mUI.setDisplayOrientation(mDisplayOrientation); 1888 if (mFocusManager != null) { 1889 mFocusManager.setDisplayOrientation(mDisplayOrientation); 1890 } 1891 // Change the camera display orientation 1892 if (mCameraDevice != null) { 1893 mCameraDevice.setDisplayOrientation(mDisplayRotation); 1894 } 1895 } 1896 1897 /** Only called by UI thread. */ 1898 private void setupPreview() { 1899 mFocusManager.resetTouchFocus(); 1900 startPreview(); 1901 } 1902 1903 /** 1904 * Returns whether we can/should start the preview or not. 1905 */ 1906 private boolean checkPreviewPreconditions() { 1907 if (mPaused) { 1908 return false; 1909 } 1910 1911 if (mCameraDevice == null) { 1912 Log.w(TAG, "startPreview: camera device not ready yet."); 1913 return false; 1914 } 1915 1916 SurfaceTexture st = mActivity.getCameraAppUI().getSurfaceTexture(); 1917 if (st == null) { 1918 Log.w(TAG, "startPreview: surfaceTexture is not ready."); 1919 return false; 1920 } 1921 1922 if (!mCameraPreviewParamsReady) { 1923 Log.w(TAG, "startPreview: parameters for preview is not ready."); 1924 return false; 1925 } 1926 return true; 1927 } 1928 1929 /** 1930 * The start/stop preview should only run on the UI thread. 1931 */ 1932 private void startPreview() { 1933 // HACK: The call to setCameraParameters(UPDATE_PARAM_ALL) may 1934 // eventually recurse back into startPreview(). 1935 // To avoid calling startPreview() twice, we must acquire 1936 // mStartPreviewLock. 1937 if (mStartPreviewLock || mCameraDevice == null) { 1938 // do nothing 1939 return; 1940 } 1941 mStartPreviewLock = true; 1942 try { 1943 if (!checkPreviewPreconditions()) { 1944 return; 1945 } 1946 1947 mCameraDevice.setErrorCallback(mHandler, mErrorCallback); 1948 setDisplayOrientation(); 1949 1950 if (!mSnapshotOnIdle) { 1951 // If the focus mode is continuous autofocus, call cancelAutoFocus 1952 // to resume it because it may have been paused by autoFocus call. 1953 if (mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) == 1954 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) { 1955 mCameraDevice.cancelAutoFocus(); 1956 } 1957 mFocusManager.setAeAwbLock(false); // Unlock AE and AWB. 1958 } 1959 setCameraParameters(UPDATE_PARAM_ALL); 1960 mCameraDevice.setPreviewTexture(mActivity.getCameraAppUI().getSurfaceTexture()); 1961 1962 Log.i(TAG, "startPreview"); 1963 // If we're using API2 in portability layers, don't use startPreviewWithCallback() 1964 // b/17576554 1965 CameraAgent.CameraStartPreviewCallback startPreviewCallback = 1966 new CameraAgent.CameraStartPreviewCallback() { 1967 @Override 1968 public void onPreviewStarted() { 1969 mFocusManager.onPreviewStarted(); 1970 PhotoModule.this.onPreviewStarted(); 1971 SessionStatsCollector.instance().previewActive(true); 1972 if (mSnapshotOnIdle) { 1973 mHandler.post(mDoSnapRunnable); 1974 } 1975 } 1976 }; 1977 if (GservicesHelper.useCamera2ApiThroughPortabilityLayer(mActivity)) { 1978 mCameraDevice.startPreview(); 1979 startPreviewCallback.onPreviewStarted(); 1980 } else { 1981 mCameraDevice.startPreviewWithCallback(new Handler(Looper.getMainLooper()), 1982 startPreviewCallback); 1983 } 1984 } finally { 1985 mStartPreviewLock = false; 1986 } 1987 } 1988 1989 @Override 1990 public void stopPreview() { 1991 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) { 1992 Log.i(TAG, "stopPreview"); 1993 mCameraDevice.stopPreview(); 1994 mFaceDetectionStarted = false; 1995 } 1996 setCameraState(PREVIEW_STOPPED); 1997 if (mFocusManager != null) { 1998 mFocusManager.onPreviewStopped(); 1999 } 2000 SessionStatsCollector.instance().previewActive(false); 2001 } 2002 2003 @Override 2004 public void onSettingChanged(SettingsManager settingsManager, String key) { 2005 if (key.equals(Keys.KEY_FLASH_MODE)) { 2006 updateParametersFlashMode(); 2007 } 2008 if (key.equals(Keys.KEY_CAMERA_HDR)) { 2009 if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 2010 Keys.KEY_CAMERA_HDR)) { 2011 // HDR is on. 2012 mAppController.getButtonManager().disableButton(ButtonManager.BUTTON_FLASH); 2013 mFlashModeBeforeSceneMode = settingsManager.getString( 2014 mAppController.getCameraScope(), Keys.KEY_FLASH_MODE); 2015 } else { 2016 if (mFlashModeBeforeSceneMode != null) { 2017 settingsManager.set(mAppController.getCameraScope(), 2018 Keys.KEY_FLASH_MODE, 2019 mFlashModeBeforeSceneMode); 2020 updateParametersFlashMode(); 2021 mFlashModeBeforeSceneMode = null; 2022 } 2023 mAppController.getButtonManager().enableButton(ButtonManager.BUTTON_FLASH); 2024 } 2025 } 2026 2027 if (mCameraDevice != null) { 2028 mCameraDevice.applySettings(mCameraSettings); 2029 } 2030 } 2031 2032 private void updateCameraParametersInitialize() { 2033 // Reset preview frame rate to the maximum because it may be lowered by 2034 // video camera application. 2035 int[] fpsRange = CameraUtil.getPhotoPreviewFpsRange(mCameraCapabilities); 2036 if (fpsRange != null && fpsRange.length > 0) { 2037 mCameraSettings.setPreviewFpsRange(fpsRange[0], fpsRange[1]); 2038 } 2039 2040 mCameraSettings.setRecordingHintEnabled(false); 2041 2042 if (mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_STABILIZATION)) { 2043 mCameraSettings.setVideoStabilization(false); 2044 } 2045 } 2046 2047 private void updateCameraParametersZoom() { 2048 // Set zoom. 2049 if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) { 2050 mCameraSettings.setZoomRatio(mZoomValue); 2051 } 2052 } 2053 2054 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 2055 private void setAutoExposureLockIfSupported() { 2056 if (mAeLockSupported) { 2057 mCameraSettings.setAutoExposureLock(mFocusManager.getAeAwbLock()); 2058 } 2059 } 2060 2061 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 2062 private void setAutoWhiteBalanceLockIfSupported() { 2063 if (mAwbLockSupported) { 2064 mCameraSettings.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock()); 2065 } 2066 } 2067 2068 private void setFocusAreasIfSupported() { 2069 if (mFocusAreaSupported) { 2070 mCameraSettings.setFocusAreas(mFocusManager.getFocusAreas()); 2071 } 2072 } 2073 2074 private void setMeteringAreasIfSupported() { 2075 if (mMeteringAreaSupported) { 2076 mCameraSettings.setMeteringAreas(mFocusManager.getMeteringAreas()); 2077 } 2078 } 2079 2080 private void updateCameraParametersPreference() { 2081 // some monkey tests can get here when shutting the app down 2082 // make sure mCameraDevice is still valid, b/17580046 2083 if (mCameraDevice == null) { 2084 return; 2085 } 2086 2087 setAutoExposureLockIfSupported(); 2088 setAutoWhiteBalanceLockIfSupported(); 2089 setFocusAreasIfSupported(); 2090 setMeteringAreasIfSupported(); 2091 2092 // Initialize focus mode. 2093 mFocusManager.overrideFocusMode(null); 2094 mCameraSettings 2095 .setFocusMode(mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode())); 2096 SessionStatsCollector.instance().autofocusActive( 2097 mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) == 2098 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE 2099 ); 2100 2101 // Set picture size. 2102 updateParametersPictureSize(); 2103 2104 // Set JPEG quality. 2105 updateParametersPictureQuality(); 2106 2107 // For the following settings, we need to check if the settings are 2108 // still supported by latest driver, if not, ignore the settings. 2109 2110 // Set exposure compensation 2111 updateParametersExposureCompensation(); 2112 2113 // Set the scene mode: also sets flash and white balance. 2114 updateParametersSceneMode(); 2115 2116 if (mContinuousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) { 2117 updateAutoFocusMoveCallback(); 2118 } 2119 } 2120 2121 private void updateParametersPictureSize() { 2122 if (mCameraDevice == null) { 2123 return; 2124 } 2125 2126 SettingsManager settingsManager = mActivity.getSettingsManager(); 2127 String pictureSizeKey = isCameraFrontFacing() ? Keys.KEY_PICTURE_SIZE_FRONT 2128 : Keys.KEY_PICTURE_SIZE_BACK; 2129 String pictureSize = settingsManager.getString(SettingsManager.SCOPE_GLOBAL, 2130 pictureSizeKey); 2131 2132 List<Size> supported = mCameraCapabilities.getSupportedPhotoSizes(); 2133 CameraPictureSizesCacher.updateSizesForCamera(mAppController.getAndroidContext(), 2134 mCameraDevice.getCameraId(), supported); 2135 SettingsUtil.setCameraPictureSize(pictureSize, supported, mCameraSettings, 2136 mCameraDevice.getCameraId()); 2137 2138 Size size = SettingsUtil.getPhotoSize(pictureSize, supported, 2139 mCameraDevice.getCameraId()); 2140 if (ApiHelper.IS_NEXUS_5) { 2141 if (ResolutionUtil.NEXUS_5_LARGE_16_BY_9.equals(pictureSize)) { 2142 mShouldResizeTo16x9 = true; 2143 } else { 2144 mShouldResizeTo16x9 = false; 2145 } 2146 } 2147 2148 // Set a preview size that is closest to the viewfinder height and has 2149 // the right aspect ratio. 2150 List<Size> sizes = mCameraCapabilities.getSupportedPreviewSizes(); 2151 Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes, 2152 (double) size.width() / size.height()); 2153 Size original = mCameraSettings.getCurrentPreviewSize(); 2154 if (!optimalSize.equals(original)) { 2155 Log.v(TAG, "setting preview size. optimal: " + optimalSize + "original: " + original); 2156 mCameraSettings.setPreviewSize(optimalSize); 2157 2158 // Zoom related settings will be changed for different preview 2159 // sizes, so set and read the parameters to get latest values 2160 if (mHandler.getLooper() == Looper.myLooper()) { 2161 Log.v(TAG, "matched looper, setting up preview"); 2162 // On UI thread only, not when camera starts up 2163 setupPreview(); 2164 } else { 2165 Log.v(TAG, "no looper match, directly applying settings"); 2166 mCameraDevice.applySettings(mCameraSettings); 2167 } 2168 mCameraSettings = mCameraDevice.getSettings(); 2169 } 2170 2171 if (optimalSize.width() != 0 && optimalSize.height() != 0) { 2172 Log.v(TAG, "updating aspect ratio"); 2173 mUI.updatePreviewAspectRatio((float) optimalSize.width() 2174 / (float) optimalSize.height()); 2175 } 2176 Log.d(TAG, "Preview size is " + optimalSize); 2177 } 2178 2179 private void updateParametersPictureQuality() { 2180 int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId, 2181 CameraProfile.QUALITY_HIGH); 2182 mCameraSettings.setPhotoJpegCompressionQuality(jpegQuality); 2183 } 2184 2185 private void updateParametersExposureCompensation() { 2186 SettingsManager settingsManager = mActivity.getSettingsManager(); 2187 if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, 2188 Keys.KEY_EXPOSURE_COMPENSATION_ENABLED)) { 2189 int value = settingsManager.getInteger(mAppController.getCameraScope(), 2190 Keys.KEY_EXPOSURE); 2191 int max = mCameraCapabilities.getMaxExposureCompensation(); 2192 int min = mCameraCapabilities.getMinExposureCompensation(); 2193 if (value >= min && value <= max) { 2194 mCameraSettings.setExposureCompensationIndex(value); 2195 } else { 2196 Log.w(TAG, "invalid exposure range: " + value); 2197 } 2198 } else { 2199 // If exposure compensation is not enabled, reset the exposure compensation value. 2200 setExposureCompensation(0); 2201 } 2202 2203 } 2204 2205 private void updateParametersSceneMode() { 2206 CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier(); 2207 SettingsManager settingsManager = mActivity.getSettingsManager(); 2208 2209 mSceneMode = stringifier. 2210 sceneModeFromString(settingsManager.getString(mAppController.getCameraScope(), 2211 Keys.KEY_SCENE_MODE)); 2212 if (mCameraCapabilities.supports(mSceneMode)) { 2213 if (mCameraSettings.getCurrentSceneMode() != mSceneMode) { 2214 mCameraSettings.setSceneMode(mSceneMode); 2215 2216 // Setting scene mode will change the settings of flash mode, 2217 // white balance, and focus mode. Here we read back the 2218 // parameters, so we can know those settings. 2219 mCameraDevice.applySettings(mCameraSettings); 2220 mCameraSettings = mCameraDevice.getSettings(); 2221 } 2222 } else { 2223 mSceneMode = mCameraSettings.getCurrentSceneMode(); 2224 if (mSceneMode == null) { 2225 mSceneMode = CameraCapabilities.SceneMode.AUTO; 2226 } 2227 } 2228 2229 if (CameraCapabilities.SceneMode.AUTO == mSceneMode) { 2230 // Set flash mode. 2231 updateParametersFlashMode(); 2232 2233 // Set focus mode. 2234 mFocusManager.overrideFocusMode(null); 2235 mCameraSettings.setFocusMode( 2236 mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode())); 2237 } else { 2238 mFocusManager.overrideFocusMode(mCameraSettings.getCurrentFocusMode()); 2239 } 2240 } 2241 2242 private void updateParametersFlashMode() { 2243 SettingsManager settingsManager = mActivity.getSettingsManager(); 2244 2245 CameraCapabilities.FlashMode flashMode = mCameraCapabilities.getStringifier() 2246 .flashModeFromString(settingsManager.getString(mAppController.getCameraScope(), 2247 Keys.KEY_FLASH_MODE)); 2248 if (mCameraCapabilities.supports(flashMode)) { 2249 mCameraSettings.setFlashMode(flashMode); 2250 } 2251 } 2252 2253 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 2254 private void updateAutoFocusMoveCallback() { 2255 if (mCameraDevice == null) { 2256 return; 2257 } 2258 if (mCameraSettings.getCurrentFocusMode() == 2259 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) { 2260 mCameraDevice.setAutoFocusMoveCallback(mHandler, 2261 (CameraAFMoveCallback) mAutoFocusMoveCallback); 2262 } else { 2263 mCameraDevice.setAutoFocusMoveCallback(null, null); 2264 } 2265 } 2266 2267 /** 2268 * Sets the exposure compensation to the given value and also updates settings. 2269 * 2270 * @param value exposure compensation value to be set 2271 */ 2272 public void setExposureCompensation(int value) { 2273 int max = mCameraCapabilities.getMaxExposureCompensation(); 2274 int min = mCameraCapabilities.getMinExposureCompensation(); 2275 if (value >= min && value <= max) { 2276 mCameraSettings.setExposureCompensationIndex(value); 2277 SettingsManager settingsManager = mActivity.getSettingsManager(); 2278 settingsManager.set(mAppController.getCameraScope(), 2279 Keys.KEY_EXPOSURE, value); 2280 } else { 2281 Log.w(TAG, "invalid exposure range: " + value); 2282 } 2283 } 2284 2285 // We separate the parameters into several subsets, so we can update only 2286 // the subsets actually need updating. The PREFERENCE set needs extra 2287 // locking because the preference can be changed from GLThread as well. 2288 private void setCameraParameters(int updateSet) { 2289 if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) { 2290 updateCameraParametersInitialize(); 2291 } 2292 2293 if ((updateSet & UPDATE_PARAM_ZOOM) != 0) { 2294 updateCameraParametersZoom(); 2295 } 2296 2297 if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) { 2298 updateCameraParametersPreference(); 2299 } 2300 2301 if (mCameraDevice != null) { 2302 mCameraDevice.applySettings(mCameraSettings); 2303 } 2304 } 2305 2306 // If the Camera is idle, update the parameters immediately, otherwise 2307 // accumulate them in mUpdateSet and update later. 2308 private void setCameraParametersWhenIdle(int additionalUpdateSet) { 2309 mUpdateSet |= additionalUpdateSet; 2310 if (mCameraDevice == null) { 2311 // We will update all the parameters when we open the device, so 2312 // we don't need to do anything now. 2313 mUpdateSet = 0; 2314 return; 2315 } else if (isCameraIdle()) { 2316 setCameraParameters(mUpdateSet); 2317 updateSceneMode(); 2318 mUpdateSet = 0; 2319 } else { 2320 if (!mHandler.hasMessages(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE)) { 2321 mHandler.sendEmptyMessageDelayed(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000); 2322 } 2323 } 2324 } 2325 2326 @Override 2327 public boolean isCameraIdle() { 2328 return (mCameraState == IDLE) || 2329 (mCameraState == PREVIEW_STOPPED) || 2330 ((mFocusManager != null) && mFocusManager.isFocusCompleted() 2331 && (mCameraState != SWITCHING_CAMERA)); 2332 } 2333 2334 @Override 2335 public boolean isImageCaptureIntent() { 2336 String action = mActivity.getIntent().getAction(); 2337 return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action) 2338 || CameraActivity.ACTION_IMAGE_CAPTURE_SECURE.equals(action)); 2339 } 2340 2341 private void setupCaptureParams() { 2342 Bundle myExtras = mActivity.getIntent().getExtras(); 2343 if (myExtras != null) { 2344 mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT); 2345 mCropValue = myExtras.getString("crop"); 2346 } 2347 } 2348 2349 private void initializeCapabilities() { 2350 mCameraCapabilities = mCameraDevice.getCapabilities(); 2351 mFocusAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA); 2352 mMeteringAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.METERING_AREA); 2353 mAeLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_EXPOSURE_LOCK); 2354 mAwbLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_WHITE_BALANCE_LOCK); 2355 mContinuousFocusSupported = 2356 mCameraCapabilities.supports(CameraCapabilities.FocusMode.CONTINUOUS_PICTURE); 2357 } 2358 2359 @Override 2360 public void onZoomChanged(float ratio) { 2361 // Not useful to change zoom value when the activity is paused. 2362 if (mPaused) { 2363 return; 2364 } 2365 mZoomValue = ratio; 2366 if (mCameraSettings == null || mCameraDevice == null) { 2367 return; 2368 } 2369 // Set zoom parameters asynchronously 2370 mCameraSettings.setZoomRatio(mZoomValue); 2371 mCameraDevice.applySettings(mCameraSettings); 2372 } 2373 2374 @Override 2375 public int getCameraState() { 2376 return mCameraState; 2377 } 2378 2379 @Override 2380 public void onMemoryStateChanged(int state) { 2381 mAppController.setShutterEnabled(state == MemoryManager.STATE_OK); 2382 } 2383 2384 @Override 2385 public void onLowMemory() { 2386 // Not much we can do in the photo module. 2387 } 2388 2389 @Override 2390 public void onAccuracyChanged(Sensor sensor, int accuracy) { 2391 } 2392 2393 @Override 2394 public void onSensorChanged(SensorEvent event) { 2395 int type = event.sensor.getType(); 2396 float[] data; 2397 if (type == Sensor.TYPE_ACCELEROMETER) { 2398 data = mGData; 2399 } else if (type == Sensor.TYPE_MAGNETIC_FIELD) { 2400 data = mMData; 2401 } else { 2402 // we should not be here. 2403 return; 2404 } 2405 for (int i = 0; i < 3; i++) { 2406 data[i] = event.values[i]; 2407 } 2408 float[] orientation = new float[3]; 2409 SensorManager.getRotationMatrix(mR, null, mGData, mMData); 2410 SensorManager.getOrientation(mR, orientation); 2411 mHeading = (int) (orientation[0] * 180f / Math.PI) % 360; 2412 if (mHeading < 0) { 2413 mHeading += 360; 2414 } 2415 } 2416 2417 // For debugging only. 2418 public void setDebugUri(Uri uri) { 2419 mDebugUri = uri; 2420 } 2421 2422 // For debugging only. 2423 private void saveToDebugUri(byte[] data) { 2424 if (mDebugUri != null) { 2425 OutputStream outputStream = null; 2426 try { 2427 outputStream = mContentResolver.openOutputStream(mDebugUri); 2428 outputStream.write(data); 2429 outputStream.close(); 2430 } catch (IOException e) { 2431 Log.e(TAG, "Exception while writing debug jpeg file", e); 2432 } finally { 2433 CameraUtil.closeSilently(outputStream); 2434 } 2435 } 2436 } 2437 2438 @Override 2439 public void onRemoteShutterPress() { 2440 mHandler.post(new Runnable() { 2441 @Override 2442 public void run() { 2443 focusAndCapture(); 2444 } 2445 }); 2446 } 2447 2448 /** 2449 * This class manages the loading/releasing/playing of the sounds needed for 2450 * countdown timer. 2451 */ 2452 private class CountdownSoundPlayer { 2453 private SoundPool mSoundPool; 2454 private int mTimerIncrement; 2455 private int mTimerFinalSecond; 2456 2457 void loadSounds() { 2458 // Load the sounds. 2459 if (mSoundPool == null) { 2460 mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0); 2461 mTimerIncrement = mSoundPool.load(mAppController.getAndroidContext(), R.raw.timer_increment, 1); 2462 mTimerFinalSecond = mSoundPool.load(mAppController.getAndroidContext(), R.raw.timer_final_second, 1); 2463 } 2464 } 2465 2466 void onRemainingSecondsChanged(int newVal) { 2467 if (mSoundPool == null) { 2468 Log.e(TAG, "Cannot play sound - they have not been loaded."); 2469 return; 2470 } 2471 if (newVal == 1) { 2472 mSoundPool.play(mTimerFinalSecond, 1.0f, 1.0f, 0, 0, 1.0f); 2473 } else if (newVal == 2 || newVal == 3) { 2474 mSoundPool.play(mTimerIncrement, 1.0f, 1.0f, 0, 0, 1.0f); 2475 } 2476 } 2477 2478 void release() { 2479 if (mSoundPool != null) { 2480 mSoundPool.release(); 2481 mSoundPool = null; 2482 } 2483 } 2484 } 2485} 2486