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