CaptureModule.java revision 62b4c3c0da3abd397b3790820359927784c50bc2
1/* 2 * Copyright (C) 2014 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.app.Activity; 20import android.content.Context; 21import android.content.res.Configuration; 22import android.graphics.Bitmap; 23import android.graphics.Matrix; 24import android.graphics.RectF; 25import android.graphics.SurfaceTexture; 26import android.hardware.Sensor; 27import android.hardware.SensorEvent; 28import android.hardware.SensorEventListener; 29import android.hardware.SensorManager; 30import android.net.Uri; 31import android.os.Handler; 32import android.provider.MediaStore; 33import android.view.KeyEvent; 34import android.view.OrientationEventListener; 35import android.view.Surface; 36import android.view.TextureView; 37import android.view.View; 38import android.view.View.OnLayoutChangeListener; 39 40import com.android.camera.app.AppController; 41import com.android.camera.app.CameraAppUI; 42import com.android.camera.app.CameraAppUI.BottomBarUISpec; 43import com.android.camera.app.MediaSaver; 44import com.android.camera.debug.DebugPropertyHelper; 45import com.android.camera.debug.Log; 46import com.android.camera.debug.Log.Tag; 47import com.android.camera.hardware.HardwareSpec; 48import com.android.camera.module.ModuleController; 49import com.android.camera.one.OneCamera; 50import com.android.camera.one.OneCamera.AutoFocusMode; 51import com.android.camera.one.OneCamera.AutoFocusState; 52import com.android.camera.one.OneCamera.CaptureReadyCallback; 53import com.android.camera.one.OneCamera.Facing; 54import com.android.camera.one.OneCamera.OpenCallback; 55import com.android.camera.one.OneCamera.PhotoCaptureParameters; 56import com.android.camera.one.OneCamera.PhotoCaptureParameters.Flash; 57import com.android.camera.one.OneCameraManager; 58import com.android.camera.one.v2.OneCameraManagerImpl; 59import com.android.camera.remote.RemoteCameraModule; 60import com.android.camera.session.CaptureSession; 61import com.android.camera.settings.Keys; 62import com.android.camera.settings.SettingsManager; 63import com.android.camera.ui.PreviewStatusListener; 64import com.android.camera.ui.TouchCoordinate; 65import com.android.camera.util.CameraUtil; 66import com.android.camera.util.GcamHelper; 67import com.android.camera.util.Size; 68import com.android.camera.util.UsageStatistics; 69import com.android.camera2.R; 70import com.android.ex.camera2.portability.CameraAgent.CameraProxy; 71 72import java.io.File; 73 74/** 75 * New Capture module that is made to support photo and video capture on top of 76 * the OneCamera API, to transparently support GCam. 77 * <p> 78 * This has been a re-write with pieces taken and improved from GCamModule and 79 * PhotoModule, which are to be retired eventually. 80 * <p> 81 * TODO: 82 * <ul> 83 * <li>Server-side logging 84 * <li>Focusing 85 * <li>Show location dialog 86 * <li>Show resolution dialog on certain devices 87 * <li>Store location 88 * <li>Timer 89 * <li>Capture intent 90 * </ul> 91 */ 92public class CaptureModule extends CameraModule 93 implements MediaSaver.QueueListener, 94 ModuleController, 95 OneCamera.PictureCallback, 96 OneCamera.FocusStateListener, 97 OneCamera.ReadyStateChangedListener, 98 PreviewStatusListener.PreviewAreaChangedListener, 99 RemoteCameraModule, 100 SensorEventListener, 101 SettingsManager.OnSettingChangedListener, 102 TextureView.SurfaceTextureListener { 103 104 /** 105 * Called on layout changes. 106 */ 107 private final OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() { 108 @Override 109 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 110 int oldTop, int oldRight, int oldBottom) { 111 int width = right - left; 112 int height = bottom - top; 113 updatePreviewTransform(width, height, false); 114 } 115 }; 116 117 /** 118 * Show AF target in center of preview and start animation. 119 */ 120 Runnable mShowAutoFocusTargetInCenterRunnable = new Runnable() { 121 @Override 122 public void run() { 123 mUI.setAutoFocusTarget(((int) (mPreviewArea.left + mPreviewArea.right)) / 2, 124 ((int) (mPreviewArea.top + mPreviewArea.bottom)) / 2); 125 mUI.showAutoFocusInProgress(); 126 } 127 }; 128 129 /** 130 * Hide AF target UI element. 131 */ 132 Runnable mHideAutoFocusTargetRunnable = new Runnable() { 133 @Override 134 public void run() { 135 // showAutoFocusSuccess() just hides the AF UI. 136 mUI.showAutoFocusSuccess(); 137 } 138 }; 139 140 private static final Tag TAG = new Tag("CaptureModule"); 141 private static final String PHOTO_MODULE_STRING_ID = "PhotoModule"; 142 /** Enable additional debug output. */ 143 private static final boolean DEBUG = true; 144 /** 145 * This is the delay before we execute onResume tasks when coming from the 146 * lock screen, to allow time for onPause to execute. 147 * <p> 148 * TODO: Make sure this value is in sync with what we see on L. 149 */ 150 private static final int ON_RESUME_TASKS_DELAY_MSEC = 20; 151 152 /** System Properties switch to enable debugging focus UI. */ 153 private static final boolean CAPTURE_DEBUG_UI = DebugPropertyHelper.showCaptureDebugUI(); 154 155 private final Object mDimensionLock = new Object(); 156 157 /** 158 * Sticky Gcam mode is when this module's sole purpose it to be the Gcam mode. 159 * If true, the device uses {@link PhotoModule} for normal picture taking. 160 */ 161 private final boolean mStickyGcamCamera; 162 163 /** 164 * Lock for race conditions in the SurfaceTextureListener callbacks. 165 */ 166 private final Object mSurfaceLock = new Object(); 167 /** Controller giving us access to other services. */ 168 private final AppController mAppController; 169 /** The applications settings manager. */ 170 private final SettingsManager mSettingsManager; 171 /** Application context. */ 172 private final Context mContext; 173 private CaptureModuleUI mUI; 174 /** The camera manager used to open cameras. */ 175 private OneCameraManager mCameraManager; 176 /** The currently opened camera device. */ 177 private OneCamera mCamera; 178 /** The direction the currently opened camera is facing to. */ 179 private Facing mCameraFacing = Facing.BACK; 180 /** Whether HDR is currently enabled. */ 181 private boolean mHdrEnabled = false; 182 183 /** The texture used to render the preview in. */ 184 private SurfaceTexture mPreviewTexture; 185 186 /** State by the module state machine. */ 187 private static enum ModuleState { 188 IDLE, 189 WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED, 190 UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE, 191 } 192 193 /** The current state of the module. */ 194 private ModuleState mState = ModuleState.IDLE; 195 /** Current orientation of the device. */ 196 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN; 197 /** Current zoom value. */ 198 private float mZoomValue = 1f; 199 200 /** True if in AF tap-to-focus sequence. */ 201 private boolean mTapToFocusInProgress = false; 202 203 /** Persistence of Tap to Focus target UI after scan complete. */ 204 private static final int FOCUS_HOLD_UI_MILLIS = 500; 205 206 /** Accelerometer data. */ 207 private final float[] mGData = new float[3]; 208 /** Magnetic sensor data. */ 209 private final float[] mMData = new float[3]; 210 /** Temporary rotation matrix. */ 211 private final float[] mR = new float[16]; 212 /** Current compass heading. */ 213 private int mHeading = -1; 214 215 /** Whether the module is paused right now. */ 216 private boolean mPaused; 217 218 /** Whether this module was resumed from lockscreen capture intent. */ 219 private boolean mIsResumeFromLockScreen = false; 220 221 private final Runnable mResumeTaskRunnable = new Runnable() { 222 @Override 223 public void run() { 224 onResumeTasks(); 225 } 226 }; 227 228 /** Main thread handler. */ 229 private Handler mMainHandler; 230 231 /** Current display rotation in degrees. */ 232 private int mDisplayRotation; 233 /** Current screen width in pixels. */ 234 private int mScreenWidth; 235 /** Current screen height in pixels. */ 236 private int mScreenHeight; 237 /** Current width of preview frames from camera. */ 238 private int mPreviewBufferWidth; 239 /** Current height of preview frames from camera.. */ 240 private int mPreviewBufferHeight; 241 /** Area used by preview. */ 242 RectF mPreviewArea; 243 244 /** The current preview transformation matrix. */ 245 private Matrix mPreviewTranformationMatrix = new Matrix(); 246 /** TODO: This is N5 specific. */ 247 public static final float FULLSCREEN_ASPECT_RATIO = 16 / 9f; 248 249 /** A directory to store debug information in during development. */ 250 private final File mDebugDataDir; 251 252 /** CLEAN UP START */ 253 // private SoundPool mSoundPool; 254 // private int mCaptureStartSoundId; 255 // private static final int NO_SOUND_STREAM = -999; 256 // private final int mCaptureStartSoundStreamId = NO_SOUND_STREAM; 257 // private int mCaptureDoneSoundId; 258 // private SoundClips.Player mSoundPlayer; 259 // private boolean mFirstLayout; 260 // private int[] mTargetFPSRanges; 261 // private float mZoomValue; 262 // private int mSensorOrientation; 263 // private int mLensFacing; 264 // private String mFlashMode; 265 /** CLEAN UP END */ 266 267 public CaptureModule(AppController appController) { 268 this(appController, false); 269 } 270 271 /** Constructs a new capture module. */ 272 public CaptureModule(AppController appController, boolean stickyHdr) { 273 super(appController); 274 mAppController = appController; 275 mContext = mAppController.getAndroidContext(); 276 mSettingsManager = mAppController.getSettingsManager(); 277 mSettingsManager.addListener(this); 278 mDebugDataDir = mContext.getExternalCacheDir(); 279 mStickyGcamCamera = stickyHdr; 280 // TODO: Read HDR setting from user preferences. 281 mHdrEnabled = stickyHdr; 282 } 283 284 @Override 285 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) { 286 Log.d(TAG, "init"); 287 mIsResumeFromLockScreen = isResumeFromLockscreen(activity); 288 mMainHandler = new Handler(activity.getMainLooper()); 289 mCameraManager = mAppController.getCameraManager(); 290 mDisplayRotation = CameraUtil.getDisplayRotation(mContext); 291 mCameraFacing = getFacingFromCameraId(mSettingsManager.getInteger( 292 mAppController.getModuleScope(), 293 Keys.KEY_CAMERA_ID)); 294 mUI = new CaptureModuleUI(activity, this, mAppController.getModuleLayoutRoot(), 295 mLayoutListener); 296 mAppController.setPreviewStatusListener(mUI); 297 mPreviewTexture = mAppController.getCameraAppUI().getSurfaceTexture(); 298 if (mPreviewTexture != null) { 299 initSurface(mPreviewTexture); 300 } 301 } 302 303 @Override 304 public void onShutterButtonFocus(boolean pressed) { 305 // TODO Auto-generated method stub 306 } 307 308 @Override 309 public void onShutterCoordinate(TouchCoordinate coord) { 310 // TODO Auto-generated method stub 311 } 312 313 @Override 314 public void onShutterButtonClick() { 315 // TODO: Add focusing. 316 if (mCamera == null) { 317 return; 318 } 319 320 // Set up the capture session. 321 long sessionTime = System.currentTimeMillis(); 322 String title = CameraUtil.createJpegName(sessionTime); 323 CaptureSession session = getServices().getCaptureSessionManager() 324 .createNewSession(title, sessionTime, null); 325 326 // TODO: Add location. 327 328 // Set up the parameters for this capture. 329 PhotoCaptureParameters params = new PhotoCaptureParameters(); 330 params.title = title; 331 params.callback = this; 332 params.orientation = getOrientation(); 333 params.flashMode = getFlashModeFromSettings(); 334 params.heading = mHeading; 335 params.debugDataFolder = mDebugDataDir; 336 337 // Take the picture. 338 mCamera.takePicture(params, session); 339 } 340 341 @Override 342 public void onPreviewAreaChanged(RectF previewArea) { 343 mPreviewArea = previewArea; 344 // mUI.updatePreviewAreaRect(previewArea); 345 // mUI.positionProgressOverlay(previewArea); 346 } 347 348 @Override 349 public void onSensorChanged(SensorEvent event) { 350 // This is literally the same as the GCamModule implementation. 351 int type = event.sensor.getType(); 352 float[] data; 353 if (type == Sensor.TYPE_ACCELEROMETER) { 354 data = mGData; 355 } else if (type == Sensor.TYPE_MAGNETIC_FIELD) { 356 data = mMData; 357 } else { 358 Log.w(TAG, String.format("Unexpected sensor type %s", event.sensor.getName())); 359 return; 360 } 361 for (int i = 0; i < 3; i++) { 362 data[i] = event.values[i]; 363 } 364 float[] orientation = new float[3]; 365 SensorManager.getRotationMatrix(mR, null, mGData, mMData); 366 SensorManager.getOrientation(mR, orientation); 367 mHeading = (int) (orientation[0] * 180f / Math.PI) % 360; 368 if (mHeading < 0) { 369 mHeading += 360; 370 } 371 } 372 373 @Override 374 public void onAccuracyChanged(Sensor sensor, int accuracy) { 375 // TODO Auto-generated method stub 376 } 377 378 @Override 379 public void onQueueStatus(boolean full) { 380 // TODO Auto-generated method stub 381 } 382 383 @Override 384 public void onRemoteShutterPress() { 385 // TODO: Check whether shutter is enabled. 386 onShutterButtonClick(); 387 } 388 389 @Override 390 public void onSurfaceTextureAvailable(final SurfaceTexture surface, int width, int height) { 391 Log.d(TAG, "onSurfaceTextureAvailable"); 392 // Force to re-apply transform matrix here as a workaround for 393 // b/11168275 394 updatePreviewTransform(width, height, true); 395 initSurface(surface); 396 } 397 398 public void initSurface(final SurfaceTexture surface) { 399 mPreviewTexture = surface; 400 closeCamera(); 401 402 // Only enable HDR on the back camera 403 boolean useHdr = mHdrEnabled && mCameraFacing == Facing.BACK; 404 mCameraManager.open(mCameraFacing, useHdr, getPictureSizeFromSettings(), 405 new OpenCallback() { 406 @Override 407 public void onFailure() { 408 Log.e(TAG, "Could not open camera."); 409 mCamera = null; 410 mAppController.showErrorAndFinish(R.string.cannot_connect_camera); 411 } 412 413 @Override 414 public void onCameraOpened(final OneCamera camera) { 415 Log.d(TAG, "onCameraOpened: " + camera); 416 mCamera = camera; 417 updatePreviewBufferDimension(); 418 419 // If the surface texture is not destroyed, it may have 420 // the last frame lingering. We need to hold off setting 421 // transform until preview is started. 422 resetDefaultBufferSize(); 423 mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED; 424 Log.d(TAG, "starting preview ..."); 425 426 // TODO: Consider rolling these two calls into one. 427 camera.startPreview(new Surface(surface), new CaptureReadyCallback() { 428 429 @Override 430 public void onSetupFailed() { 431 Log.e(TAG, "Could not set up preview."); 432 mCamera.close(null); 433 mCamera = null; 434 // TODO: Show an error message and exit. 435 } 436 437 @Override 438 public void onReadyForCapture() { 439 Log.d(TAG, "Ready for capture."); 440 onPreviewStarted(); 441 // Enable zooming after preview has started. 442 mUI.initializeZoom(mCamera.getMaxZoom()); 443 mCamera.setFocusStateListener(CaptureModule.this); 444 mCamera.setReadyStateChangedListener(CaptureModule.this); 445 } 446 }); 447 } 448 }); 449 } 450 451 @Override 452 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 453 Log.d(TAG, "onSurfaceTextureSizeChanged"); 454 resetDefaultBufferSize(); 455 } 456 457 @Override 458 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 459 Log.d(TAG, "onSurfaceTextureDestroyed"); 460 closeCamera(); 461 return true; 462 } 463 464 @Override 465 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 466 if (mState == ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE) { 467 Log.d(TAG, "onSurfaceTextureUpdated --> updatePreviewTransform"); 468 mState = ModuleState.IDLE; 469 CameraAppUI appUI = mAppController.getCameraAppUI(); 470 updatePreviewTransform(appUI.getSurfaceWidth(), appUI.getSurfaceHeight(), true); 471 } 472 } 473 474 @Override 475 public String getModuleStringIdentifier() { 476 return PHOTO_MODULE_STRING_ID; 477 } 478 479 @Override 480 public void resume() { 481 // Add delay on resume from lock screen only, in order to to speed up 482 // the onResume --> onPause --> onResume cycle from lock screen. 483 // Don't do always because letting go of thread can cause delay. 484 if (mIsResumeFromLockScreen) { 485 Log.v(TAG, "Delayng onResumeTasks from lock screen. " + System.currentTimeMillis()); 486 // Note: onPauseAfterSuper() will delete this runnable, so we will 487 // at most have 1 copy queued up. 488 mMainHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC); 489 } else { 490 onResumeTasks(); 491 } 492 } 493 494 private void onResumeTasks() { 495 Log.d(TAG, "onResumeTasks + " + System.currentTimeMillis()); 496 mPaused = false; 497 mAppController.getCameraAppUI().onChangeCamera(); 498 mAppController.addPreviewAreaSizeChangedListener(this); 499 resetDefaultBufferSize(); 500 getServices().getRemoteShutterListener().onModuleReady(this); 501 // TODO: Check if we can really take a photo right now (memory, camera 502 // state, ... ). 503 mAppController.setShutterEnabled(true); 504 } 505 506 @Override 507 public void pause() { 508 mPaused = true; 509 resetTextureBufferSize(); 510 closeCamera(); 511 // Remove delayed resume trigger, if it hasn't been executed yet. 512 mMainHandler.removeCallbacksAndMessages(null); 513 } 514 515 @Override 516 public void destroy() { 517 } 518 519 @Override 520 public void onLayoutOrientationChanged(boolean isLandscape) { 521 Log.d(TAG, "onLayoutOrientationChanged"); 522 } 523 524 @Override 525 public void onOrientationChanged(int orientation) { 526 // We keep the last known orientation. So if the user first orient 527 // the camera then point the camera to floor or sky, we still have 528 // the correct orientation. 529 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) { 530 return; 531 } 532 mOrientation = CameraUtil.roundOrientation(orientation, mOrientation); 533 } 534 535 @Override 536 public void onCameraAvailable(CameraProxy cameraProxy) { 537 // Ignore since we manage the camera ourselves until we remove this. 538 } 539 540 @Override 541 public void hardResetSettings(SettingsManager settingsManager) { 542 if (mStickyGcamCamera) { 543 // Sitcky HDR+ mode should hard reset HDR+ to on, and camera back 544 // facing. 545 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, true); 546 settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID, 547 getBackFacingCameraId()); 548 } 549 } 550 551 @Override 552 public HardwareSpec getHardwareSpec() { 553 return new HardwareSpec() { 554 @Override 555 public boolean isFrontCameraSupported() { 556 return true; 557 } 558 559 @Override 560 public boolean isHdrSupported() { 561 // TODO: Check if the device has HDR and not HDR+. 562 return false; 563 } 564 565 @Override 566 public boolean isHdrPlusSupported() { 567 return GcamHelper.hasGcamCapture(); 568 } 569 570 @Override 571 public boolean isFlashSupported() { 572 return true; 573 } 574 }; 575 } 576 577 @Override 578 public BottomBarUISpec getBottomBarSpec() { 579 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec(); 580 bottomBarSpec.enableGridLines = true; 581 bottomBarSpec.enableCamera = true; 582 bottomBarSpec.cameraCallback = getCameraCallback(); 583 bottomBarSpec.enableHdr = GcamHelper.hasGcamCapture(); 584 bottomBarSpec.hdrCallback = getHdrButtonCallback(); 585 // TODO: Enable once we support this. 586 bottomBarSpec.enableSelfTimer = false; 587 bottomBarSpec.showSelfTimer = false; 588 // TODO: Deal with e.g. HDR+ if it doesn't support it. 589 // bottomBarSpec.enableFlash = true; 590 return bottomBarSpec; 591 } 592 593 @Override 594 public boolean isUsingBottomBar() { 595 return true; 596 } 597 598 @Override 599 public boolean onKeyDown(int keyCode, KeyEvent event) { 600 return false; 601 } 602 603 @Override 604 public boolean onKeyUp(int keyCode, KeyEvent event) { 605 return false; 606 } 607 608 /** 609 * Focus sequence starts for zone around tap location for single tap. 610 */ 611 @Override 612 public void onSingleTapUp(View view, int x, int y) { 613 Log.v(TAG, "onSingleTapUp x=" + x + " y=" + y); 614 // TODO: This should query actual capability. 615 if (mCameraFacing == Facing.FRONT) { 616 return; 617 } 618 triggerFocusAtScreenCoord(x, y); 619 } 620 621 // TODO: Consider refactoring FocusOverlayManager. 622 // Currently AF state transitions are controlled in OneCameraImpl. 623 // PhotoModule uses FocusOverlayManager which uses API1/portability 624 // logic and coordinates. 625 private void triggerFocusAtScreenCoord(int x, int y) { 626 mTapToFocusInProgress = true; 627 // Show UI immediately even though scan has not started yet. 628 mUI.setAutoFocusTarget(x, y); 629 mUI.showAutoFocusInProgress(); 630 631 // Normalize coordinates to [0,1] per CameraOne API. 632 float points[] = new float[2]; 633 points[0] = (x - mPreviewArea.left) / mPreviewArea.width(); 634 points[1] = (y - mPreviewArea.top) / mPreviewArea.height(); 635 636 // Rotate coordinates to portrait orientation per CameraOne API. 637 Matrix rotationMatrix = new Matrix(); 638 rotationMatrix.setRotate(mDisplayRotation, 0.5f, 0.5f); 639 rotationMatrix.mapPoints(points); 640 mCamera.triggerFocusAndMeterAtPoint(points[0], points[1]); 641 642 // Log touch (screen coordinates). 643 if (mZoomValue == 1f) { 644 TouchCoordinate touchCoordinate = new TouchCoordinate(x - mPreviewArea.left, 645 y - mPreviewArea.top, mPreviewArea.width(), mPreviewArea.height()); 646 // TODO: Add to logging: duration, rotation. 647 UsageStatistics.instance().tapToFocus(touchCoordinate, null); 648 } 649 } 650 651 /** 652 * This AF status listener does two things: 653 * <ol> 654 * <li>Ends tap-to-focus period when mode goes from AUTO to 655 * CONTINUOUS_PICTURE.</li> 656 * <li>Updates AF UI if tap-to-focus is not in progress.</li> 657 * </ol> 658 */ 659 @Override 660 public void onFocusStatusUpdate(final AutoFocusMode mode, final AutoFocusState state) { 661 Log.v(TAG, "AF status is mode:" + mode + " state:" + state); 662 663 if (CAPTURE_DEBUG_UI) { 664 // TODO: Add debug circle radius+color UI to FocusOverlay. 665 // mMainHandler.post(...) 666 } 667 668 // If mTapToFocusInProgress, clear UI. 669 if (mTapToFocusInProgress) { 670 // Clear UI on return to CONTINUOUS_PICTURE (debug mode). 671 if (CAPTURE_DEBUG_UI) { 672 if (mode == AutoFocusMode.CONTINUOUS_PICTURE) { 673 mTapToFocusInProgress = false; 674 mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable); 675 mMainHandler.post(mHideAutoFocusTargetRunnable); 676 } 677 } else { // Clear UI FOCUS_HOLD_UI_MILLIS after scan end (normal). 678 if (mode == AutoFocusMode.AUTO && (state == AutoFocusState.STOPPED_FOCUSED || 679 state == AutoFocusState.STOPPED_UNFOCUSED)) { 680 mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable); 681 mMainHandler.postDelayed(new Runnable() { 682 @Override 683 public void run() { 684 mTapToFocusInProgress = false; 685 mMainHandler.post(mHideAutoFocusTargetRunnable); 686 } 687 }, FOCUS_HOLD_UI_MILLIS); 688 } 689 } 690 } 691 692 // Use the OneCamera auto focus callbacks to show the UI, except for 693 // tap to focus where we show UI right away at touch, and then turn 694 // it off early at 0.5 sec, before the focus lock expires at 3 sec. 695 if (!mTapToFocusInProgress) { 696 switch (state) { 697 case SCANNING: 698 mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable); 699 mMainHandler.post(mShowAutoFocusTargetInCenterRunnable); 700 break; 701 case STOPPED_FOCUSED: 702 case STOPPED_UNFOCUSED: 703 mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable); 704 mMainHandler.post(mHideAutoFocusTargetRunnable); 705 break; 706 } 707 } 708 } 709 710 @Override 711 public void onReadyStateChanged(boolean readyForCapture) { 712 mAppController.setShutterEnabled(readyForCapture); 713 } 714 715 @Override 716 public String getPeekAccessibilityString() { 717 return mAppController.getAndroidContext() 718 .getResources().getString(R.string.photo_accessibility_peek); 719 } 720 721 @Override 722 public void onThumbnailResult(Bitmap bitmap) { 723 // TODO 724 } 725 726 @Override 727 public void onPictureTaken(CaptureSession session) { 728 } 729 730 @Override 731 public void onPictureSaved(Uri uri) { 732 mAppController.notifyNewMedia(uri); 733 } 734 735 @Override 736 public void onTakePictureProgress(float progress) { 737 mUI.setPictureTakingProgress((int) (progress * 100)); 738 } 739 740 @Override 741 public void onPictureTakenFailed() { 742 } 743 744 @Override 745 public void onSettingChanged(SettingsManager settingsManager, String key) { 746 // TODO Auto-generated method stub 747 } 748 749 /** 750 * Updates the preview transform matrix to adapt to the current preview 751 * width, height, and orientation. 752 */ 753 public void updatePreviewTransform() { 754 int width; 755 int height; 756 synchronized (mDimensionLock) { 757 width = mScreenWidth; 758 height = mScreenHeight; 759 } 760 updatePreviewTransform(width, height); 761 } 762 763 /** 764 * Set zoom value. 765 * 766 * @param zoom Zoom value, must be between 1.0 and mCamera.getMaxZoom(). 767 */ 768 public void setZoom(float zoom) { 769 mZoomValue = zoom; 770 if (mCamera != null) { 771 mCamera.setZoom(zoom); 772 } 773 } 774 775 /** 776 * TODO: Remove this method once we are in pure CaptureModule land. 777 */ 778 private String getBackFacingCameraId() { 779 if (!(mCameraManager instanceof OneCameraManagerImpl)) { 780 throw new IllegalStateException("This should never be called with Camera API V1"); 781 } 782 OneCameraManagerImpl manager = (OneCameraManagerImpl) mCameraManager; 783 return manager.getFirstBackCameraId(); 784 } 785 786 /** 787 * @return Depending on whether we're in sticky-HDR mode or not, return the 788 * proper callback to be used for when the HDR/HDR+ button is 789 * pressed. 790 */ 791 private ButtonManager.ButtonCallback getHdrButtonCallback() { 792 if (mStickyGcamCamera) { 793 return new ButtonManager.ButtonCallback() { 794 @Override 795 public void onStateChanged(int state) { 796 if (mPaused) { 797 return; 798 } 799 if (state == ButtonManager.ON) { 800 throw new IllegalStateException( 801 "Can't leave hdr plus mode if switching to hdr plus mode."); 802 } 803 SettingsManager settingsManager = mAppController.getSettingsManager(); 804 settingsManager.set(mAppController.getModuleScope(), 805 Keys.KEY_REQUEST_RETURN_HDR_PLUS, false); 806 switchToRegularCapture(); 807 } 808 }; 809 } else { 810 return new ButtonManager.ButtonCallback() { 811 @Override 812 public void onStateChanged(int hdrEnabled) { 813 if (mPaused) { 814 return; 815 } 816 Log.d(TAG, "HDR enabled =" + hdrEnabled); 817 mHdrEnabled = hdrEnabled == 1; 818 switchCamera(); 819 } 820 }; 821 } 822 } 823 824 /** 825 * @return Depending on whether we're in sticky-HDR mode or not, this 826 * returns the proper callback to be used for when the camera 827 * (front/back switch) button is pressed. 828 */ 829 private ButtonManager.ButtonCallback getCameraCallback() { 830 if (mStickyGcamCamera) { 831 return new ButtonManager.ButtonCallback() { 832 @Override 833 public void onStateChanged(int state) { 834 if (mPaused) { 835 return; 836 } 837 838 // At the time this callback is fired, the camera id setting 839 // has changed to the desired camera. 840 SettingsManager settingsManager = mAppController.getSettingsManager(); 841 if (Keys.isCameraBackFacing(settingsManager, 842 mAppController.getModuleScope())) { 843 throw new IllegalStateException( 844 "Hdr plus should never be switching from front facing camera."); 845 } 846 847 // Switch to photo mode, but request a return to hdr plus on 848 // switching to back camera again. 849 settingsManager.set(mAppController.getModuleScope(), 850 Keys.KEY_REQUEST_RETURN_HDR_PLUS, true); 851 switchToRegularCapture(); 852 } 853 }; 854 } else { 855 return new ButtonManager.ButtonCallback() { 856 @Override 857 public void onStateChanged(int cameraId) { 858 if (mPaused) { 859 return; 860 } 861 862 // At the time this callback is fired, the camera id 863 // has be set to the desired camera. 864 mSettingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID, 865 cameraId); 866 867 Log.d(TAG, "Start to switch camera. cameraId=" + cameraId); 868 mCameraFacing = getFacingFromCameraId(cameraId); 869 switchCamera(); 870 } 871 }; 872 } 873 } 874 875 /** 876 * Switches to PhotoModule to do regular photo captures. 877 * <p> 878 * TODO: Remove this once we use CaptureModule for photo taking. 879 */ 880 private void switchToRegularCapture() { 881 // Turn off HDR+ before switching back to normal photo mode. 882 SettingsManager settingsManager = mAppController.getSettingsManager(); 883 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false); 884 885 // Disable this button to prevent callbacks from this module from firing 886 // while we are transitioning modules. 887 ButtonManager buttonManager = mAppController.getButtonManager(); 888 buttonManager.disableButtonClick(ButtonManager.BUTTON_HDR_PLUS); 889 mAppController.getCameraAppUI().freezeScreenUntilPreviewReady(); 890 mAppController.onModeSelected(mContext.getResources().getInteger( 891 R.integer.camera_mode_photo)); 892 buttonManager.enableButtonClick(ButtonManager.BUTTON_HDR_PLUS); 893 } 894 895 /** 896 * Called when the preview started. Informs the app controller and queues a 897 * transform update when the next preview frame arrives. 898 */ 899 private void onPreviewStarted() { 900 if (mState == ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED) { 901 mState = ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE; 902 } 903 mAppController.onPreviewStarted(); 904 } 905 906 /** 907 * Update the preview transform based on the new dimensions. Will not force 908 * an update, if it's not necessary. 909 */ 910 private void updatePreviewTransform(int incomingWidth, int incomingHeight) { 911 updatePreviewTransform(incomingWidth, incomingHeight, false); 912 } 913 914 /*** 915 * Update the preview transform based on the new dimensions. TODO: Make work 916 * with all: aspect ratios/resolutions x screens/cameras. 917 */ 918 private void updatePreviewTransform(int incomingWidth, int incomingHeight, 919 boolean forceUpdate) { 920 Log.d(TAG, "updatePreviewTransform: " + incomingWidth + " x " + incomingHeight); 921 922 synchronized (mDimensionLock) { 923 int incomingRotation = CameraUtil 924 .getDisplayRotation(mContext); 925 // Check for an actual change: 926 if (mScreenHeight == incomingHeight && mScreenWidth == incomingWidth && 927 incomingRotation == mDisplayRotation && !forceUpdate) { 928 return; 929 } 930 // Update display rotation and dimensions 931 mDisplayRotation = incomingRotation; 932 mScreenWidth = incomingWidth; 933 mScreenHeight = incomingHeight; 934 updatePreviewBufferDimension(); 935 936 mPreviewTranformationMatrix = mAppController.getCameraAppUI().getPreviewTransform( 937 mPreviewTranformationMatrix); 938 int width = mScreenWidth; 939 int height = mScreenHeight; 940 941 // Assumptions: 942 // - Aspect ratio for the sensor buffers is in landscape 943 // orientation, 944 // - Dimensions of buffers received are rotated to the natural 945 // device orientation. 946 // - The contents of each buffer are rotated by the inverse of 947 // the display rotation. 948 // - Surface scales the buffer to fit the current view bounds. 949 950 // Get natural orientation and buffer dimensions 951 int naturalOrientation = CaptureModuleUtil 952 .getDeviceNaturalOrientation(mContext); 953 int effectiveWidth = mPreviewBufferWidth; 954 int effectiveHeight = mPreviewBufferHeight; 955 956 if (DEBUG) { 957 Log.v(TAG, "Rotation: " + mDisplayRotation); 958 Log.v(TAG, "Screen Width: " + mScreenWidth); 959 Log.v(TAG, "Screen Height: " + mScreenHeight); 960 Log.v(TAG, "Buffer width: " + mPreviewBufferWidth); 961 Log.v(TAG, "Buffer height: " + mPreviewBufferHeight); 962 Log.v(TAG, "Natural orientation: " + naturalOrientation); 963 } 964 965 // If natural orientation is portrait, rotate the buffer 966 // dimensions 967 if (naturalOrientation == Configuration.ORIENTATION_PORTRAIT) { 968 int temp = effectiveWidth; 969 effectiveWidth = effectiveHeight; 970 effectiveHeight = temp; 971 } 972 973 // Find and center view rect and buffer rect 974 RectF viewRect = new RectF(0, 0, width, height); 975 RectF bufRect = new RectF(0, 0, effectiveWidth, effectiveHeight); 976 float centerX = viewRect.centerX(); 977 float centerY = viewRect.centerY(); 978 bufRect.offset(centerX - bufRect.centerX(), centerY - bufRect.centerY()); 979 980 // Undo ScaleToFit.FILL done by the surface 981 mPreviewTranformationMatrix.setRectToRect(viewRect, bufRect, Matrix.ScaleToFit.FILL); 982 983 // Rotate buffer contents to proper orientation 984 mPreviewTranformationMatrix.postRotate(getPreviewOrientation(mDisplayRotation), 985 centerX, centerY); 986 987 // TODO: This is probably only working for the N5. Need to test 988 // on a device like N10 with different sensor orientation. 989 if ((mDisplayRotation % 180) == 90) { 990 int temp = effectiveWidth; 991 effectiveWidth = effectiveHeight; 992 effectiveHeight = temp; 993 } 994 995 // Scale to fit view, cropping the longest dimension 996 float scale = 997 Math.min(width / (float) effectiveWidth, height 998 / (float) effectiveHeight); 999 mPreviewTranformationMatrix.postScale(scale, scale, centerX, centerY); 1000 1001 // TODO: Take these quantities from mPreviewArea. 1002 float previewWidth = effectiveWidth * scale; 1003 float previewHeight = effectiveHeight * scale; 1004 float previewCenterX = previewWidth / 2; 1005 float previewCenterY = previewHeight / 2; 1006 mPreviewTranformationMatrix.postTranslate(previewCenterX - centerX, previewCenterY 1007 - centerY); 1008 1009 mAppController.updatePreviewTransform(mPreviewTranformationMatrix); 1010 mAppController.getCameraAppUI().hideLetterboxing(); 1011 // if (mGcamProxy != null) { 1012 // mGcamProxy.postSetAspectRatio(mFinalAspectRatio); 1013 // } 1014 // mUI.updatePreviewAreaRect(new RectF(0, 0, previewWidth, 1015 // previewHeight)); 1016 1017 // TODO: Add face detection. 1018 // Characteristics info = 1019 // mapp.getCameraProvider().getCharacteristics(0); 1020 // mUI.setupFaceDetection(CameraUtil.getDisplayOrientation(incomingRotation, 1021 // info), false); 1022 // updateCamera2FaceBoundTransform(new 1023 // RectF(mEffectiveCropRegion), 1024 // new RectF(0, 0, mBufferWidth, mBufferHeight), 1025 // new RectF(0, 0, previewWidth, previewHeight), getRotation()); 1026 } 1027 } 1028 1029 /** 1030 * Based on the current picture size, selects the best preview dimension and 1031 * stores it in {@link #mPreviewBufferWidth} and 1032 * {@link #mPreviewBufferHeight}. 1033 */ 1034 private void updatePreviewBufferDimension() { 1035 if (mCamera == null) { 1036 return; 1037 } 1038 1039 Size pictureSize = getPictureSizeFromSettings(); 1040 Size previewBufferSize = mCamera.pickPreviewSize(pictureSize, mContext); 1041 mPreviewBufferWidth = previewBufferSize.getWidth(); 1042 mPreviewBufferHeight = previewBufferSize.getHeight(); 1043 } 1044 1045 /** 1046 * Resets the default buffer size to the initially calculated size. 1047 */ 1048 private void resetDefaultBufferSize() { 1049 synchronized (mSurfaceLock) { 1050 if (mPreviewTexture != null) { 1051 mPreviewTexture.setDefaultBufferSize(mPreviewBufferWidth, mPreviewBufferHeight); 1052 } 1053 } 1054 } 1055 1056 private void closeCamera() { 1057 if (mCamera != null) { 1058 mCamera.setFocusStateListener(null); 1059 mCamera.close(null); 1060 mCamera = null; 1061 } 1062 } 1063 1064 private int getOrientation() { 1065 if (mAppController.isAutoRotateScreen()) { 1066 return mDisplayRotation; 1067 } else { 1068 return mOrientation; 1069 } 1070 } 1071 1072 /** 1073 * @return Whether we are resuming from within the lockscreen. 1074 */ 1075 private static boolean isResumeFromLockscreen(Activity activity) { 1076 String action = activity.getIntent().getAction(); 1077 return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action) 1078 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)); 1079 } 1080 1081 /** 1082 * Re-initialize the camera if e.g. the HDR mode or facing property changed. 1083 */ 1084 private void switchCamera() { 1085 if (mPaused) { 1086 return; 1087 } 1088 // TODO: Un-comment once we have timer back. 1089 // cancelCountDown(); 1090 1091 mAppController.freezeScreenUntilPreviewReady(); 1092 1093 initSurface(mPreviewTexture); 1094 1095 // TODO: Un-comment once we have focus back. 1096 // if (mFocusManager != null) { 1097 // mFocusManager.removeMessages(); 1098 // } 1099 // mFocusManager.setMirror(mMirror); 1100 } 1101 1102 private Size getPictureSizeFromSettings() { 1103 String pictureSizeKey = mCameraFacing == Facing.FRONT ? Keys.KEY_PICTURE_SIZE_FRONT 1104 : Keys.KEY_PICTURE_SIZE_BACK; 1105 return mSettingsManager.getSize(SettingsManager.SCOPE_GLOBAL, pictureSizeKey); 1106 } 1107 1108 private int getPreviewOrientation(int deviceOrientationDegrees) { 1109 // Important: Camera2 buffers are already rotated to the natural 1110 // orientation of the device (at least for the back-camera). 1111 1112 // TODO: Remove this hack for the front camera as soon as b/16637957 is 1113 // fixed. 1114 if (mCameraFacing == Facing.FRONT) { 1115 deviceOrientationDegrees += 180; 1116 } 1117 return (360 - deviceOrientationDegrees) % 360; 1118 } 1119 1120 /** 1121 * Returns which way around the camera is facing, based on it's ID. 1122 * <p> 1123 * TODO: This needs to change so that we store the direction directly in the 1124 * settings, rather than a Camera ID. 1125 */ 1126 private static Facing getFacingFromCameraId(int cameraId) { 1127 return cameraId == 1 ? Facing.FRONT : Facing.BACK; 1128 } 1129 1130 private void resetTextureBufferSize() { 1131 // Reset the default buffer sizes on the shared SurfaceTexture 1132 // so they are not scaled for gcam. 1133 // 1134 // According to the documentation for 1135 // SurfaceTexture.setDefaultBufferSize, 1136 // photo and video based image producers (presumably only Camera 1 api), 1137 // override this buffer size. Any module that uses egl to render to a 1138 // SurfaceTexture must have these buffer sizes reset manually. Otherwise 1139 // the SurfaceTexture cannot be transformed by matrix set on the 1140 // TextureView. 1141 if (mPreviewTexture != null) { 1142 mPreviewTexture.setDefaultBufferSize(mAppController.getCameraAppUI().getSurfaceWidth(), 1143 mAppController.getCameraAppUI().getSurfaceHeight()); 1144 } 1145 } 1146 1147 /** 1148 * @return The currently set Flash settings. Defaults to AUTO if the setting 1149 * could not be parsed. 1150 */ 1151 private Flash getFlashModeFromSettings() { 1152 String flashSetting = mSettingsManager.getString(mAppController.getCameraScope(), 1153 Keys.KEY_FLASH_MODE); 1154 try { 1155 return Flash.valueOf(flashSetting.toUpperCase()); 1156 } catch (IllegalArgumentException ex) { 1157 Log.w(TAG, "Could not parse Flash Setting. Defaulting to AUTO."); 1158 return Flash.AUTO; 1159 } 1160 } 1161} 1162