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