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