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