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