CaptureModule.java revision 73ec9851f8e92968ac50193916526afd8f1343fc
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.ContentResolver; 21import android.content.Context; 22import android.content.res.Configuration; 23import android.graphics.Bitmap; 24import android.graphics.Matrix; 25import android.graphics.RectF; 26import android.graphics.SurfaceTexture; 27import android.hardware.Sensor; 28import android.hardware.SensorEvent; 29import android.hardware.SensorEventListener; 30import android.hardware.SensorManager; 31import android.net.Uri; 32import android.os.Handler; 33import android.provider.MediaStore; 34import android.util.Size; 35import android.view.KeyEvent; 36import android.view.OrientationEventListener; 37import android.view.Surface; 38import android.view.TextureView; 39import android.view.View; 40import android.view.View.OnLayoutChangeListener; 41 42import com.android.camera.app.AppController; 43import com.android.camera.app.CameraAppUI; 44import com.android.camera.app.CameraAppUI.BottomBarUISpec; 45import com.android.camera.app.MediaSaver; 46import com.android.camera.debug.Log; 47import com.android.camera.debug.Log.Tag; 48import com.android.camera.exif.ExifInterface; 49import com.android.camera.exif.ExifTag; 50import com.android.camera.exif.Rational; 51import com.android.camera.hardware.HardwareSpec; 52import com.android.camera.module.ModuleController; 53import com.android.camera.one.OneCamera; 54import com.android.camera.one.OneCamera.CaptureReadyCallback; 55import com.android.camera.one.OneCamera.Facing; 56import com.android.camera.one.OneCamera.OpenCallback; 57import com.android.camera.one.OneCamera.PhotoCaptureParameters; 58import com.android.camera.one.OneCameraManager; 59import com.android.camera.remote.RemoteCameraModule; 60import com.android.camera.session.CaptureSession; 61import com.android.camera.settings.Keys; 62import com.android.camera.settings.ResolutionUtil; 63import com.android.camera.settings.SettingsManager; 64import com.android.camera.ui.PreviewStatusListener; 65import com.android.camera.ui.TouchCoordinate; 66import com.android.camera.util.CameraUtil; 67import com.android.camera2.R; 68import com.android.ex.camera2.portability.CameraAgent.CameraProxy; 69 70import java.io.IOException; 71 72/** 73 * New Capture module that is made to support photo and video capture on top of 74 * the OneCamera API, to transparently support GCam. 75 * <p> 76 * This has been a re-write with pieces taken and improved from GCamModule and 77 * PhotoModule, which are to be retired eventually. 78 * <p> 79 * TODO: 80 * <ul> 81 * <li>Server-side logging 82 * <li>Focusing 83 * <li>Show location dialog 84 * <li>Show resolution dialog on certain devices 85 * <li>Store location 86 * <li>Timer 87 * </ul> 88 */ 89public class CaptureModule extends CameraModule implements ModuleController, 90 TextureView.SurfaceTextureListener, RemoteCameraModule, 91 MediaSaver.QueueListener, SensorEventListener, 92 PreviewStatusListener.PreviewAreaChangedListener, 93 OneCamera.PictureCallback { 94 95 /** 96 * Called on layout changes. 97 */ 98 private final OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() { 99 @Override 100 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 101 int oldTop, int oldRight, int oldBottom) { 102 int width = right - left; 103 int height = bottom - top; 104 updatePreviewTransform(width, height, false); 105 } 106 }; 107 108 /** 109 * Called when the captured media has been saved. 110 */ 111 private final MediaSaver.OnMediaSavedListener mOnMediaSavedListener = 112 new MediaSaver.OnMediaSavedListener() { 113 @Override 114 public void onMediaSaved(Uri uri) { 115 if (uri != null) { 116 mAppController.notifyNewMedia(uri); 117 } 118 } 119 }; 120 121 /** 122 * Called when the user pressed the back/front camera switch button. 123 */ 124 private final ButtonManager.ButtonCallback mCameraSwitchCallback = 125 new ButtonManager.ButtonCallback() { 126 @Override 127 public void onStateChanged(int cameraId) { 128 // At the time this callback is fired, the camera id 129 // has be set to the desired camera. 130 if (mPaused) { 131 return; 132 } 133 134 mSettingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID, 135 cameraId); 136 137 Log.d(TAG, "Start to switch camera. cameraId=" + cameraId); 138 switchCamera(getFacingFromCameraId(cameraId)); 139 } 140 }; 141 142 private static final Tag TAG = new Tag("CaptureModule"); 143 private static final String PHOTO_MODULE_STRING_ID = "PhotoModule"; 144 /** Enable additional debug output. */ 145 private static final boolean DEBUG = true; 146 /** 147 * This is the delay before we execute onResume tasks when coming from the 148 * lock screen, to allow time for onPause to execute. 149 * <p> 150 * TODO: Make sure this value is in sync with what we see on L. 151 */ 152 private static final int ON_RESUME_TASKS_DELAY_MSEC = 20; 153 154 private final Object mDimensionLock = new Object(); 155 /** 156 * Lock for race conditions in the SurfaceTextureListener callbacks. 157 */ 158 private final Object mSurfaceLock = new Object(); 159 /** Controller giving us access to other services. */ 160 private final AppController mAppController; 161 /** The applications settings manager. */ 162 private final SettingsManager mSettingsManager; 163 /** Application context. */ 164 private final Context mContext; 165 private CaptureModuleUI mUI; 166 /** Your standard content resolver. */ 167 private ContentResolver mContentResolver; 168 /** The camera manager used to open cameras. */ 169 private OneCameraManager mCameraManager; 170 /** The currently opened camera device. */ 171 private OneCamera mCamera; 172 /** The direction the currently opened camera is facing to. */ 173 private Facing mCameraFacing = Facing.BACK; 174 /** The texture used to render the preview in. */ 175 private SurfaceTexture mPreviewTexture; 176 177 /** State by the module state machine. */ 178 private static enum ModuleState { 179 IDLE, 180 WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED, 181 UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE, 182 } 183 184 /** The current state of the module. */ 185 private ModuleState mState = ModuleState.IDLE; 186 /** Current orientation of the device. */ 187 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN; 188 189 /** Accelerometer data. */ 190 private final float[] mGData = new float[3]; 191 /** Magnetic sensor data. */ 192 private final float[] mMData = new float[3]; 193 /** Temporary rotation matrix. */ 194 private final float[] mR = new float[16]; 195 /** Current compass heading. */ 196 private int mHeading = -1; 197 198 /** Whether the module is paused right now. */ 199 private boolean mPaused; 200 201 /** Whether this module was resumed from lockscreen capture intent. */ 202 private boolean mIsResumeFromLockScreen = false; 203 204 private final Runnable mResumeTaskRunnable = new Runnable() { 205 @Override 206 public void run() { 207 onResumeTasks(); 208 } 209 }; 210 211 /** Main thread handler. */ 212 private Handler mMainHandler; 213 214 /** Current display rotation in degrees. */ 215 private int mDisplayRotation; 216 /** Current width of the screen, in pixels. */ 217 private int mScreenWidth; 218 /** Current height of the screen, in pixels. */ 219 private int mScreenHeight; 220 /** Current preview width, in pixels. */ 221 private int mPreviewBufferWidth; 222 /** Current preview height, in pixels. */ 223 private int mPreviewBufferHeight; 224 225 // /** Current preview area width. */ 226 // private float mFullPreviewWidth; 227 // /** Current preview area height. */ 228 // private float mFullPreviewHeight; 229 230 /** The current preview transformation matrix. */ 231 private Matrix mPreviewTranformationMatrix = new Matrix(); 232 /** TODO: This is N5 specific. */ 233 public static final float FULLSCREEN_ASPECT_RATIO = 16 / 9f; 234 235 /** 236 * Desires aspect ratio of the final image. 237 * <p> 238 * TODO: Can't we deduct this from the final image's resolution? 239 */ 240 private Float mFinalAspectRatio; 241 242 /** CLEAN UP START */ 243 // private SoundPool mSoundPool; 244 // private int mCaptureStartSoundId; 245 // private static final int NO_SOUND_STREAM = -999; 246 // private final int mCaptureStartSoundStreamId = NO_SOUND_STREAM; 247 // private int mCaptureDoneSoundId; 248 // private SoundClips.Player mSoundPlayer; 249 // private boolean mFirstLayout; 250 // private int[] mTargetFPSRanges; 251 // private float mZoomValue; 252 // private int mSensorOrientation; 253 // private int mLensFacing; 254 // private volatile float mMaxZoomRatio = 1.0f; 255 // private String mFlashMode; 256 /** CLEAN UP END */ 257 258 /** Constructs a new capture module. */ 259 public CaptureModule(AppController appController) { 260 super(appController); 261 mAppController = appController; 262 mContext = mAppController.getAndroidContext(); 263 mSettingsManager = mAppController.getSettingsManager(); 264 } 265 266 @Override 267 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) { 268 Log.d(TAG, "init"); 269 mIsResumeFromLockScreen = isResumeFromLockscreen(activity); 270 mMainHandler = new Handler(activity.getMainLooper()); 271 mCameraManager = mAppController.getCameraManager(); 272 mContentResolver = activity.getContentResolver(); 273 mDisplayRotation = CameraUtil.getDisplayRotation(mContext); 274 mCameraFacing = getFacingFromCameraId(mSettingsManager.getInteger( 275 mAppController.getModuleScope(), 276 Keys.KEY_CAMERA_ID)); 277 mUI = new CaptureModuleUI(activity, this, mAppController.getModuleLayoutRoot(), 278 mLayoutListener); 279 mAppController.setPreviewStatusListener(mUI); 280 mPreviewTexture = mAppController.getCameraAppUI().getSurfaceTexture(); 281 if (mPreviewTexture != null) { 282 initSurface(mPreviewTexture); 283 } 284 } 285 286 @Override 287 public void onShutterButtonFocus(boolean pressed) { 288 // TODO Auto-generated method stub 289 } 290 291 @Override 292 public void onShutterCoordinate(TouchCoordinate coord) { 293 // TODO Auto-generated method stub 294 } 295 296 @Override 297 public void onShutterButtonClick() { 298 // TODO: Add focusing. 299 if (mCamera == null) { 300 return; 301 } 302 303 // Set up the capture session. 304 String title = CameraUtil.createJpegName(System.currentTimeMillis()); 305 CaptureSession session = getServices().getCaptureSessionManager() 306 .createNewSession(title, null); 307 308 // Set up the parameters for this capture. 309 PhotoCaptureParameters params = new PhotoCaptureParameters(); 310 params.callback = this; 311 params.orientation = getOrientation(); 312 313 // Take the picture. 314 mCamera.takePicture(params, session); 315 } 316 317 @Override 318 public void onPreviewAreaChanged(RectF previewArea) { 319// mUI.updatePreviewAreaRect(previewArea); 320// mUI.positionProgressOverlay(previewArea); 321 } 322 323 @Override 324 public void onSensorChanged(SensorEvent event) { 325 // This is literally the same as the GCamModule implementation. 326 int type = event.sensor.getType(); 327 float[] data; 328 if (type == Sensor.TYPE_ACCELEROMETER) { 329 data = mGData; 330 } else if (type == Sensor.TYPE_MAGNETIC_FIELD) { 331 data = mMData; 332 } else { 333 Log.w(TAG, String.format("Unexpected sensor type %s", event.sensor.getName())); 334 return; 335 } 336 for (int i = 0; i < 3; i++) { 337 data[i] = event.values[i]; 338 } 339 float[] orientation = new float[3]; 340 SensorManager.getRotationMatrix(mR, null, mGData, mMData); 341 SensorManager.getOrientation(mR, orientation); 342 mHeading = (int) (orientation[0] * 180f / Math.PI) % 360; 343 if (mHeading < 0) { 344 mHeading += 360; 345 } 346 } 347 348 @Override 349 public void onAccuracyChanged(Sensor sensor, int accuracy) { 350 // TODO Auto-generated method stub 351 } 352 353 @Override 354 public void onQueueStatus(boolean full) { 355 // TODO Auto-generated method stub 356 } 357 358 @Override 359 public void onRemoteShutterPress() { 360 // TODO: Check whether shutter is enabled. 361 onShutterButtonClick(); 362 } 363 364 @Override 365 public void onSurfaceTextureAvailable(final SurfaceTexture surface, int width, int height) { 366 Log.d(TAG, "onSurfaceTextureAvailable"); 367 // Force to re-apply transform matrix here as a workaround for 368 // b/11168275 369 updatePreviewTransform(width, height, true); 370 initSurface(surface); 371 } 372 373 public void initSurface(final SurfaceTexture surface) { 374 mPreviewTexture = surface; 375 closeCamera(); 376 377 mCameraManager.open(mCameraFacing, getPictureSizeFromSettings(), new OpenCallback() { 378 @Override 379 public void onFailure() { 380 Log.e(TAG, "Could not open camera."); 381 mCamera = null; 382 } 383 384 @Override 385 public void onCameraOpened(final OneCamera camera) { 386 Log.d(TAG, "onCameraOpened: " + camera); 387 mCamera = camera; 388 updateBufferDimension(); 389 390 // If the surface texture is not destroyed, it may have the last 391 // frame lingering. 392 // We need to hold off setting transform until preview is 393 // started. 394 resetDefaultBufferSize(); 395 mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED; 396 397 Log.d(TAG, "starting preview ..."); 398 399 // TODO: Consider rolling these two calls into one. 400 camera.startPreview(new Surface(surface), new CaptureReadyCallback() { 401 402 @Override 403 public void onSetupFailed() { 404 Log.e(TAG, "Could not set up preview."); 405 mCamera.close(null); 406 mCamera = null; 407 } 408 409 @Override 410 public void onReadyForCapture() { 411 Log.d(TAG, "Ready for capture, w000t!"); 412 onPreviewStarted(); 413 } 414 }); 415 } 416 }); 417 } 418 419 @Override 420 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 421 Log.d(TAG, "onSurfaceTextureSizeChanged"); 422 resetDefaultBufferSize(); 423 } 424 425 @Override 426 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 427 Log.d(TAG, "onSurfaceTextureDestroyed"); 428 closeCamera(); 429 return true; 430 } 431 432 @Override 433 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 434 if (mState == ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE) { 435 Log.d(TAG, "onSurfaceTextureUpdated --> updatePreviewTransform"); 436 mState = ModuleState.IDLE; 437 CameraAppUI appUI = mAppController.getCameraAppUI(); 438 updatePreviewTransform(appUI.getSurfaceWidth(), appUI.getSurfaceHeight(), true); 439 } 440 } 441 442 @Override 443 public String getModuleStringIdentifier() { 444 return PHOTO_MODULE_STRING_ID; 445 } 446 447 @Override 448 public void resume() { 449 // Add delay on resume from lock screen only, in order to to speed up 450 // the onResume --> onPause --> onResume cycle from lock screen. 451 // Don't do always because letting go of thread can cause delay. 452 if (mIsResumeFromLockScreen) { 453 Log.v(TAG, "Delayng onResumeTasks from lock screen. " + System.currentTimeMillis()); 454 // Note: onPauseAfterSuper() will delete this runnable, so we will 455 // at most have 1 copy queued up. 456 mMainHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC); 457 } else { 458 onResumeTasks(); 459 } 460 } 461 462 private void onResumeTasks() { 463 Log.d(TAG, "onResumeTasks + " + System.currentTimeMillis()); 464 mPaused = false; 465 mAppController.getCameraAppUI().onChangeCamera(); 466 mAppController.addPreviewAreaSizeChangedListener(this); 467 resetDefaultBufferSize(); 468 getServices().getRemoteShutterListener().onModuleReady(this); 469 mAppController.setShutterEnabled(true); 470 } 471 472 @Override 473 public void pause() { 474 mPaused = true; 475 resetTextureBufferSize(); 476 closeCamera(); 477 // Remove delayed resume trigger, if it hasn't been executed yet. 478 mMainHandler.removeCallbacksAndMessages(null); 479 } 480 481 @Override 482 public void destroy() { 483 } 484 485 @Override 486 public void onLayoutOrientationChanged(boolean isLandscape) { 487 Log.d(TAG, "onLayoutOrientationChanged"); 488 } 489 490 @Override 491 public void onOrientationChanged(int orientation) { 492 // We keep the last known orientation. So if the user first orient 493 // the camera then point the camera to floor or sky, we still have 494 // the correct orientation. 495 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) { 496 return; 497 } 498 mOrientation = CameraUtil.roundOrientation(orientation, mOrientation); 499 } 500 501 @Override 502 public void onCameraAvailable(CameraProxy cameraProxy) { 503 // Ignore since we manage the camera ourselves until we remove this. 504 } 505 506 @Override 507 public void hardResetSettings(SettingsManager settingsManager) { 508 // TODO Auto-generated method stub 509 } 510 511 @Override 512 public HardwareSpec getHardwareSpec() { 513 return new HardwareSpec() { 514 @Override 515 public boolean isFrontCameraSupported() { 516 return true; 517 } 518 519 @Override 520 public boolean isHdrSupported() { 521 return false; 522 } 523 524 @Override 525 public boolean isHdrPlusSupported() { 526 // TODO: Enable once we support this. 527 return false; 528 } 529 530 @Override 531 public boolean isFlashSupported() { 532 // TODO: Enable once we support this. 533 return false; 534 } 535 }; 536 } 537 538 @Override 539 public BottomBarUISpec getBottomBarSpec() { 540 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec(); 541 bottomBarSpec.enableGridLines = true; 542 bottomBarSpec.enableCamera = true; 543 bottomBarSpec.cameraCallback = mCameraSwitchCallback; 544 // TODO: Enable once we support this. 545 bottomBarSpec.enableHdr = false; 546 // TODO: Enable once we support this. 547 bottomBarSpec.hdrCallback = null; 548 // TODO: Enable once we support this. 549 bottomBarSpec.enableSelfTimer = false; 550 bottomBarSpec.showSelfTimer = false; 551 return bottomBarSpec; 552 } 553 554 @Override 555 public boolean isUsingBottomBar() { 556 return true; 557 } 558 559 @Override 560 public boolean onKeyDown(int keyCode, KeyEvent event) { 561 return false; 562 } 563 564 @Override 565 public boolean onKeyUp(int keyCode, KeyEvent event) { 566 return false; 567 } 568 569 @Override 570 public void onSingleTapUp(View view, int x, int y) { 571 } 572 573 @Override 574 public String getPeekAccessibilityString() { 575 return mAppController.getAndroidContext() 576 .getResources().getString(R.string.photo_accessibility_peek); 577 } 578 579 @Override 580 public void onThumbnailResult(Bitmap bitmap) { 581 // TODO 582 } 583 584 @Override 585 public void onPictureTaken(byte[] jpegData, CaptureSession session) { 586 int heading = mHeading; 587 int width = 0; 588 int height = 0; 589 int rotation = 0; 590 ExifInterface exif = null; 591 try { 592 exif = new ExifInterface(); 593 exif.readExif(jpegData); 594 595 Integer w = exif.getTagIntValue(ExifInterface.TAG_PIXEL_X_DIMENSION); 596 width = (w == null) ? width : w; 597 Integer h = exif.getTagIntValue(ExifInterface.TAG_PIXEL_Y_DIMENSION); 598 height = (h == null) ? height : h; 599 600 // Get image rotation from EXIF. 601 rotation = Exif.getOrientation(exif); 602 603 // Set GPS heading direction based on sensor, if location is on. 604 if (heading >= 0) { 605 ExifTag directionRefTag = exif.buildTag( 606 ExifInterface.TAG_GPS_IMG_DIRECTION_REF, 607 ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION); 608 ExifTag directionTag = exif.buildTag( 609 ExifInterface.TAG_GPS_IMG_DIRECTION, 610 new Rational(heading, 1)); 611 exif.setTag(directionRefTag); 612 exif.setTag(directionTag); 613 } 614 } catch (IOException e) { 615 Log.w(TAG, "Could not read exif from gcam jpeg", e); 616 exif = null; 617 } 618 619 // TODO: Use correct time, location etc data. 620 getServices().getMediaSaver().addImage( 621 jpegData, session.getTitle(), System.currentTimeMillis(), null, width, height, 622 rotation, exif, mOnMediaSavedListener, mContentResolver); 623 } 624 625 @Override 626 public void onTakePictureProgress(int progressPercent) { 627 // TODO once we hace HDR+ hooke dup. 628 } 629 630 @Override 631 public void onPictureTakenFailed() { 632 // TODO 633 } 634 635 /** 636 * Updates the preview transform matrix to adapt to the current preview 637 * width, height, and orientation. 638 */ 639 public void updatePreviewTransform() { 640 int width; 641 int height; 642 synchronized (mDimensionLock) { 643 width = mScreenWidth; 644 height = mScreenHeight; 645 } 646 updatePreviewTransform(width, height); 647 } 648 649 /** 650 * Called when the preview started. Informs the app controller and queues a 651 * transform update when the next preview frame arrives. 652 */ 653 private void onPreviewStarted() { 654 if (mState == ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED) { 655 mState = ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE; 656 } 657 mAppController.onPreviewStarted(); 658 } 659 660 /** 661 * Update the preview transform based on the new dimensions. Will not force 662 * an update, if it's not necessary. 663 */ 664 private void updatePreviewTransform(int incomingWidth, int incomingHeight) { 665 updatePreviewTransform(incomingWidth, incomingHeight, false); 666 } 667 668 /*** 669 * Update the preview transform based on the new dimensions. 670 */ 671 private void updatePreviewTransform(int incomingWidth, int incomingHeight, 672 boolean forceUpdate) { 673 Log.d(TAG, "updatePreviewTransform: " + incomingWidth + " x " + incomingHeight); 674 675 synchronized (mDimensionLock) { 676 int incomingRotation = CameraUtil 677 .getDisplayRotation(mContext); 678 // Check for an actual change: 679 if (mScreenHeight == incomingHeight && mScreenWidth == incomingWidth && 680 incomingRotation == mDisplayRotation && !forceUpdate) { 681 return; 682 } 683 // Update display rotation and dimensions 684 mDisplayRotation = incomingRotation; 685 mScreenWidth = incomingWidth; 686 mScreenHeight = incomingHeight; 687 updateBufferDimension(); 688 689 mPreviewTranformationMatrix = mAppController.getCameraAppUI().getPreviewTransform(mPreviewTranformationMatrix); 690 int width = mScreenWidth; 691 int height = mScreenHeight; 692 693 // Assumptions: 694 // - Aspect ratio for the sensor buffers is in landscape 695 // orientation, 696 // - Dimensions of buffers received are rotated to the natural 697 // device orientation. 698 // - The contents of each buffer are rotated by the inverse of 699 // the display rotation. 700 // - Surface scales the buffer to fit the current view bounds. 701 702 // Get natural orientation and buffer dimensions 703 int naturalOrientation = CaptureModuleUtil 704 .getDeviceNaturalOrientation(mContext); 705 int effectiveWidth = mPreviewBufferWidth; 706 int effectiveHeight = mPreviewBufferHeight; 707 708 if (DEBUG) { 709 Log.v(TAG, "Rotation: " + mDisplayRotation); 710 Log.v(TAG, "Screen Width: " + mScreenWidth); 711 Log.v(TAG, "Screen Height: " + mScreenHeight); 712 Log.v(TAG, "Buffer width: " + mPreviewBufferWidth); 713 Log.v(TAG, "Buffer height: " + mPreviewBufferHeight); 714 Log.v(TAG, "Natural orientation: " + naturalOrientation); 715 } 716 717 // If natural orientation is portrait, rotate the buffer 718 // dimensions 719 if (naturalOrientation == Configuration.ORIENTATION_PORTRAIT) { 720 int temp = effectiveWidth; 721 effectiveWidth = effectiveHeight; 722 effectiveHeight = temp; 723 } 724 725 // Find and center view rect and buffer rect 726 RectF viewRect = new RectF(0, 0, width, height); 727 RectF bufRect = new RectF(0, 0, effectiveWidth, effectiveHeight); 728 float centerX = viewRect.centerX(); 729 float centerY = viewRect.centerY(); 730 bufRect.offset(centerX - bufRect.centerX(), centerY - bufRect.centerY()); 731 732 // Undo ScaleToFit.FILL done by the surface 733 mPreviewTranformationMatrix.setRectToRect(viewRect, bufRect, Matrix.ScaleToFit.FILL); 734 735 // Rotate buffer contents to proper orientation 736 mPreviewTranformationMatrix.postRotate(getPreviewOrientation(mDisplayRotation), centerX, centerY); 737 738 // TODO: This is probably only working for the N5. Need to test 739 // on a device like N10 with different sensor orientation. 740 if ((mDisplayRotation % 180) == 90) { 741 int temp = effectiveWidth; 742 effectiveWidth = effectiveHeight; 743 effectiveHeight = temp; 744 } 745 746 boolean is16by9 = false; 747 748 // TODO: BACK/FRONT. 749 Size pictureSize = getPictureSizeFromSettings(); 750 if (pictureSize != null) { 751 pictureSize = ResolutionUtil.getApproximateSize(pictureSize); 752 if (pictureSize.equals(new Size(16, 9))) { 753 is16by9 = true; 754 } 755 } 756 757 float scale; 758 if (is16by9) { 759 // We are going to be clipping off edges to achieve the 16 760 // by 9 aspect ratio so we will choose the max here to fill, 761 // instead of fit. 762 scale = 763 Math.max(width / (float) effectiveWidth, height 764 / (float) effectiveHeight); 765 } else { 766 // Scale to fit view, cropping the longest dimension 767 scale = 768 Math.min(width / (float) effectiveWidth, height 769 / (float) effectiveHeight); 770 } 771 mPreviewTranformationMatrix.postScale(scale, scale, centerX, centerY); 772 773 float previewWidth = effectiveWidth * scale; 774 float previewHeight = effectiveHeight * scale; 775 // mFullPreviewWidth = previewWidth; 776 // mFullPreviewHeight = previewHeight; 777 778 float previewCenterX = previewWidth / 2; 779 float previewCenterY = previewHeight / 2; 780 mPreviewTranformationMatrix.postTranslate(previewCenterX - centerX, previewCenterY - centerY); 781 782 if (is16by9) { 783 float aspectRatio = FULLSCREEN_ASPECT_RATIO; 784 RectF renderedPreviewRect = mAppController.getFullscreenRect(); 785 float desiredPreviewWidth = Math.max(renderedPreviewRect.height(), 786 renderedPreviewRect.width()) * 1 / aspectRatio; 787 int letterBoxWidth = (int) Math.ceil((Math.min(renderedPreviewRect.width(), 788 renderedPreviewRect.height()) - desiredPreviewWidth) / 2.0f); 789 mAppController.getCameraAppUI().addLetterboxing(letterBoxWidth); 790 791 float wOffset = -(previewWidth - renderedPreviewRect.width()) / 2.0f; 792 float hOffset = -(previewHeight - renderedPreviewRect.height()) / 2.0f; 793 mPreviewTranformationMatrix.postTranslate(wOffset, hOffset); 794 mAppController.updatePreviewTransformFullscreen(mPreviewTranformationMatrix, aspectRatio); 795 mFinalAspectRatio = aspectRatio; 796 } else { 797 mAppController.updatePreviewTransform(mPreviewTranformationMatrix); 798 mFinalAspectRatio = null; 799 mAppController.getCameraAppUI().hideLetterboxing(); 800 } 801 // if (mGcamProxy != null) { 802 // mGcamProxy.postSetAspectRatio(mFinalAspectRatio); 803 // } 804// mUI.updatePreviewAreaRect(new RectF(0, 0, previewWidth, previewHeight)); 805 806 // TODO: Add face detection. 807 // Characteristics info = 808 // mapp.getCameraProvider().getCharacteristics(0); 809 // mUI.setupFaceDetection(CameraUtil.getDisplayOrientation(incomingRotation, 810 // info), false); 811 // updateCamera2FaceBoundTransform(new 812 // RectF(mEffectiveCropRegion), 813 // new RectF(0, 0, mBufferWidth, mBufferHeight), 814 // new RectF(0, 0, previewWidth, previewHeight), getRotation()); 815 } 816 } 817 818 private void updateBufferDimension() { 819 if (mCamera == null) { 820 return; 821 } 822 823 Size picked = CaptureModuleUtil.pickBufferDimensions( 824 mCamera.getSupportedSizes(), 825 mCamera.getFullSizeAspectRatio(), 826 mContext); 827 mPreviewBufferWidth = picked.getWidth(); 828 mPreviewBufferHeight = picked.getHeight(); 829 } 830 831 /** 832 * Resets the default buffer size to the initially calculated size. 833 */ 834 private void resetDefaultBufferSize() { 835 synchronized (mSurfaceLock) { 836 if (mPreviewTexture != null) { 837 mPreviewTexture.setDefaultBufferSize(mPreviewBufferWidth, mPreviewBufferHeight); 838 } 839 } 840 } 841 842 private void closeCamera() { 843 if (mCamera != null) { 844 mCamera.close(null); 845 mCamera = null; 846 } 847 } 848 849 private int getOrientation() { 850 if (mAppController.isAutoRotateScreen()) { 851 return mDisplayRotation; 852 } else { 853 return mOrientation; 854 } 855 } 856 857 /** 858 * @return Whether we are resuming from within the lockscreen. 859 */ 860 private static boolean isResumeFromLockscreen(Activity activity) { 861 String action = activity.getIntent().getAction(); 862 return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action) 863 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)); 864 } 865 866 private void switchCamera(Facing switchTo) { 867 if (mPaused || mCameraFacing == switchTo) { 868 return; 869 } 870 // TODO: Un-comment once we have timer back. 871 // cancelCountDown(); 872 873 mAppController.freezeScreenUntilPreviewReady(); 874 875 mCameraFacing = switchTo; 876 initSurface(mPreviewTexture); 877 878 // TODO: Un-comment once we have focus back. 879 // if (mFocusManager != null) { 880 // mFocusManager.removeMessages(); 881 // } 882 // mFocusManager.setMirror(mMirror); 883 } 884 885 private Size getPictureSizeFromSettings() { 886 String pictureSizeKey = mCameraFacing == Facing.FRONT ? Keys.KEY_PICTURE_SIZE_FRONT 887 : Keys.KEY_PICTURE_SIZE_BACK; 888 return mSettingsManager.getSize(SettingsManager.SCOPE_GLOBAL, pictureSizeKey); 889 } 890 891 private int getPreviewOrientation(int deviceOrientationDegrees) { 892 // Important: Camera2 buffers are already rotated to the natural 893 // orientation of the device (at least for the back-camera). 894 895 // TODO: Remove this hack for the front camera as soon as b/16637957 is 896 // fixed. 897 if (mCameraFacing == Facing.FRONT) { 898 deviceOrientationDegrees += 180; 899 } 900 return (360 - deviceOrientationDegrees) % 360; 901 } 902 903 /** 904 * Returns which way around the camera is facing, based on it's ID. 905 * <p> 906 * TODO: This needs to change so that we store the direction directly in the 907 * settings, rather than a Camera ID. 908 */ 909 private static Facing getFacingFromCameraId(int cameraId) { 910 return cameraId == 1 ? Facing.FRONT : Facing.BACK; 911 } 912 913 private void resetTextureBufferSize() { 914 // Reset the default buffer sizes on the shared SurfaceTexture 915 // so they are not scaled for gcam. 916 // 917 // According to the documentation for SurfaceTexture.setDefaultBufferSize, 918 // photo and video based image producers (presumably only Camera 1 api), 919 // override this buffer size. Any module that uses egl to render to a 920 // SurfaceTexture must have these buffer sizes reset manually. Otherwise 921 // the SurfaceTexture cannot be transformed by matrix set on the TextureView. 922 if (mPreviewTexture != null) { 923 mPreviewTexture.setDefaultBufferSize(mAppController.getCameraAppUI().getSurfaceWidth(), 924 mAppController.getCameraAppUI().getSurfaceHeight()); 925 } 926 } 927} 928