CaptureModule.java revision 26b3334755ffccc039249005435d5cc598d0689e
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 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 String mFlashMode; 289 /** CLEAN UP END */ 290 291 /** Constructs a new capture module. */ 292 public CaptureModule(AppController appController) { 293 super(appController); 294 mAppController = appController; 295 mContext = mAppController.getAndroidContext(); 296 mSettingsManager = mAppController.getSettingsManager(); 297 mSettingsManager.addListener(this); 298 mDebugDataDir = mContext.getExternalCacheDir(); 299 } 300 301 @Override 302 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) { 303 Log.d(TAG, "init"); 304 mIsResumeFromLockScreen = isResumeFromLockscreen(activity); 305 mMainHandler = new Handler(activity.getMainLooper()); 306 mCameraManager = mAppController.getCameraManager(); 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 if (mPreviewTexture != null) { 316 initSurface(mPreviewTexture); 317 } 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 337 // Set up the capture session. 338 long sessionTime = System.currentTimeMillis(); 339 String title = CameraUtil.createJpegName(sessionTime); 340 CaptureSession session = getServices().getCaptureSessionManager() 341 .createNewSession(title, sessionTime, null); 342 343 // TODO: Add location. 344 345 // Set up the parameters for this capture. 346 PhotoCaptureParameters params = new PhotoCaptureParameters(); 347 params.title = title; 348 params.callback = this; 349 params.orientation = getOrientation(); 350 params.flashMode = getFlashModeFromSettings(); 351 params.heading = mHeading; 352 params.debugDataFolder = mDebugDataDir; 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 if (mHeading < 0) { 386 mHeading += 360; 387 } 388 } 389 390 @Override 391 public void onAccuracyChanged(Sensor sensor, int accuracy) { 392 // TODO Auto-generated method stub 393 } 394 395 @Override 396 public void onQueueStatus(boolean full) { 397 // TODO Auto-generated method stub 398 } 399 400 @Override 401 public void onRemoteShutterPress() { 402 // TODO: Check whether shutter is enabled. 403 onShutterButtonClick(); 404 } 405 406 @Override 407 public void onSurfaceTextureAvailable(final SurfaceTexture surface, int width, int height) { 408 Log.d(TAG, "onSurfaceTextureAvailable"); 409 // Force to re-apply transform matrix here as a workaround for 410 // b/11168275 411 updatePreviewTransform(width, height, true); 412 initSurface(surface); 413 } 414 415 public void initSurface(final SurfaceTexture surface) { 416 mPreviewTexture = surface; 417 closeCamera(); 418 419 mCameraManager.open(mCameraFacing, getPictureSizeFromSettings(), new OpenCallback() { 420 @Override 421 public void onFailure() { 422 Log.e(TAG, "Could not open camera."); 423 mCamera = null; 424 mAppController.showErrorAndFinish(R.string.cannot_connect_camera); 425 } 426 427 @Override 428 public void onCameraOpened(final OneCamera camera) { 429 Log.d(TAG, "onCameraOpened: " + camera); 430 mCamera = camera; 431 updateBufferDimension(); 432 433 // If the surface texture is not destroyed, it may have the last 434 // frame lingering. 435 // We need to hold off setting transform until preview is 436 // started. 437 resetDefaultBufferSize(); 438 mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED; 439 440 Log.d(TAG, "starting preview ..."); 441 442 // TODO: Consider rolling these two calls into one. 443 camera.startPreview(new Surface(surface), new CaptureReadyCallback() { 444 445 @Override 446 public void onSetupFailed() { 447 Log.e(TAG, "Could not set up preview."); 448 mCamera.close(null); 449 mCamera = null; 450 // TODO: Show an error message and exit. 451 } 452 453 @Override 454 public void onReadyForCapture() { 455 Log.d(TAG, "Ready for capture."); 456 onPreviewStarted(); 457 // Enable zooming after preview has started. 458 mUI.initializeZoom(mCamera.getMaxZoom()); 459 mCamera.setFocusStateListener(CaptureModule.this); 460 mCamera.setReadyStateChangedListener(CaptureModule.this); 461 } 462 }); 463 } 464 }); 465 } 466 467 @Override 468 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 469 Log.d(TAG, "onSurfaceTextureSizeChanged"); 470 resetDefaultBufferSize(); 471 } 472 473 @Override 474 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 475 Log.d(TAG, "onSurfaceTextureDestroyed"); 476 closeCamera(); 477 return true; 478 } 479 480 @Override 481 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 482 if (mState == ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE) { 483 Log.d(TAG, "onSurfaceTextureUpdated --> updatePreviewTransform"); 484 mState = ModuleState.IDLE; 485 CameraAppUI appUI = mAppController.getCameraAppUI(); 486 updatePreviewTransform(appUI.getSurfaceWidth(), appUI.getSurfaceHeight(), true); 487 } 488 } 489 490 @Override 491 public String getModuleStringIdentifier() { 492 return PHOTO_MODULE_STRING_ID; 493 } 494 495 @Override 496 public void resume() { 497 // Add delay on resume from lock screen only, in order to to speed up 498 // the onResume --> onPause --> onResume cycle from lock screen. 499 // Don't do always because letting go of thread can cause delay. 500 if (mIsResumeFromLockScreen) { 501 Log.v(TAG, "Delayng onResumeTasks from lock screen. " + System.currentTimeMillis()); 502 // Note: onPauseAfterSuper() will delete this runnable, so we will 503 // at most have 1 copy queued up. 504 mMainHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC); 505 } else { 506 onResumeTasks(); 507 } 508 } 509 510 private void onResumeTasks() { 511 Log.d(TAG, "onResumeTasks + " + System.currentTimeMillis()); 512 mPaused = false; 513 mAppController.getCameraAppUI().onChangeCamera(); 514 mAppController.addPreviewAreaSizeChangedListener(this); 515 resetDefaultBufferSize(); 516 getServices().getRemoteShutterListener().onModuleReady(this); 517 // TODO: Check if we can really take a photo right now (memory, camera 518 // state, ... ). 519 mAppController.setShutterEnabled(true); 520 } 521 522 @Override 523 public void pause() { 524 mPaused = true; 525 resetTextureBufferSize(); 526 closeCamera(); 527 // Remove delayed resume trigger, if it hasn't been executed yet. 528 mMainHandler.removeCallbacksAndMessages(null); 529 } 530 531 @Override 532 public void destroy() { 533 } 534 535 @Override 536 public void onLayoutOrientationChanged(boolean isLandscape) { 537 Log.d(TAG, "onLayoutOrientationChanged"); 538 } 539 540 @Override 541 public void onOrientationChanged(int orientation) { 542 // We keep the last known orientation. So if the user first orient 543 // the camera then point the camera to floor or sky, we still have 544 // the correct orientation. 545 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) { 546 return; 547 } 548 mOrientation = CameraUtil.roundOrientation(orientation, mOrientation); 549 } 550 551 @Override 552 public void onCameraAvailable(CameraProxy cameraProxy) { 553 // Ignore since we manage the camera ourselves until we remove this. 554 } 555 556 @Override 557 public void hardResetSettings(SettingsManager settingsManager) { 558 // TODO Auto-generated method stub 559 } 560 561 @Override 562 public HardwareSpec getHardwareSpec() { 563 return new HardwareSpec() { 564 @Override 565 public boolean isFrontCameraSupported() { 566 return true; 567 } 568 569 @Override 570 public boolean isHdrSupported() { 571 return false; 572 } 573 574 @Override 575 public boolean isHdrPlusSupported() { 576 // TODO: Enable once we support this. 577 return false; 578 } 579 580 @Override 581 public boolean isFlashSupported() { 582 return true; 583 } 584 }; 585 } 586 587 @Override 588 public BottomBarUISpec getBottomBarSpec() { 589 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec(); 590 bottomBarSpec.enableGridLines = true; 591 bottomBarSpec.enableCamera = true; 592 bottomBarSpec.cameraCallback = mCameraSwitchCallback; 593 // TODO: Enable once we support this. 594 bottomBarSpec.enableHdr = false; 595 // TODO: Enable once we support this. 596 bottomBarSpec.hdrCallback = null; 597 // TODO: Enable once we support this. 598 bottomBarSpec.enableSelfTimer = false; 599 bottomBarSpec.showSelfTimer = false; 600 // TODO: Deal with e.g. HDR+ if it doesn't support it. 601 bottomBarSpec.enableFlash = true; 602 return bottomBarSpec; 603 } 604 605 @Override 606 public boolean isUsingBottomBar() { 607 return true; 608 } 609 610 @Override 611 public boolean onKeyDown(int keyCode, KeyEvent event) { 612 return false; 613 } 614 615 @Override 616 public boolean onKeyUp(int keyCode, KeyEvent event) { 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 638 private void triggerFocusAtScreenCoord(int x, int y) { 639 mTapToFocusInProgress = true; 640 // Show UI immediately even though scan has not started yet. 641 mUI.setAutoFocusTarget(x, y); 642 mUI.showAutoFocusInProgress(); 643 644 // Normalize coordinates to [0,1] per CameraOne API. 645 float points[] = new float[2]; 646 points[0] = (x - mPreviewArea.left) / mPreviewArea.width(); 647 points[1] = (y - mPreviewArea.top) / mPreviewArea.height(); 648 649 // Rotate coordinates to portrait orientation per CameraOne API. 650 Matrix rotationMatrix = new Matrix(); 651 rotationMatrix.setRotate(mDisplayRotation, 0.5f, 0.5f); 652 rotationMatrix.mapPoints(points); 653 mCamera.triggerFocusAndMeterAtPoint(points[0], points[1]); 654 655 // Log touch (screen coordinates). 656 if (mZoomValue == 1f) { 657 TouchCoordinate touchCoordinate = new TouchCoordinate(x - mPreviewArea.left, 658 y - mPreviewArea.top, mPreviewArea.width(), mPreviewArea.height()); 659 // TODO: Add to logging: duration, rotation. 660 UsageStatistics.instance().tapToFocus(touchCoordinate, null); 661 } 662 } 663 664 /** 665 * This AF status listener does two things: 666 * <ol> 667 * <li>Ends tap-to-focus period when mode goes from AUTO to CONTINUOUS_PICTURE.</li> 668 * <li>Updates AF UI if tap-to-focus is not in progress.</li> 669 * </ol> 670 */ 671 @Override 672 public void onFocusStatusUpdate(final AutoFocusMode mode, final AutoFocusState state) { 673 Log.v(TAG, "AF status is mode:" + mode + " state:" + state); 674 675 if (FOCUS_DEBUG_UI) { 676 // TODO: Add debug circle radius+color UI to FocusOverlay. 677 // mMainHandler.post(...) 678 } 679 680 // If mTapToFocusInProgress, clear UI. 681 if (mTapToFocusInProgress) { 682 // Clear UI on return to CONTINUOUS_PICTURE (debug mode). 683 if (FOCUS_DEBUG_UI) { 684 if (mode == AutoFocusMode.CONTINUOUS_PICTURE) { 685 mTapToFocusInProgress = false; 686 mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable); 687 mMainHandler.post(mHideAutoFocusTargetRunnable); 688 } 689 } else { // Clear UI FOCUS_HOLD_UI_MILLIS after scan end (normal). 690 if (mode == AutoFocusMode.AUTO && (state == AutoFocusState.STOPPED_FOCUSED || 691 state == AutoFocusState.STOPPED_UNFOCUSED)) { 692 mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable); 693 mMainHandler.postDelayed(new Runnable() { 694 @Override 695 public void run() { 696 mTapToFocusInProgress = false; 697 mMainHandler.post(mHideAutoFocusTargetRunnable); 698 } 699 }, FOCUS_HOLD_UI_MILLIS); 700 } 701 } 702 } 703 704 // Use the OneCamera auto focus callbacks to show the UI, except for 705 // tap to focus where we show UI right away at touch, and then turn 706 // it off early at 0.5 sec, before the focus lock expires at 3 sec. 707 if (!mTapToFocusInProgress) { 708 switch (state) { 709 case SCANNING: 710 mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable); 711 mMainHandler.post(mShowAutoFocusTargetInCenterRunnable); 712 break; 713 case STOPPED_FOCUSED: 714 case STOPPED_UNFOCUSED: 715 mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable); 716 mMainHandler.post(mHideAutoFocusTargetRunnable); 717 break; 718 } 719 } 720 } 721 722 @Override 723 public void onReadyStateChanged(boolean readyForCapture) { 724 mAppController.setShutterEnabled(readyForCapture); 725 } 726 727 @Override 728 public String getPeekAccessibilityString() { 729 return mAppController.getAndroidContext() 730 .getResources().getString(R.string.photo_accessibility_peek); 731 } 732 733 @Override 734 public void onThumbnailResult(Bitmap bitmap) { 735 // TODO 736 } 737 738 @Override 739 public void onPictureTaken(CaptureSession session) { 740 } 741 742 @Override 743 public void onPictureSaved(Uri uri) { 744 mAppController.notifyNewMedia(uri); 745 } 746 747 @Override 748 public void onTakePictureProgress(int progressPercent) { 749 // TODO once we have HDR+ hooked up. 750 } 751 752 @Override 753 public void onPictureTakenFailed() { 754 } 755 756 @Override 757 public void onSettingChanged(SettingsManager settingsManager, String key) { 758 // TODO Auto-generated method stub 759 } 760 761 /** 762 * Updates the preview transform matrix to adapt to the current preview 763 * width, height, and orientation. 764 */ 765 public void updatePreviewTransform() { 766 int width; 767 int height; 768 synchronized (mDimensionLock) { 769 width = mScreenWidth; 770 height = mScreenHeight; 771 } 772 updatePreviewTransform(width, height); 773 } 774 775 /** 776 * Set zoom value. 777 * @param zoom Zoom value, must be between 1.0 and mCamera.getMaxZoom(). 778 */ 779 public void setZoom(float zoom) { 780 mZoomValue = zoom; 781 if (mCamera != null) { 782 mCamera.setZoom(zoom); 783 } 784 } 785 786 /** 787 * Called when the preview started. Informs the app controller and queues a 788 * transform update when the next preview frame arrives. 789 */ 790 private void onPreviewStarted() { 791 if (mState == ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED) { 792 mState = ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE; 793 } 794 mAppController.onPreviewStarted(); 795 } 796 797 /** 798 * Update the preview transform based on the new dimensions. Will not force 799 * an update, if it's not necessary. 800 */ 801 private void updatePreviewTransform(int incomingWidth, int incomingHeight) { 802 updatePreviewTransform(incomingWidth, incomingHeight, false); 803 } 804 805 /*** 806 * Update the preview transform based on the new dimensions. 807 * TODO: Make work with all: aspect ratios/resolutions x screens/cameras. 808 */ 809 private void updatePreviewTransform(int incomingWidth, int incomingHeight, 810 boolean forceUpdate) { 811 Log.d(TAG, "updatePreviewTransform: " + incomingWidth + " x " + incomingHeight); 812 813 synchronized (mDimensionLock) { 814 int incomingRotation = CameraUtil 815 .getDisplayRotation(mContext); 816 // Check for an actual change: 817 if (mScreenHeight == incomingHeight && mScreenWidth == incomingWidth && 818 incomingRotation == mDisplayRotation && !forceUpdate) { 819 return; 820 } 821 // Update display rotation and dimensions 822 mDisplayRotation = incomingRotation; 823 mScreenWidth = incomingWidth; 824 mScreenHeight = incomingHeight; 825 updateBufferDimension(); 826 827 mPreviewTranformationMatrix = mAppController.getCameraAppUI().getPreviewTransform( 828 mPreviewTranformationMatrix); 829 int width = mScreenWidth; 830 int height = mScreenHeight; 831 832 // Assumptions: 833 // - Aspect ratio for the sensor buffers is in landscape 834 // orientation, 835 // - Dimensions of buffers received are rotated to the natural 836 // device orientation. 837 // - The contents of each buffer are rotated by the inverse of 838 // the display rotation. 839 // - Surface scales the buffer to fit the current view bounds. 840 841 // Get natural orientation and buffer dimensions 842 int naturalOrientation = CaptureModuleUtil 843 .getDeviceNaturalOrientation(mContext); 844 int effectiveWidth = mPreviewBufferWidth; 845 int effectiveHeight = mPreviewBufferHeight; 846 847 if (DEBUG) { 848 Log.v(TAG, "Rotation: " + mDisplayRotation); 849 Log.v(TAG, "Screen Width: " + mScreenWidth); 850 Log.v(TAG, "Screen Height: " + mScreenHeight); 851 Log.v(TAG, "Buffer width: " + mPreviewBufferWidth); 852 Log.v(TAG, "Buffer height: " + mPreviewBufferHeight); 853 Log.v(TAG, "Natural orientation: " + naturalOrientation); 854 } 855 856 // If natural orientation is portrait, rotate the buffer 857 // dimensions 858 if (naturalOrientation == Configuration.ORIENTATION_PORTRAIT) { 859 int temp = effectiveWidth; 860 effectiveWidth = effectiveHeight; 861 effectiveHeight = temp; 862 } 863 864 // Find and center view rect and buffer rect 865 RectF viewRect = new RectF(0, 0, width, height); 866 RectF bufRect = new RectF(0, 0, effectiveWidth, effectiveHeight); 867 float centerX = viewRect.centerX(); 868 float centerY = viewRect.centerY(); 869 bufRect.offset(centerX - bufRect.centerX(), centerY - bufRect.centerY()); 870 871 // Undo ScaleToFit.FILL done by the surface 872 mPreviewTranformationMatrix.setRectToRect(viewRect, bufRect, Matrix.ScaleToFit.FILL); 873 874 // Rotate buffer contents to proper orientation 875 mPreviewTranformationMatrix.postRotate(getPreviewOrientation(mDisplayRotation), 876 centerX, centerY); 877 878 // TODO: This is probably only working for the N5. Need to test 879 // on a device like N10 with different sensor orientation. 880 if ((mDisplayRotation % 180) == 90) { 881 int temp = effectiveWidth; 882 effectiveWidth = effectiveHeight; 883 effectiveHeight = temp; 884 } 885 886 boolean is16by9 = false; 887 888 // TODO: BACK/FRONT. 889 Size pictureSize = getPictureSizeFromSettings(); 890 if (pictureSize != null) { 891 pictureSize = ResolutionUtil.getApproximateSize(pictureSize); 892 if (pictureSize.equals(new Size(16, 9))) { 893 is16by9 = true; 894 } 895 } 896 897 float scale; 898 if (is16by9) { 899 // We are going to be clipping off edges to achieve the 16 900 // by 9 aspect ratio so we will choose the max here to fill, 901 // instead of fit. 902 scale = 903 Math.max(width / (float) effectiveWidth, height 904 / (float) effectiveHeight); 905 } else { 906 // Scale to fit view, cropping the longest dimension 907 scale = 908 Math.min(width / (float) effectiveWidth, height 909 / (float) effectiveHeight); 910 } 911 mPreviewTranformationMatrix.postScale(scale, scale, centerX, centerY); 912 913 // TODO: Take these quantities from mPreviewArea. 914 float previewWidth = effectiveWidth * scale; 915 float previewHeight = effectiveHeight * scale; 916 float previewCenterX = previewWidth / 2; 917 float previewCenterY = previewHeight / 2; 918 mPreviewTranformationMatrix.postTranslate(previewCenterX - centerX, previewCenterY 919 - centerY); 920 921 if (is16by9) { 922 float aspectRatio = FULLSCREEN_ASPECT_RATIO; 923 RectF renderedPreviewRect = mAppController.getFullscreenRect(); 924 float desiredPreviewWidth = Math.max(renderedPreviewRect.height(), 925 renderedPreviewRect.width()) * 1 / aspectRatio; 926 int letterBoxWidth = (int) Math.ceil((Math.min(renderedPreviewRect.width(), 927 renderedPreviewRect.height()) - desiredPreviewWidth) / 2.0f); 928 mAppController.getCameraAppUI().addLetterboxing(letterBoxWidth); 929 930 float wOffset = -(previewWidth - renderedPreviewRect.width()) / 2.0f; 931 float hOffset = -(previewHeight - renderedPreviewRect.height()) / 2.0f; 932 mPreviewTranformationMatrix.postTranslate(wOffset, hOffset); 933 mAppController.updatePreviewTransformFullscreen(mPreviewTranformationMatrix, 934 aspectRatio); 935 } else { 936 mAppController.updatePreviewTransform(mPreviewTranformationMatrix); 937 mAppController.getCameraAppUI().hideLetterboxing(); 938 } 939 // if (mGcamProxy != null) { 940 // mGcamProxy.postSetAspectRatio(mFinalAspectRatio); 941 // } 942 // mUI.updatePreviewAreaRect(new RectF(0, 0, previewWidth, 943 // previewHeight)); 944 945 // TODO: Add face detection. 946 // Characteristics info = 947 // mapp.getCameraProvider().getCharacteristics(0); 948 // mUI.setupFaceDetection(CameraUtil.getDisplayOrientation(incomingRotation, 949 // info), false); 950 // updateCamera2FaceBoundTransform(new 951 // RectF(mEffectiveCropRegion), 952 // new RectF(0, 0, mBufferWidth, mBufferHeight), 953 // new RectF(0, 0, previewWidth, previewHeight), getRotation()); 954 } 955 } 956 957 private void updateBufferDimension() { 958 if (mCamera == null) { 959 return; 960 } 961 962 Size picked = CaptureModuleUtil.pickBufferDimensions( 963 mCamera.getSupportedSizes(), 964 mCamera.getFullSizeAspectRatio(), 965 mContext); 966 mPreviewBufferWidth = picked.getWidth(); 967 mPreviewBufferHeight = picked.getHeight(); 968 } 969 970 /** 971 * Resets the default buffer size to the initially calculated size. 972 */ 973 private void resetDefaultBufferSize() { 974 synchronized (mSurfaceLock) { 975 if (mPreviewTexture != null) { 976 mPreviewTexture.setDefaultBufferSize(mPreviewBufferWidth, mPreviewBufferHeight); 977 } 978 } 979 } 980 981 private void closeCamera() { 982 if (mCamera != null) { 983 mCamera.setFocusStateListener(null); 984 mCamera.close(null); 985 mCamera = null; 986 } 987 } 988 989 private int getOrientation() { 990 if (mAppController.isAutoRotateScreen()) { 991 return mDisplayRotation; 992 } else { 993 return mOrientation; 994 } 995 } 996 997 /** 998 * @return Whether we are resuming from within the lockscreen. 999 */ 1000 private static boolean isResumeFromLockscreen(Activity activity) { 1001 String action = activity.getIntent().getAction(); 1002 return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action) 1003 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)); 1004 } 1005 1006 private void switchCamera(Facing switchTo) { 1007 if (mPaused || mCameraFacing == switchTo) { 1008 return; 1009 } 1010 // TODO: Un-comment once we have timer back. 1011 // cancelCountDown(); 1012 1013 mAppController.freezeScreenUntilPreviewReady(); 1014 1015 mCameraFacing = switchTo; 1016 initSurface(mPreviewTexture); 1017 1018 // TODO: Un-comment once we have focus back. 1019 // if (mFocusManager != null) { 1020 // mFocusManager.removeMessages(); 1021 // } 1022 // mFocusManager.setMirror(mMirror); 1023 } 1024 1025 private Size getPictureSizeFromSettings() { 1026 String pictureSizeKey = mCameraFacing == Facing.FRONT ? Keys.KEY_PICTURE_SIZE_FRONT 1027 : Keys.KEY_PICTURE_SIZE_BACK; 1028 return mSettingsManager.getSize(SettingsManager.SCOPE_GLOBAL, pictureSizeKey); 1029 } 1030 1031 private int getPreviewOrientation(int deviceOrientationDegrees) { 1032 // Important: Camera2 buffers are already rotated to the natural 1033 // orientation of the device (at least for the back-camera). 1034 1035 // TODO: Remove this hack for the front camera as soon as b/16637957 is 1036 // fixed. 1037 if (mCameraFacing == Facing.FRONT) { 1038 deviceOrientationDegrees += 180; 1039 } 1040 return (360 - deviceOrientationDegrees) % 360; 1041 } 1042 1043 /** 1044 * Returns which way around the camera is facing, based on it's ID. 1045 * <p> 1046 * TODO: This needs to change so that we store the direction directly in the 1047 * settings, rather than a Camera ID. 1048 */ 1049 private static Facing getFacingFromCameraId(int cameraId) { 1050 return cameraId == 1 ? Facing.FRONT : Facing.BACK; 1051 } 1052 1053 private void resetTextureBufferSize() { 1054 // Reset the default buffer sizes on the shared SurfaceTexture 1055 // so they are not scaled for gcam. 1056 // 1057 // According to the documentation for 1058 // SurfaceTexture.setDefaultBufferSize, 1059 // photo and video based image producers (presumably only Camera 1 api), 1060 // override this buffer size. Any module that uses egl to render to a 1061 // SurfaceTexture must have these buffer sizes reset manually. Otherwise 1062 // the SurfaceTexture cannot be transformed by matrix set on the 1063 // TextureView. 1064 if (mPreviewTexture != null) { 1065 mPreviewTexture.setDefaultBufferSize(mAppController.getCameraAppUI().getSurfaceWidth(), 1066 mAppController.getCameraAppUI().getSurfaceHeight()); 1067 } 1068 } 1069 1070 /** 1071 * @return The currently set Flash settings. Defaults to AUTO if the setting 1072 * could not be parsed. 1073 */ 1074 private Flash getFlashModeFromSettings() { 1075 String flashSetting = mSettingsManager.getString(mAppController.getCameraScope(), 1076 Keys.KEY_FLASH_MODE); 1077 try { 1078 return Flash.valueOf(flashSetting.toUpperCase()); 1079 } catch (IllegalArgumentException ex) { 1080 Log.w(TAG, "Could not parse Flash Setting. Defaulting to AUTO."); 1081 return Flash.AUTO; 1082 } 1083 } 1084} 1085