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