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