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