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