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