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