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