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