CaptureModule.java revision 987ee64612e2510004fdf08536746c87234d01c1
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.Matrix; 23import android.graphics.RectF; 24import android.graphics.SurfaceTexture; 25import android.hardware.Sensor; 26import android.hardware.SensorEvent; 27import android.hardware.SensorEventListener; 28import android.hardware.SensorManager; 29import android.location.Location; 30import android.net.Uri; 31import android.os.Handler; 32import android.os.HandlerThread; 33import android.os.SystemClock; 34import android.provider.MediaStore; 35import android.view.KeyEvent; 36import android.view.Surface; 37import android.view.TextureView; 38import android.view.View; 39import android.view.View.OnLayoutChangeListener; 40 41import com.android.camera.app.AppController; 42import com.android.camera.app.CameraAppUI; 43import com.android.camera.app.CameraAppUI.BottomBarUISpec; 44import com.android.camera.app.LocationManager; 45import com.android.camera.app.MediaSaver; 46import com.android.camera.burst.BurstFacade; 47import com.android.camera.burst.BurstFacadeFactory; 48import com.android.camera.burst.BurstReadyStateChangeListener; 49import com.android.camera.burst.ToastingBurstFacadeDecorator; 50import com.android.camera.burst.ToastingBurstFacadeDecorator.BurstToaster; 51import com.android.camera.debug.DebugPropertyHelper; 52import com.android.camera.debug.Log; 53import com.android.camera.debug.Log.Tag; 54import com.android.camera.gl.FrameDistributor.FrameConsumer; 55import com.android.camera.gl.FrameDistributorWrapper; 56import com.android.camera.gl.SurfaceTextureConsumer; 57import com.android.camera.hardware.HardwareSpec; 58import com.android.camera.module.ModuleController; 59import com.android.camera.one.OneCamera; 60import com.android.camera.one.OneCamera.AutoFocusState; 61import com.android.camera.one.OneCamera.CaptureReadyCallback; 62import com.android.camera.one.OneCamera.Facing; 63import com.android.camera.one.OneCamera.OpenCallback; 64import com.android.camera.one.OneCamera.PhotoCaptureParameters; 65import com.android.camera.one.OneCamera.PhotoCaptureParameters.Flash; 66import com.android.camera.one.OneCameraManager; 67import com.android.camera.one.v2.OneCameraManagerImpl; 68import com.android.camera.remote.RemoteCameraModule; 69import com.android.camera.session.CaptureSession; 70import com.android.camera.settings.Keys; 71import com.android.camera.settings.SettingsManager; 72import com.android.camera.ui.CountDownView; 73import com.android.camera.ui.PreviewStatusListener; 74import com.android.camera.ui.TouchCoordinate; 75import com.android.camera.ui.focus.FocusController; 76import com.android.camera.ui.focus.FocusSound; 77import com.android.camera.util.CameraUtil; 78import com.android.camera.util.GcamHelper; 79import com.android.camera.util.Size; 80import com.android.camera.util.UsageStatistics; 81import com.android.camera2.R; 82import com.android.ex.camera2.portability.CameraAgent.CameraProxy; 83 84import java.io.File; 85import java.io.IOException; 86import java.util.ArrayList; 87import java.util.List; 88import java.util.concurrent.Semaphore; 89import java.util.concurrent.TimeUnit; 90 91/** 92 * New Capture module that is made to support photo and video capture on top of 93 * the OneCamera API, to transparently support GCam. 94 * <p> 95 * This has been a re-write with pieces taken and improved from GCamModule and 96 * PhotoModule, which are to be retired eventually. 97 * <p> 98 */ 99public class CaptureModule extends CameraModule 100 implements MediaSaver.QueueListener, 101 ModuleController, 102 CountDownView.OnCountDownStatusListener, 103 OneCamera.PictureCallback, 104 OneCamera.FocusStateListener, 105 OneCamera.ReadyStateChangedListener, 106 PreviewStatusListener.PreviewAreaChangedListener, 107 RemoteCameraModule, 108 SensorEventListener, 109 SettingsManager.OnSettingChangedListener, 110 TextureView.SurfaceTextureListener { 111 112 /** 113 * Called on layout changes. 114 */ 115 private final OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() { 116 @Override 117 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 118 int oldTop, int oldRight, int oldBottom) { 119 int width = right - left; 120 int height = bottom - top; 121 mPreviewConsumer.setSize(width, height); 122 updatePreviewTransform(width, height, false); 123 } 124 }; 125 126 private static final Tag TAG = new Tag("CaptureModule"); 127 private static final String PHOTO_MODULE_STRING_ID = "PhotoModule"; 128 /** Enable additional debug output. */ 129 private static final boolean DEBUG = true; 130 131 /** Timeout for camera open/close operations. */ 132 private static final int CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS = 2500; 133 134 /** System Properties switch to enable debugging focus UI. */ 135 private static final boolean CAPTURE_DEBUG_UI = DebugPropertyHelper.showCaptureDebugUI(); 136 137 private final Object mDimensionLock = new Object(); 138 139 /** 140 * Sticky Gcam mode is when this module's sole purpose it to be the Gcam 141 * mode. If true, the device uses {@link PhotoModule} for normal picture 142 * taking. 143 */ 144 private final boolean mStickyGcamCamera; 145 146 /** Controller giving us access to other services. */ 147 private final AppController mAppController; 148 /** The applications settings manager. */ 149 private final SettingsManager mSettingsManager; 150 /** Application context. */ 151 private final Context mContext; 152 private CaptureModuleUI mUI; 153 /** The camera manager used to open cameras. */ 154 private OneCameraManager mCameraManager; 155 /** The currently opened camera device, or null if the camera is closed. */ 156 private OneCamera mCamera; 157 /** Held when opening or closing the camera. */ 158 private final Semaphore mCameraOpenCloseLock = new Semaphore(1); 159 /** The direction the currently opened camera is facing to. */ 160 private Facing mCameraFacing = Facing.BACK; 161 /** Whether HDR is currently enabled. */ 162 private boolean mHdrEnabled = false; 163 164 private FocusController mFocusController; 165 166 167 /** State by the module state machine. */ 168 private static enum ModuleState { 169 IDLE, 170 WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED, 171 UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE, 172 } 173 174 /** The current state of the module. */ 175 private ModuleState mState = ModuleState.IDLE; 176 /** Current zoom value. */ 177 private float mZoomValue = 1f; 178 /** Current duration of capture timer in seconds. */ 179 private int mTimerDuration; 180 // TODO: Get image capture intent UI working. 181 private boolean mIsImageCaptureIntent; 182 183 /** True if in AF tap-to-focus sequence. */ 184 private boolean mTapToFocusWaitForActiveScan = false; 185 /** Records beginning frame of each AF scan. */ 186 private long mAutoFocusScanStartFrame = -1; 187 /** Records beginning time of each AF scan in uptimeMillis. */ 188 private long mAutoFocusScanStartTime; 189 190 /** Persistence of Tap to Focus target UI after scan complete. */ 191 private static final int FOCUS_HOLD_UI_MILLIS = 0; 192 /** Worst case persistence of TTF target UI. */ 193 private static final int FOCUS_UI_TIMEOUT_MILLIS = 2000; 194 /** Results from last tap to focus scan */ 195 private boolean mFocusedAtEnd; 196 /** Sensor manager we use to get the heading of the device. */ 197 private SensorManager mSensorManager; 198 /** Accelerometer. */ 199 private Sensor mAccelerometerSensor; 200 /** Compass. */ 201 private Sensor mMagneticSensor; 202 203 /** Accelerometer data. */ 204 private final float[] mGData = new float[3]; 205 /** Magnetic sensor data. */ 206 private final float[] mMData = new float[3]; 207 /** Temporary rotation matrix. */ 208 private final float[] mR = new float[16]; 209 /** Current compass heading. */ 210 private int mHeading = -1; 211 212 /** Used to fetch and embed the location into captured images. */ 213 private final LocationManager mLocationManager; 214 /** Plays sounds for countdown timer. */ 215 private SoundPlayer mSoundPlayer; 216 217 /** Whether the module is paused right now. */ 218 private boolean mPaused; 219 220 /** Main thread handler. */ 221 private Handler mMainHandler; 222 /** Handler thread for camera-related operations. */ 223 private Handler mCameraHandler; 224 225 /** Current display rotation in degrees. */ 226 private int mDisplayRotation; 227 /** Current screen width in pixels. */ 228 private int mScreenWidth; 229 /** Current screen height in pixels. */ 230 private int mScreenHeight; 231 /** Current width of preview frames from camera. */ 232 private int mPreviewBufferWidth; 233 /** Current height of preview frames from camera.. */ 234 private int mPreviewBufferHeight; 235 /** Area used by preview. */ 236 RectF mPreviewArea; 237 238 /** The current preview transformation matrix. */ 239 private Matrix mPreviewTranformationMatrix = new Matrix(); 240 /** TODO: This is N5 specific. */ 241 public static final float FULLSCREEN_ASPECT_RATIO = 16 / 9f; 242 243 /** Used to distribute camera frames to consumers. */ 244 private final FrameDistributorWrapper mFrameDistributor; 245 246 /** The frame consumer that renders frames to the preview. */ 247 private final SurfaceTextureConsumer mPreviewConsumer; 248 249 /** The burst manager for controlling the burst. */ 250 private final BurstFacade mBurstController; 251 private static final String BURST_SESSIONS_DIR = "burst_sessions"; 252 253 public CaptureModule(AppController appController) { 254 this(appController, false); 255 } 256 257 /** Constructs a new capture module. */ 258 public CaptureModule(AppController appController, boolean stickyHdr) { 259 super(appController); 260 mAppController = appController; 261 mContext = mAppController.getAndroidContext(); 262 mSettingsManager = mAppController.getSettingsManager(); 263 mSettingsManager.addListener(this); 264 mStickyGcamCamera = stickyHdr; 265 mLocationManager = mAppController.getLocationManager(); 266 267 mPreviewConsumer = new SurfaceTextureConsumer(); 268 mFrameDistributor = new FrameDistributorWrapper(); 269 BurstFacade burstController = BurstFacadeFactory.create(mContext, mAppController 270 .getOrientationManager(), new BurstReadyStateChangeListener() { 271 @Override 272 public void onBurstReadyStateChanged(boolean ready) { 273 // TODO: This needs to take into account the state of the whole 274 // system, not just burst. 275 mAppController.setShutterEnabled(ready); 276 } 277 }); 278 BurstToaster toaster = new BurstToaster(appController.getAndroidContext()); 279 mBurstController = new ToastingBurstFacadeDecorator(burstController, toaster); 280 } 281 282 @Override 283 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) { 284 Log.d(TAG, "init"); 285 mMainHandler = new Handler(activity.getMainLooper()); 286 HandlerThread thread = new HandlerThread("CaptureModule.mCameraHandler"); 287 thread.start(); 288 mCameraHandler = new Handler(thread.getLooper()); 289 mCameraManager = mAppController.getCameraManager(); 290 mDisplayRotation = CameraUtil.getDisplayRotation(mContext); 291 mCameraFacing = getFacingFromCameraId(mSettingsManager.getInteger( 292 mAppController.getModuleScope(), 293 Keys.KEY_CAMERA_ID)); 294 mUI = new CaptureModuleUI(activity, this, mAppController.getModuleLayoutRoot(), 295 mLayoutListener); 296 mAppController.setPreviewStatusListener(mUI); 297 298 mSoundPlayer = new SoundPlayer(mContext); 299 FocusSound focusSound = new FocusSound(mSoundPlayer, R.raw.material_camera_focus); 300 mFocusController = new FocusController(mUI.getFocusRing(), focusSound, mMainHandler); 301 302 // Set the preview texture from UI for the SurfaceTextureConsumer. 303 mPreviewConsumer.setSurfaceTexture( 304 mAppController.getCameraAppUI().getSurfaceTexture(), 305 mAppController.getCameraAppUI().getSurfaceWidth(), 306 mAppController.getCameraAppUI().getSurfaceHeight()); 307 308 mSensorManager = (SensorManager) (mContext.getSystemService(Context.SENSOR_SERVICE)); 309 mAccelerometerSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 310 mMagneticSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); 311 312 313 String action = activity.getIntent().getAction(); 314 mIsImageCaptureIntent = (MediaStore.ACTION_IMAGE_CAPTURE.equals(action) 315 || CameraActivity.ACTION_IMAGE_CAPTURE_SECURE.equals(action)); 316 View cancelButton = activity.findViewById(R.id.shutter_cancel_button); 317 cancelButton.setOnClickListener(new View.OnClickListener() { 318 @Override 319 public void onClick(View view) { 320 cancelCountDown(); 321 } 322 }); 323 } 324 325 @Override 326 public void onShutterButtonLongPressed() { 327 File tempSessionDataDirectory; 328 try { 329 tempSessionDataDirectory = getServices().getCaptureSessionManager() 330 .getSessionDirectory(BURST_SESSIONS_DIR); 331 } catch (IOException e) { 332 Log.e(TAG, "Cannot start burst", e); 333 return; 334 } 335 CaptureSession session = createCaptureSession(); 336 mBurstController.startBurst(session, tempSessionDataDirectory); 337 } 338 339 @Override 340 public void onShutterButtonFocus(boolean pressed) { 341 if (!pressed) { 342 // the shutter button was released, stop any bursts. 343 mBurstController.stopBurst(); 344 } 345 } 346 347 @Override 348 public void onShutterCoordinate(TouchCoordinate coord) { 349 // TODO Auto-generated method stub 350 } 351 352 @Override 353 public void onShutterButtonClick() { 354 if (mCamera == null) { 355 return; 356 } 357 358 int countDownDuration = mSettingsManager 359 .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION); 360 mTimerDuration = countDownDuration; 361 if (countDownDuration > 0) { 362 // Start count down. 363 mAppController.getCameraAppUI().transitionToCancel(); 364 mAppController.getCameraAppUI().hideModeOptions(); 365 mUI.setCountdownFinishedListener(this); 366 mUI.startCountdown(countDownDuration); 367 // Will take picture later via listener callback. 368 } else { 369 takePictureNow(); 370 } 371 } 372 373 private void takePictureNow() { 374 CaptureSession session = createCaptureSession(); 375 int orientation = mAppController.getOrientationManager().getDeviceOrientation() 376 .getDegrees(); 377 // TODO: This should really not use getExternalCacheDir and instead use 378 // the SessionStorage API. Need to sync with gcam if that's OK. 379 PhotoCaptureParameters params = new PhotoCaptureParameters( 380 session.getTitle(), orientation, session.getLocation(), 381 mContext.getExternalCacheDir(), this, 382 mHeading, getFlashModeFromSettings(), mZoomValue, 0); 383 384 mCamera.takePicture(params, session); 385 } 386 387 private CaptureSession createCaptureSession() { 388 long sessionTime = getSessionTime(); 389 Location location = mLocationManager.getCurrentLocation(); 390 String title = CameraUtil.createJpegName(sessionTime); 391 return getServices().getCaptureSessionManager() 392 .createNewSession(title, sessionTime, location); 393 } 394 395 private long getSessionTime() { 396 // TODO: Replace with a mockable TimeProvider interface. 397 return System.currentTimeMillis(); 398 } 399 400 @Override 401 public void onCountDownFinished() { 402 mAppController.getCameraAppUI().transitionToCapture(); 403 mAppController.getCameraAppUI().showModeOptions(); 404 if (mPaused) { 405 return; 406 } 407 takePictureNow(); 408 } 409 410 @Override 411 public void onRemainingSecondsChanged(int remainingSeconds) { 412 if (remainingSeconds == 1) { 413 mSoundPlayer.play(R.raw.timer_final_second, 0.6f); 414 } else if (remainingSeconds == 2 || remainingSeconds == 3) { 415 mSoundPlayer.play(R.raw.timer_increment, 0.6f); 416 } 417 } 418 419 private void cancelCountDown() { 420 if (mUI.isCountingDown()) { 421 // Cancel on-going countdown. 422 mUI.cancelCountDown(); 423 } 424 mAppController.getCameraAppUI().showModeOptions(); 425 mAppController.getCameraAppUI().transitionToCapture(); 426 } 427 428 @Override 429 public void onQuickExpose() { 430 mMainHandler.post(new Runnable() { 431 @Override 432 public void run() { 433 // Starts the short version of the capture animation UI. 434 mAppController.startFlashAnimation(true); 435 } 436 }); 437 } 438 439 @Override 440 public void onPreviewAreaChanged(RectF previewArea) { 441 mPreviewArea = previewArea; 442 mUI.onPreviewAreaChanged(previewArea); 443 // mUI.updatePreviewAreaRect(previewArea); 444 mUI.positionProgressOverlay(previewArea); 445 } 446 447 @Override 448 public void onSensorChanged(SensorEvent event) { 449 // This is literally the same as the GCamModule implementation. 450 int type = event.sensor.getType(); 451 float[] data; 452 if (type == Sensor.TYPE_ACCELEROMETER) { 453 data = mGData; 454 } else if (type == Sensor.TYPE_MAGNETIC_FIELD) { 455 data = mMData; 456 } else { 457 Log.w(TAG, String.format("Unexpected sensor type %s", event.sensor.getName())); 458 return; 459 } 460 for (int i = 0; i < 3; i++) { 461 data[i] = event.values[i]; 462 } 463 float[] orientation = new float[3]; 464 SensorManager.getRotationMatrix(mR, null, mGData, mMData); 465 SensorManager.getOrientation(mR, orientation); 466 mHeading = (int) (orientation[0] * 180f / Math.PI) % 360; 467 468 if (mHeading < 0) { 469 mHeading += 360; 470 } 471 } 472 473 @Override 474 public void onAccuracyChanged(Sensor sensor, int accuracy) { 475 // TODO Auto-generated method stub 476 } 477 478 @Override 479 public void onQueueStatus(boolean full) { 480 // TODO Auto-generated method stub 481 } 482 483 @Override 484 public void onRemoteShutterPress() { 485 Log.d(TAG, "onRemoteShutterPress"); 486 // TODO: Check whether shutter is enabled. 487 takePictureNow(); 488 } 489 490 @Override 491 public void onSurfaceTextureAvailable(final SurfaceTexture surface, int width, int height) { 492 Log.d(TAG, "onSurfaceTextureAvailable"); 493 // Force to re-apply transform matrix here as a workaround for 494 // b/11168275 495 updatePreviewTransform(width, height, true); 496 initSurfaceTextureConsumer(surface, width, height); 497 } 498 499 private void initSurfaceTextureConsumer(SurfaceTexture surface, int width, int height) { 500 if (mPreviewConsumer.getSurfaceTexture() != surface) { 501 mPreviewConsumer.setSurfaceTexture(surface, width, height); 502 } else if (mPreviewConsumer.getWidth() != width 503 || mPreviewConsumer.getHeight() != height) { 504 mPreviewConsumer.setSize(width, height); 505 } 506 closeCamera(); 507 openCameraAndStartPreview(); 508 } 509 510 @Override 511 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 512 Log.d(TAG, "onSurfaceTextureSizeChanged"); 513 updateFrameDistributorBufferSize(); 514 } 515 516 @Override 517 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 518 Log.d(TAG, "onSurfaceTextureDestroyed"); 519 closeCamera(); 520 521 return true; 522 } 523 524 @Override 525 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 526 if (mState == ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE) { 527 Log.d(TAG, "onSurfaceTextureUpdated --> updatePreviewTransform"); 528 mState = ModuleState.IDLE; 529 CameraAppUI appUI = mAppController.getCameraAppUI(); 530 updatePreviewTransform(appUI.getSurfaceWidth(), appUI.getSurfaceHeight(), true); 531 } 532 } 533 534 private void initializeFrameDistributor() { 535 // Currently, there is only one consumer to FrameDistributor for 536 // rendering the frames to the preview texture. 537 List<FrameConsumer> frameConsumers = new ArrayList<FrameConsumer>(); 538 frameConsumers.add(mBurstController.getPreviewFrameConsumer()); 539 frameConsumers.add(mPreviewConsumer); 540 mFrameDistributor.start(frameConsumers); 541 } 542 543 private void updateFrameDistributorBufferSize() { 544 mFrameDistributor.updatePreviewBufferSize(mPreviewBufferWidth, mPreviewBufferHeight); 545 } 546 547 @Override 548 public String getModuleStringIdentifier() { 549 return PHOTO_MODULE_STRING_ID; 550 } 551 552 @Override 553 public void resume() { 554 mPaused = false; 555 mAppController.getCameraAppUI().onChangeCamera(); 556 mAppController.addPreviewAreaSizeChangedListener(this); 557 initializeFrameDistributor(); 558 updateFrameDistributorBufferSize(); 559 getServices().getRemoteShutterListener().onModuleReady(this); 560 // TODO: Check if we can really take a photo right now (memory, camera 561 // state, ... ). 562 mAppController.getCameraAppUI().enableModeOptions(); 563 mAppController.setShutterEnabled(true); 564 565 // Get events from the accelerometer and magnetic sensor. 566 if (mAccelerometerSensor != null) { 567 mSensorManager.registerListener(this, mAccelerometerSensor, 568 SensorManager.SENSOR_DELAY_NORMAL); 569 } 570 if (mMagneticSensor != null) { 571 mSensorManager.registerListener(this, mMagneticSensor, 572 SensorManager.SENSOR_DELAY_NORMAL); 573 } 574 mHdrEnabled = mStickyGcamCamera || mAppController.getSettingsManager().getInteger( 575 SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS) == 1; 576 577 // This means we are resuming with an existing preview texture. This 578 // means we will never get the onSurfaceTextureAvailable call. So we 579 // have to open the camera and start the preview here. 580 if (mPreviewConsumer.getSurfaceTexture() != null) { 581 initSurfaceTextureConsumer(mPreviewConsumer.getSurfaceTexture(), 582 mAppController.getCameraAppUI().getSurfaceWidth(), 583 mAppController.getCameraAppUI().getSurfaceHeight()); 584 } 585 586 mSoundPlayer.loadSound(R.raw.timer_final_second); 587 mSoundPlayer.loadSound(R.raw.timer_increment); 588 } 589 590 @Override 591 public void pause() { 592 mPaused = true; 593 getServices().getRemoteShutterListener().onModuleExit(); 594 mBurstController.stopBurst(); 595 cancelCountDown(); 596 closeCamera(); 597 resetTextureBufferSize(); 598 mFrameDistributor.close(); 599 mSoundPlayer.unloadSound(R.raw.timer_final_second); 600 mSoundPlayer.unloadSound(R.raw.timer_increment); 601 // Remove delayed resume trigger, if it hasn't been executed yet. 602 mMainHandler.removeCallbacksAndMessages(null); 603 604 // Unregister the sensors. 605 if (mAccelerometerSensor != null) { 606 mSensorManager.unregisterListener(this, mAccelerometerSensor); 607 } 608 if (mMagneticSensor != null) { 609 mSensorManager.unregisterListener(this, mMagneticSensor); 610 } 611 } 612 613 @Override 614 public void destroy() { 615 mSoundPlayer.release(); 616 mCameraHandler.getLooper().quitSafely(); 617 } 618 619 @Override 620 public void onLayoutOrientationChanged(boolean isLandscape) { 621 Log.d(TAG, "onLayoutOrientationChanged"); 622 mBurstController.stopBurst(); 623 } 624 625 @Override 626 public void onCameraAvailable(CameraProxy cameraProxy) { 627 // Ignore since we manage the camera ourselves until we remove this. 628 } 629 630 @Override 631 public void hardResetSettings(SettingsManager settingsManager) { 632 if (mStickyGcamCamera) { 633 // Sitcky HDR+ mode should hard reset HDR+ to on, and camera back 634 // facing. 635 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, true); 636 settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID, 637 getBackFacingCameraId()); 638 } 639 } 640 641 @Override 642 public HardwareSpec getHardwareSpec() { 643 return new HardwareSpec() { 644 @Override 645 public boolean isFrontCameraSupported() { 646 return true; 647 } 648 649 @Override 650 public boolean isHdrSupported() { 651 // TODO: Check if the device has HDR and not HDR+. 652 return false; 653 } 654 655 @Override 656 public boolean isHdrPlusSupported() { 657 return GcamHelper.hasGcamCapture(); 658 } 659 660 @Override 661 public boolean isFlashSupported() { 662 return true; 663 } 664 }; 665 } 666 667 @Override 668 public BottomBarUISpec getBottomBarSpec() { 669 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec(); 670 bottomBarSpec.enableGridLines = true; 671 bottomBarSpec.enableCamera = true; 672 bottomBarSpec.cameraCallback = getCameraCallback(); 673 bottomBarSpec.enableHdr = GcamHelper.hasGcamCapture(); 674 bottomBarSpec.hdrCallback = getHdrButtonCallback(); 675 bottomBarSpec.enableSelfTimer = true; 676 bottomBarSpec.showSelfTimer = true; 677 if (!mHdrEnabled) { 678 bottomBarSpec.enableFlash = true; 679 } 680 // Added to handle case of CaptureModule being used only for Gcam. 681 if (mStickyGcamCamera) { 682 bottomBarSpec.enableFlash = false; 683 } 684 return bottomBarSpec; 685 } 686 687 @Override 688 public boolean isUsingBottomBar() { 689 return true; 690 } 691 692 @Override 693 public boolean onKeyDown(int keyCode, KeyEvent event) { 694 switch (keyCode) { 695 case KeyEvent.KEYCODE_CAMERA: 696 case KeyEvent.KEYCODE_DPAD_CENTER: 697 if (mUI.isCountingDown()) { 698 cancelCountDown(); 699 } else if (event.getRepeatCount() == 0) { 700 onShutterButtonClick(); 701 } 702 return true; 703 case KeyEvent.KEYCODE_VOLUME_UP: 704 case KeyEvent.KEYCODE_VOLUME_DOWN: 705 // Prevent default. 706 return true; 707 } 708 return false; 709 } 710 711 @Override 712 public boolean onKeyUp(int keyCode, KeyEvent event) { 713 switch (keyCode) { 714 case KeyEvent.KEYCODE_VOLUME_UP: 715 case KeyEvent.KEYCODE_VOLUME_DOWN: 716 onShutterButtonClick(); 717 return true; 718 } 719 return false; 720 } 721 722 /** 723 * Focus sequence starts for zone around tap location for single tap. 724 */ 725 @Override 726 public void onSingleTapUp(View view, int viewX, int viewY) { 727 Log.v(TAG, "onSingleTapUp x=" + viewX + " y=" + viewY); 728 // TODO: This should query actual capability. 729 if (mCameraFacing == Facing.FRONT) { 730 return; 731 } 732 startActiveFocusAt(viewX, viewY); 733 } 734 735 // TODO: Consider refactoring FocusOverlayManager. 736 // Currently AF state transitions are controlled in OneCameraImpl. 737 // PhotoModule uses FocusOverlayManager which uses API1/portability 738 // logic and coordinates. 739 private void startActiveFocusAt(int viewX, int viewY) { 740 if (mCamera == null) { 741 // If we receive this after the camera is closed, do nothing. 742 return; 743 } 744 745 // TODO: make mFocusController final and remove null check. 746 if (mFocusController == null) { 747 Log.v(TAG, "CaptureModule mFocusController is null!"); 748 return; 749 } 750 mFocusController.showActiveFocusAt(viewX, viewY); 751 752 // Normalize coordinates to [0,1] per CameraOne API. 753 float points[] = new float[2]; 754 points[0] = (viewX - mPreviewArea.left) / mPreviewArea.width(); 755 points[1] = (viewY - mPreviewArea.top) / mPreviewArea.height(); 756 757 // Rotate coordinates to portrait orientation per CameraOne API. 758 Matrix rotationMatrix = new Matrix(); 759 rotationMatrix.setRotate(mDisplayRotation, 0.5f, 0.5f); 760 rotationMatrix.mapPoints(points); 761 mCamera.triggerFocusAndMeterAtPoint(points[0], points[1]); 762 763 // Log touch (screen coordinates). 764 if (mZoomValue == 1f) { 765 TouchCoordinate touchCoordinate = new TouchCoordinate( 766 viewX - mPreviewArea.left, 767 viewY - mPreviewArea.top, 768 mPreviewArea.width(), 769 mPreviewArea.height()); 770 // TODO: Add to logging: duration, rotation. 771 UsageStatistics.instance().tapToFocus(touchCoordinate, null); 772 } 773 } 774 775 /** 776 * Show AF target in center of preview. 777 */ 778 private void startPassiveFocus() { 779 // TODO: make mFocusController final and remove null check. 780 if (mFocusController == null) { 781 return; 782 } 783 784 // TODO: Some passive focus scans may trigger on a location 785 // instead of the center of the screen. 786 mFocusController.showPassiveFocusAt( 787 (int) (mPreviewArea.width() / 2.0f), 788 (int) (mPreviewArea.height() / 2.0f)); 789 } 790 791 /** 792 * Update UI based on AF state changes. 793 */ 794 @Override 795 public void onFocusStatusUpdate(final AutoFocusState state, long frameNumber) { 796 Log.v(TAG, "AF status is state:" + state); 797 798 switch (state) { 799 case PASSIVE_SCAN: 800 startPassiveFocus(); 801 break; 802 case ACTIVE_SCAN: 803 break; 804 case PASSIVE_FOCUSED: 805 case PASSIVE_UNFOCUSED: 806 mFocusController.clearFocusIndicator(); 807 break; 808 case ACTIVE_FOCUSED: 809 case ACTIVE_UNFOCUSED: 810 mFocusController.clearFocusIndicator(); 811 break; 812 } 813 814 if (CAPTURE_DEBUG_UI) { 815 measureAutoFocusScans(state, frameNumber); 816 } 817 } 818 819 private void measureAutoFocusScans(final AutoFocusState state, long frameNumber) { 820 // Log AF scan lengths. 821 boolean passive = false; 822 switch (state) { 823 case PASSIVE_SCAN: 824 case ACTIVE_SCAN: 825 if (mAutoFocusScanStartFrame == -1) { 826 mAutoFocusScanStartFrame = frameNumber; 827 mAutoFocusScanStartTime = SystemClock.uptimeMillis(); 828 } 829 break; 830 case PASSIVE_FOCUSED: 831 case PASSIVE_UNFOCUSED: 832 passive = true; 833 case ACTIVE_FOCUSED: 834 case ACTIVE_UNFOCUSED: 835 if (mAutoFocusScanStartFrame != -1) { 836 long frames = frameNumber - mAutoFocusScanStartFrame; 837 long dt = SystemClock.uptimeMillis() - mAutoFocusScanStartTime; 838 int fps = Math.round(frames * 1000f / dt); 839 String report = String.format("%s scan: fps=%d frames=%d", 840 passive ? "CAF" : "AF", fps, frames); 841 Log.v(TAG, report); 842 mUI.showDebugMessage(String.format("%d / %d", frames, fps)); 843 mAutoFocusScanStartFrame = -1; 844 } 845 break; 846 } 847 } 848 849 @Override 850 public void onReadyStateChanged(boolean readyForCapture) { 851 if (!mBurstController.isReady()) { 852 return; 853 } 854 855 if (readyForCapture) { 856 mAppController.getCameraAppUI().enableModeOptions(); 857 } 858 mAppController.setShutterEnabled(readyForCapture); 859 } 860 861 @Override 862 public String getPeekAccessibilityString() { 863 return mAppController.getAndroidContext() 864 .getResources().getString(R.string.photo_accessibility_peek); 865 } 866 867 @Override 868 public void onThumbnailResult(byte[] jpegData) { 869 getServices().getRemoteShutterListener().onPictureTaken(jpegData); 870 } 871 872 @Override 873 public void onPictureTaken(CaptureSession session) { 874 mAppController.getCameraAppUI().enableModeOptions(); 875 } 876 877 @Override 878 public void onPictureSaved(Uri uri) { 879 mAppController.notifyNewMedia(uri); 880 } 881 882 @Override 883 public void onTakePictureProgress(float progress) { 884 mUI.setPictureTakingProgress((int) (progress * 100)); 885 } 886 887 @Override 888 public void onPictureTakingFailed() { 889 } 890 891 @Override 892 public void onSettingChanged(SettingsManager settingsManager, String key) { 893 // TODO Auto-generated method stub 894 } 895 896 /** 897 * Updates the preview transform matrix to adapt to the current preview 898 * width, height, and orientation. 899 */ 900 public void updatePreviewTransform() { 901 int width; 902 int height; 903 synchronized (mDimensionLock) { 904 width = mScreenWidth; 905 height = mScreenHeight; 906 } 907 updatePreviewTransform(width, height); 908 } 909 910 /** 911 * Set zoom value. 912 * 913 * @param zoom Zoom value, must be between 1.0 and mCamera.getMaxZoom(). 914 */ 915 public void setZoom(float zoom) { 916 mZoomValue = zoom; 917 if (mCamera != null) { 918 mCamera.setZoom(zoom); 919 } 920 } 921 922 /** 923 * TODO: Remove this method once we are in pure CaptureModule land. 924 */ 925 private String getBackFacingCameraId() { 926 if (!(mCameraManager instanceof OneCameraManagerImpl)) { 927 throw new IllegalStateException("This should never be called with Camera API V1"); 928 } 929 OneCameraManagerImpl manager = (OneCameraManagerImpl) mCameraManager; 930 return manager.getFirstBackCameraId(); 931 } 932 933 /** 934 * @return Depending on whether we're in sticky-HDR mode or not, return the 935 * proper callback to be used for when the HDR/HDR+ button is 936 * pressed. 937 */ 938 private ButtonManager.ButtonCallback getHdrButtonCallback() { 939 if (mStickyGcamCamera) { 940 return new ButtonManager.ButtonCallback() { 941 @Override 942 public void onStateChanged(int state) { 943 if (mPaused) { 944 return; 945 } 946 if (state == ButtonManager.ON) { 947 throw new IllegalStateException( 948 "Can't leave hdr plus mode if switching to hdr plus mode."); 949 } 950 SettingsManager settingsManager = mAppController.getSettingsManager(); 951 settingsManager.set(mAppController.getModuleScope(), 952 Keys.KEY_REQUEST_RETURN_HDR_PLUS, false); 953 switchToRegularCapture(); 954 } 955 }; 956 } else { 957 return new ButtonManager.ButtonCallback() { 958 @Override 959 public void onStateChanged(int hdrEnabled) { 960 if (mPaused) { 961 return; 962 } 963 Log.d(TAG, "HDR enabled =" + hdrEnabled); 964 mHdrEnabled = hdrEnabled == 1; 965 switchCamera(); 966 } 967 }; 968 } 969 } 970 971 /** 972 * @return Depending on whether we're in sticky-HDR mode or not, this 973 * returns the proper callback to be used for when the camera 974 * (front/back switch) button is pressed. 975 */ 976 private ButtonManager.ButtonCallback getCameraCallback() { 977 if (mStickyGcamCamera) { 978 return new ButtonManager.ButtonCallback() { 979 @Override 980 public void onStateChanged(int state) { 981 if (mPaused) { 982 return; 983 } 984 985 // At the time this callback is fired, the camera id setting 986 // has changed to the desired camera. 987 SettingsManager settingsManager = mAppController.getSettingsManager(); 988 if (Keys.isCameraBackFacing(settingsManager, 989 mAppController.getModuleScope())) { 990 throw new IllegalStateException( 991 "Hdr plus should never be switching from front facing camera."); 992 } 993 994 // Switch to photo mode, but request a return to hdr plus on 995 // switching to back camera again. 996 settingsManager.set(mAppController.getModuleScope(), 997 Keys.KEY_REQUEST_RETURN_HDR_PLUS, true); 998 switchToRegularCapture(); 999 } 1000 }; 1001 } else { 1002 return new ButtonManager.ButtonCallback() { 1003 @Override 1004 public void onStateChanged(int cameraId) { 1005 if (mPaused) { 1006 return; 1007 } 1008 1009 // At the time this callback is fired, the camera id 1010 // has be set to the desired camera. 1011 mSettingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID, 1012 cameraId); 1013 1014 Log.d(TAG, "Start to switch camera. cameraId=" + cameraId); 1015 mCameraFacing = getFacingFromCameraId(cameraId); 1016 switchCamera(); 1017 } 1018 }; 1019 } 1020 } 1021 1022 /** 1023 * Switches to PhotoModule to do regular photo captures. 1024 * <p> 1025 * TODO: Remove this once we use CaptureModule for photo taking. 1026 */ 1027 private void switchToRegularCapture() { 1028 // Turn off HDR+ before switching back to normal photo mode. 1029 SettingsManager settingsManager = mAppController.getSettingsManager(); 1030 settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false); 1031 1032 // Disable this button to prevent callbacks from this module from firing 1033 // while we are transitioning modules. 1034 ButtonManager buttonManager = mAppController.getButtonManager(); 1035 buttonManager.disableButtonClick(ButtonManager.BUTTON_HDR_PLUS); 1036 mAppController.getCameraAppUI().freezeScreenUntilPreviewReady(); 1037 mAppController.onModeSelected(mContext.getResources().getInteger( 1038 R.integer.camera_mode_photo)); 1039 buttonManager.enableButtonClick(ButtonManager.BUTTON_HDR_PLUS); 1040 } 1041 1042 /** 1043 * Called when the preview started. Informs the app controller and queues a 1044 * transform update when the next preview frame arrives. 1045 */ 1046 private void onPreviewStarted() { 1047 if (mState == ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED) { 1048 mState = ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE; 1049 } 1050 mAppController.onPreviewStarted(); 1051 onReadyStateChanged(true); 1052 } 1053 1054 /** 1055 * Update the preview transform based on the new dimensions. Will not force 1056 * an update, if it's not necessary. 1057 */ 1058 private void updatePreviewTransform(int incomingWidth, int incomingHeight) { 1059 updatePreviewTransform(incomingWidth, incomingHeight, false); 1060 } 1061 1062 /*** 1063 * Update the preview transform based on the new dimensions. TODO: Make work 1064 * with all: aspect ratios/resolutions x screens/cameras. 1065 */ 1066 private void updatePreviewTransform(int incomingWidth, int incomingHeight, 1067 boolean forceUpdate) { 1068 Log.d(TAG, "updatePreviewTransform: " + incomingWidth + " x " + incomingHeight); 1069 1070 synchronized (mDimensionLock) { 1071 int incomingRotation = CameraUtil 1072 .getDisplayRotation(mContext); 1073 // Check for an actual change: 1074 if (mScreenHeight == incomingHeight && mScreenWidth == incomingWidth && 1075 incomingRotation == mDisplayRotation && !forceUpdate) { 1076 return; 1077 } 1078 // Update display rotation and dimensions 1079 mDisplayRotation = incomingRotation; 1080 mScreenWidth = incomingWidth; 1081 mScreenHeight = incomingHeight; 1082 updatePreviewBufferDimension(); 1083 1084 mPreviewTranformationMatrix = mAppController.getCameraAppUI().getPreviewTransform( 1085 mPreviewTranformationMatrix); 1086 int width = mScreenWidth; 1087 int height = mScreenHeight; 1088 1089 // Assumptions: 1090 // - Aspect ratio for the sensor buffers is in landscape 1091 // orientation, 1092 // - Dimensions of buffers received are rotated to the natural 1093 // device orientation. 1094 // - The contents of each buffer are rotated by the inverse of 1095 // the display rotation. 1096 // - Surface scales the buffer to fit the current view bounds. 1097 1098 // Get natural orientation and buffer dimensions 1099 int naturalOrientation = CaptureModuleUtil 1100 .getDeviceNaturalOrientation(mContext); 1101 int effectiveWidth = mPreviewBufferWidth; 1102 int effectiveHeight = mPreviewBufferHeight; 1103 1104 if (DEBUG) { 1105 Log.v(TAG, "Rotation: " + mDisplayRotation); 1106 Log.v(TAG, "Screen Width: " + mScreenWidth); 1107 Log.v(TAG, "Screen Height: " + mScreenHeight); 1108 Log.v(TAG, "Buffer width: " + mPreviewBufferWidth); 1109 Log.v(TAG, "Buffer height: " + mPreviewBufferHeight); 1110 Log.v(TAG, "Natural orientation: " + naturalOrientation); 1111 } 1112 1113 // If natural orientation is portrait, rotate the buffer 1114 // dimensions 1115 if (naturalOrientation == Configuration.ORIENTATION_PORTRAIT) { 1116 int temp = effectiveWidth; 1117 effectiveWidth = effectiveHeight; 1118 effectiveHeight = temp; 1119 } 1120 1121 // Find and center view rect and buffer rect 1122 RectF viewRect = new RectF(0, 0, width, height); 1123 RectF bufRect = new RectF(0, 0, effectiveWidth, effectiveHeight); 1124 float centerX = viewRect.centerX(); 1125 float centerY = viewRect.centerY(); 1126 bufRect.offset(centerX - bufRect.centerX(), centerY - bufRect.centerY()); 1127 1128 // Undo ScaleToFit.FILL done by the surface 1129 mPreviewTranformationMatrix.setRectToRect(viewRect, bufRect, Matrix.ScaleToFit.FILL); 1130 1131 // Rotate buffer contents to proper orientation 1132 mPreviewTranformationMatrix.postRotate(getPreviewOrientation(mDisplayRotation), 1133 centerX, centerY); 1134 1135 // TODO: This is probably only working for the N5. Need to test 1136 // on a device like N10 with different sensor orientation. 1137 if ((mDisplayRotation % 180) == 90) { 1138 int temp = effectiveWidth; 1139 effectiveWidth = effectiveHeight; 1140 effectiveHeight = temp; 1141 } 1142 1143 // Scale to fit view, cropping the longest dimension 1144 float scale = 1145 Math.min(width / (float) effectiveWidth, height 1146 / (float) effectiveHeight); 1147 mPreviewTranformationMatrix.postScale(scale, scale, centerX, centerY); 1148 1149 // TODO: Take these quantities from mPreviewArea. 1150 float previewWidth = effectiveWidth * scale; 1151 float previewHeight = effectiveHeight * scale; 1152 float previewCenterX = previewWidth / 2; 1153 float previewCenterY = previewHeight / 2; 1154 mPreviewTranformationMatrix.postTranslate(previewCenterX - centerX, previewCenterY 1155 - centerY); 1156 1157 mAppController.updatePreviewTransform(mPreviewTranformationMatrix); 1158 // if (mGcamProxy != null) { 1159 // mGcamProxy.postSetAspectRatio(mFinalAspectRatio); 1160 // } 1161 // mUI.updatePreviewAreaRect(new RectF(0, 0, previewWidth, 1162 // previewHeight)); 1163 1164 // TODO: Add face detection. 1165 // Characteristics info = 1166 // mapp.getCameraProvider().getCharacteristics(0); 1167 // mUI.setupFaceDetection(CameraUtil.getDisplayOrientation(incomingRotation, 1168 // info), false); 1169 // updateCamera2FaceBoundTransform(new 1170 // RectF(mEffectiveCropRegion), 1171 // new RectF(0, 0, mBufferWidth, mBufferHeight), 1172 // new RectF(0, 0, previewWidth, previewHeight), getRotation()); 1173 } 1174 } 1175 1176 /** 1177 * Based on the current picture size, selects the best preview dimension and 1178 * stores it in {@link #mPreviewBufferWidth} and 1179 * {@link #mPreviewBufferHeight}. 1180 */ 1181 private void updatePreviewBufferDimension() { 1182 if (mCamera == null) { 1183 return; 1184 } 1185 1186 Size pictureSize = getPictureSizeFromSettings(); 1187 Size previewBufferSize = mCamera.pickPreviewSize(pictureSize, mContext); 1188 mPreviewBufferWidth = previewBufferSize.getWidth(); 1189 mPreviewBufferHeight = previewBufferSize.getHeight(); 1190 updateFrameDistributorBufferSize(); 1191 } 1192 1193 /** 1194 * Open camera and start the preview. 1195 */ 1196 private void openCameraAndStartPreview() { 1197 // Only enable HDR on the back camera 1198 boolean useHdr = mHdrEnabled && mCameraFacing == Facing.BACK; 1199 1200 try { 1201 // TODO Given the current design, we cannot guarantee that one of 1202 // CaptureReadyCallback.onSetupFailed or onReadyForCapture will 1203 // be called (see below), so it's possible that 1204 // mCameraOpenCloseLock.release() is never called under extremely 1205 // rare cases. If we leak the lock, this timeout ensures that we at 1206 // least crash so we don't deadlock the app. 1207 if (!mCameraOpenCloseLock.tryAcquire(CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { 1208 throw new RuntimeException("Time out waiting to acquire camera-open lock."); 1209 } 1210 } catch (InterruptedException e) { 1211 throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e); 1212 } 1213 if (mCamera != null) { 1214 // If the camera is already open, do nothing. 1215 Log.d(TAG, "Camera already open, not re-opening."); 1216 mCameraOpenCloseLock.release(); 1217 return; 1218 } 1219 mCameraManager.open(mCameraFacing, useHdr, getPictureSizeFromSettings(), 1220 new OpenCallback() { 1221 @Override 1222 public void onFailure() { 1223 Log.e(TAG, "Could not open camera."); 1224 mCamera = null; 1225 mCameraOpenCloseLock.release(); 1226 mAppController.showErrorAndFinish(R.string.cannot_connect_camera); 1227 } 1228 1229 @Override 1230 public void onCameraClosed() { 1231 mCamera = null; 1232 mBurstController.onCameraDetached(); 1233 mCameraOpenCloseLock.release(); 1234 } 1235 1236 @Override 1237 public void onCameraOpened(final OneCamera camera) { 1238 Log.d(TAG, "onCameraOpened: " + camera); 1239 mCamera = camera; 1240 mBurstController.onCameraAttached(mCamera); 1241 updatePreviewBufferDimension(); 1242 1243 // If the surface texture is not destroyed, it may have 1244 // the last frame lingering. We need to hold off setting 1245 // transform until preview is started. 1246 updateFrameDistributorBufferSize(); 1247 mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED; 1248 Log.d(TAG, "starting preview ..."); 1249 1250 1251 // TODO: make mFocusController final and remove null check. 1252 if (mFocusController != null) { 1253 camera.setFocusDistanceListener(mFocusController); 1254 } 1255 1256 // TODO: Consider rolling these two calls into one. 1257 camera.startPreview(new Surface(mFrameDistributor.getInputSurfaceTexture()), 1258 new CaptureReadyCallback() { 1259 @Override 1260 public void onSetupFailed() { 1261 // We must release this lock here, before posting 1262 // to the main handler since we may be blocked 1263 // in pause(), getting ready to close the camera. 1264 mCameraOpenCloseLock.release(); 1265 Log.e(TAG, "Could not set up preview."); 1266 mMainHandler.post(new Runnable() { 1267 @Override 1268 public void run() { 1269 if (mCamera == null) { 1270 Log.d(TAG, "Camera closed, aborting."); 1271 return; 1272 } 1273 mCamera.close(null); 1274 mCamera = null; 1275 // TODO: Show an error message and exit. 1276 } 1277 }); 1278 } 1279 1280 @Override 1281 public void onReadyForCapture() { 1282 // We must release this lock here, before posting 1283 // to the main handler since we may be blocked 1284 // in pause(), getting ready to close the camera. 1285 mCameraOpenCloseLock.release(); 1286 mMainHandler.post(new Runnable() { 1287 @Override 1288 public void run() { 1289 Log.d(TAG, "Ready for capture."); 1290 if (mCamera == null) { 1291 Log.d(TAG, "Camera closed, aborting."); 1292 return; 1293 } 1294 onPreviewStarted(); 1295 // Enable zooming after preview has 1296 // started. 1297 mUI.initializeZoom(mCamera.getMaxZoom()); 1298 mCamera.setFocusStateListener(CaptureModule.this); 1299 mCamera.setReadyStateChangedListener(CaptureModule.this); 1300 } 1301 }); 1302 } 1303 }); 1304 } 1305 }, mCameraHandler); 1306 } 1307 1308 private void closeCamera() { 1309 try { 1310 mCameraOpenCloseLock.acquire(); 1311 } catch (InterruptedException e) { 1312 throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e); 1313 } 1314 try { 1315 if (mCamera != null) { 1316 mCamera.close(null); 1317 mCamera.setFocusStateListener(null); 1318 mCamera = null; 1319 } 1320 } finally { 1321 mCameraOpenCloseLock.release(); 1322 } 1323 } 1324 1325 /** 1326 * @return Whether we are resuming from within the lockscreen. 1327 */ 1328 private static boolean isResumeFromLockscreen(Activity activity) { 1329 String action = activity.getIntent().getAction(); 1330 return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action) 1331 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)); 1332 } 1333 1334 /** 1335 * Re-initialize the camera if e.g. the HDR mode or facing property changed. 1336 */ 1337 private void switchCamera() { 1338 if (mPaused) { 1339 return; 1340 } 1341 cancelCountDown(); 1342 mAppController.freezeScreenUntilPreviewReady(); 1343 initSurfaceTextureConsumer(mPreviewConsumer.getSurfaceTexture(), 1344 mAppController.getCameraAppUI().getSurfaceWidth(), 1345 mAppController.getCameraAppUI().getSurfaceHeight()); 1346 1347 // TODO: Un-comment once we have focus back. 1348 // if (mFocusManager != null) { 1349 // mFocusManager.removeMessages(); 1350 // } 1351 // mFocusManager.setMirror(mMirror); 1352 } 1353 1354 private Size getPictureSizeFromSettings() { 1355 String pictureSizeKey = mCameraFacing == Facing.FRONT ? Keys.KEY_PICTURE_SIZE_FRONT 1356 : Keys.KEY_PICTURE_SIZE_BACK; 1357 return mSettingsManager.getSize(SettingsManager.SCOPE_GLOBAL, pictureSizeKey); 1358 } 1359 1360 private int getPreviewOrientation(int deviceOrientationDegrees) { 1361 // Important: Camera2 buffers are already rotated to the natural 1362 // orientation of the device (at least for the back-camera). 1363 1364 return (360 - deviceOrientationDegrees) % 360; 1365 } 1366 1367 /** 1368 * Returns which way around the camera is facing, based on it's ID. 1369 * <p> 1370 * TODO: This needs to change so that we store the direction directly in the 1371 * settings, rather than a Camera ID. 1372 */ 1373 private static Facing getFacingFromCameraId(int cameraId) { 1374 return cameraId == 1 ? Facing.FRONT : Facing.BACK; 1375 } 1376 1377 private void resetTextureBufferSize() { 1378 // According to the documentation for 1379 // SurfaceTexture.setDefaultBufferSize, 1380 // photo and video based image producers (presumably only Camera 1 api), 1381 // override this buffer size. Any module that uses egl to render to a 1382 // SurfaceTexture must have these buffer sizes reset manually. Otherwise 1383 // the SurfaceTexture cannot be transformed by matrix set on the 1384 // TextureView. 1385 updateFrameDistributorBufferSize(); 1386 } 1387 1388 /** 1389 * @return The currently set Flash settings. Defaults to AUTO if the setting 1390 * could not be parsed. 1391 */ 1392 private Flash getFlashModeFromSettings() { 1393 String flashSetting = mSettingsManager.getString(mAppController.getCameraScope(), 1394 Keys.KEY_FLASH_MODE); 1395 try { 1396 return Flash.valueOf(flashSetting.toUpperCase()); 1397 } catch (IllegalArgumentException ex) { 1398 Log.w(TAG, "Could not parse Flash Setting. Defaulting to AUTO."); 1399 return Flash.AUTO; 1400 } 1401 } 1402} 1403