1 /* 2 * Copyright (C) 2016 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 17 package com.android.dialer.callcomposer.camera; 18 19 import android.content.Context; 20 import android.hardware.Camera; 21 import android.hardware.Camera.CameraInfo; 22 import android.net.Uri; 23 import android.os.AsyncTask; 24 import android.os.Looper; 25 import android.support.annotation.NonNull; 26 import android.support.annotation.Nullable; 27 import android.support.annotation.VisibleForTesting; 28 import android.text.TextUtils; 29 import android.view.MotionEvent; 30 import android.view.OrientationEventListener; 31 import android.view.Surface; 32 import android.view.View; 33 import android.view.WindowManager; 34 import com.android.dialer.callcomposer.camera.camerafocus.FocusOverlayManager; 35 import com.android.dialer.callcomposer.camera.camerafocus.RenderOverlay; 36 import com.android.dialer.common.Assert; 37 import com.android.dialer.common.LogUtil; 38 import java.io.IOException; 39 import java.util.ArrayList; 40 import java.util.Collections; 41 import java.util.Comparator; 42 import java.util.List; 43 44 /** 45 * Class which manages interactions with the camera, but does not do any UI. This class is designed 46 * to be a singleton to ensure there is one component managing the camera and releasing the native 47 * resources. In order to acquire a camera, a caller must: 48 * 49 * <ul> 50 * <li>Call selectCamera to select front or back camera 51 * <li>Call setSurface to control where the preview is shown 52 * <li>Call openCamera to request the camera start preview 53 * </ul> 54 * 55 * Callers should call onPause and onResume to ensure that the camera is release while the activity 56 * is not active. This class is not thread safe. It should only be called from one thread (the UI 57 * thread or test thread) 58 */ 59 public class CameraManager implements FocusOverlayManager.Listener { 60 /** Callbacks for the camera manager listener */ 61 public interface CameraManagerListener { onCameraError(int errorCode, Exception e)62 void onCameraError(int errorCode, Exception e); 63 onCameraChanged()64 void onCameraChanged(); 65 } 66 67 /** Callback when taking image or video */ 68 public interface MediaCallback { 69 int MEDIA_CAMERA_CHANGED = 1; 70 int MEDIA_NO_DATA = 2; 71 onMediaReady(Uri uriToMedia, String contentType, int width, int height)72 void onMediaReady(Uri uriToMedia, String contentType, int width, int height); 73 onMediaFailed(Exception exception)74 void onMediaFailed(Exception exception); 75 onMediaInfo(int what)76 void onMediaInfo(int what); 77 } 78 79 // Error codes 80 private static final int ERROR_OPENING_CAMERA = 1; 81 private static final int ERROR_SHOWING_PREVIEW = 2; 82 private static final int ERROR_HARDWARE_ACCELERATION_DISABLED = 3; 83 private static final int ERROR_TAKING_PICTURE = 4; 84 85 private static final int NO_CAMERA_SELECTED = -1; 86 87 private static final Camera.ShutterCallback DUMMY_SHUTTER_CALLBACK = 88 new Camera.ShutterCallback() { 89 @Override 90 public void onShutter() { 91 // Do nothing 92 } 93 }; 94 95 private static CameraManager sInstance; 96 97 /** The CameraInfo for the currently selected camera */ 98 private final CameraInfo mCameraInfo; 99 100 /** The index of the selected camera or NO_CAMERA_SELECTED if a camera hasn't been selected yet */ 101 private int mCameraIndex; 102 103 /** True if the device has front and back cameras */ 104 private final boolean mHasFrontAndBackCamera; 105 106 /** True if the camera should be open (may not yet be actually open) */ 107 private boolean mOpenRequested; 108 109 /** The preview view to show the preview on */ 110 private CameraPreview mCameraPreview; 111 112 /** The helper classs to handle orientation changes */ 113 private OrientationHandler mOrientationHandler; 114 115 /** Tracks whether the preview has hardware acceleration */ 116 private boolean mIsHardwareAccelerationSupported; 117 118 /** 119 * The task for opening the camera, so it doesn't block the UI thread Using AsyncTask rather than 120 * SafeAsyncTask because the tasks need to be serialized, but don't need to be on the UI thread 121 * TODO: If we have other AyncTasks (not SafeAsyncTasks) this may contend and we may need 122 * to create a dedicated thread, or synchronize the threads in the thread pool 123 */ 124 private AsyncTask<Integer, Void, Camera> mOpenCameraTask; 125 126 /** 127 * The camera index that is queued to be opened, but not completed yet, or NO_CAMERA_SELECTED if 128 * no open task is pending 129 */ 130 private int mPendingOpenCameraIndex = NO_CAMERA_SELECTED; 131 132 /** The instance of the currently opened camera */ 133 private Camera mCamera; 134 135 /** The rotation of the screen relative to the camera's natural orientation */ 136 private int mRotation; 137 138 /** The callback to notify when errors or other events occur */ 139 private CameraManagerListener mListener; 140 141 /** True if the camera is currently in the process of taking an image */ 142 private boolean mTakingPicture; 143 144 /** Manages auto focus visual and behavior */ 145 private final FocusOverlayManager mFocusOverlayManager; 146 CameraManager()147 private CameraManager() { 148 mCameraInfo = new CameraInfo(); 149 mCameraIndex = NO_CAMERA_SELECTED; 150 151 // Check to see if a front and back camera exist 152 boolean hasFrontCamera = false; 153 boolean hasBackCamera = false; 154 final CameraInfo cameraInfo = new CameraInfo(); 155 final int cameraCount = Camera.getNumberOfCameras(); 156 try { 157 for (int i = 0; i < cameraCount; i++) { 158 Camera.getCameraInfo(i, cameraInfo); 159 if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) { 160 hasFrontCamera = true; 161 } else if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) { 162 hasBackCamera = true; 163 } 164 if (hasFrontCamera && hasBackCamera) { 165 break; 166 } 167 } 168 } catch (final RuntimeException e) { 169 LogUtil.e("CameraManager.CameraManager", "Unable to load camera info", e); 170 } 171 mHasFrontAndBackCamera = hasFrontCamera && hasBackCamera; 172 mFocusOverlayManager = new FocusOverlayManager(this, Looper.getMainLooper()); 173 174 // Assume the best until we are proven otherwise 175 mIsHardwareAccelerationSupported = true; 176 } 177 178 /** Gets the singleton instance */ get()179 public static CameraManager get() { 180 if (sInstance == null) { 181 sInstance = new CameraManager(); 182 } 183 return sInstance; 184 } 185 186 /** 187 * Sets the surface to use to display the preview This must only be called AFTER the CameraPreview 188 * has a texture ready 189 * 190 * @param preview The preview surface view 191 */ setSurface(final CameraPreview preview)192 void setSurface(final CameraPreview preview) { 193 if (preview == mCameraPreview) { 194 return; 195 } 196 197 if (preview != null) { 198 Assert.checkArgument(preview.isValid()); 199 preview.setOnTouchListener( 200 new View.OnTouchListener() { 201 @Override 202 public boolean onTouch(final View view, final MotionEvent motionEvent) { 203 if ((motionEvent.getActionMasked() & MotionEvent.ACTION_UP) 204 == MotionEvent.ACTION_UP) { 205 mFocusOverlayManager.setPreviewSize(view.getWidth(), view.getHeight()); 206 mFocusOverlayManager.onSingleTapUp( 207 (int) motionEvent.getX() + view.getLeft(), 208 (int) motionEvent.getY() + view.getTop()); 209 } 210 view.performClick(); 211 return true; 212 } 213 }); 214 } 215 mCameraPreview = preview; 216 tryShowPreview(); 217 } 218 setRenderOverlay(final RenderOverlay renderOverlay)219 public void setRenderOverlay(final RenderOverlay renderOverlay) { 220 mFocusOverlayManager.setFocusRenderer( 221 renderOverlay != null ? renderOverlay.getPieRenderer() : null); 222 } 223 224 /** Convenience function to swap between front and back facing cameras */ swapCamera()225 public void swapCamera() { 226 Assert.checkState(mCameraIndex >= 0); 227 selectCamera( 228 mCameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT 229 ? CameraInfo.CAMERA_FACING_BACK 230 : CameraInfo.CAMERA_FACING_FRONT); 231 } 232 233 /** 234 * Selects the first camera facing the desired direction, or the first camera if there is no 235 * camera in the desired direction 236 * 237 * @param desiredFacing One of the CameraInfo.CAMERA_FACING_* constants 238 * @return True if a camera was selected, or false if selecting a camera failed 239 */ selectCamera(final int desiredFacing)240 public boolean selectCamera(final int desiredFacing) { 241 try { 242 // We already selected a camera facing that direction 243 if (mCameraIndex >= 0 && mCameraInfo.facing == desiredFacing) { 244 return true; 245 } 246 247 final int cameraCount = Camera.getNumberOfCameras(); 248 Assert.checkState(cameraCount > 0); 249 250 mCameraIndex = NO_CAMERA_SELECTED; 251 setCamera(null); 252 final CameraInfo cameraInfo = new CameraInfo(); 253 for (int i = 0; i < cameraCount; i++) { 254 Camera.getCameraInfo(i, cameraInfo); 255 if (cameraInfo.facing == desiredFacing) { 256 mCameraIndex = i; 257 Camera.getCameraInfo(i, mCameraInfo); 258 break; 259 } 260 } 261 262 // There's no camera in the desired facing direction, just select the first camera 263 // regardless of direction 264 if (mCameraIndex < 0) { 265 mCameraIndex = 0; 266 Camera.getCameraInfo(0, mCameraInfo); 267 } 268 269 if (mOpenRequested) { 270 // The camera is open, so reopen with the newly selected camera 271 openCamera(); 272 } 273 return true; 274 } catch (final RuntimeException e) { 275 LogUtil.e("CameraManager.selectCamera", "RuntimeException in CameraManager.selectCamera", e); 276 if (mListener != null) { 277 mListener.onCameraError(ERROR_OPENING_CAMERA, e); 278 } 279 return false; 280 } 281 } 282 getCameraIndex()283 public int getCameraIndex() { 284 return mCameraIndex; 285 } 286 selectCameraByIndex(final int cameraIndex)287 public void selectCameraByIndex(final int cameraIndex) { 288 if (mCameraIndex == cameraIndex) { 289 return; 290 } 291 292 try { 293 mCameraIndex = cameraIndex; 294 Camera.getCameraInfo(mCameraIndex, mCameraInfo); 295 if (mOpenRequested) { 296 openCamera(); 297 } 298 } catch (final RuntimeException e) { 299 LogUtil.e( 300 "CameraManager.selectCameraByIndex", 301 "RuntimeException in CameraManager.selectCameraByIndex", 302 e); 303 if (mListener != null) { 304 mListener.onCameraError(ERROR_OPENING_CAMERA, e); 305 } 306 } 307 } 308 309 @Nullable 310 @VisibleForTesting getCameraInfo()311 public CameraInfo getCameraInfo() { 312 if (mCameraIndex == NO_CAMERA_SELECTED) { 313 return null; 314 } 315 return mCameraInfo; 316 } 317 318 /** @return True if the device has both a front and back camera */ hasFrontAndBackCamera()319 public boolean hasFrontAndBackCamera() { 320 return mHasFrontAndBackCamera; 321 } 322 323 /** Opens the camera on a separate thread and initiates the preview if one is available */ openCamera()324 void openCamera() { 325 if (mCameraIndex == NO_CAMERA_SELECTED) { 326 // Ensure a selected camera if none is currently selected. This may happen if the 327 // camera chooser is not the default media chooser. 328 selectCamera(CameraInfo.CAMERA_FACING_BACK); 329 } 330 mOpenRequested = true; 331 // We're already opening the camera or already have the camera handle, nothing more to do 332 if (mPendingOpenCameraIndex == mCameraIndex || mCamera != null) { 333 return; 334 } 335 336 // True if the task to open the camera has to be delayed until the current one completes 337 boolean delayTask = false; 338 339 // Cancel any previous open camera tasks 340 if (mOpenCameraTask != null) { 341 mPendingOpenCameraIndex = NO_CAMERA_SELECTED; 342 delayTask = true; 343 } 344 345 mPendingOpenCameraIndex = mCameraIndex; 346 mOpenCameraTask = 347 new AsyncTask<Integer, Void, Camera>() { 348 private Exception mException; 349 350 @Override 351 protected Camera doInBackground(final Integer... params) { 352 try { 353 final int cameraIndex = params[0]; 354 LogUtil.v("CameraManager.doInBackground", "Opening camera " + mCameraIndex); 355 return Camera.open(cameraIndex); 356 } catch (final Exception e) { 357 LogUtil.e("CameraManager.doInBackground", "Exception while opening camera", e); 358 mException = e; 359 return null; 360 } 361 } 362 363 @Override 364 protected void onPostExecute(final Camera camera) { 365 // If we completed, but no longer want this camera, then release the camera 366 if (mOpenCameraTask != this || !mOpenRequested) { 367 releaseCamera(camera); 368 cleanup(); 369 return; 370 } 371 372 cleanup(); 373 374 LogUtil.v( 375 "CameraManager.onPostExecute", 376 "Opened camera " + mCameraIndex + " " + (camera != null)); 377 setCamera(camera); 378 if (camera == null) { 379 if (mListener != null) { 380 mListener.onCameraError(ERROR_OPENING_CAMERA, mException); 381 } 382 LogUtil.e("CameraManager.onPostExecute", "Error opening camera"); 383 } 384 } 385 386 @Override 387 protected void onCancelled() { 388 super.onCancelled(); 389 cleanup(); 390 } 391 392 private void cleanup() { 393 mPendingOpenCameraIndex = NO_CAMERA_SELECTED; 394 if (mOpenCameraTask != null && mOpenCameraTask.getStatus() == Status.PENDING) { 395 // If there's another task waiting on this one to complete, start it now 396 mOpenCameraTask.execute(mCameraIndex); 397 } else { 398 mOpenCameraTask = null; 399 } 400 } 401 }; 402 LogUtil.v("CameraManager.openCamera", "Start opening camera " + mCameraIndex); 403 if (!delayTask) { 404 mOpenCameraTask.execute(mCameraIndex); 405 } 406 } 407 408 /** Closes the camera releasing the resources it uses */ closeCamera()409 void closeCamera() { 410 mOpenRequested = false; 411 setCamera(null); 412 } 413 414 /** 415 * Sets the listener which will be notified of errors or other events in the camera 416 * 417 * @param listener The listener to notify 418 */ setListener(final CameraManagerListener listener)419 public void setListener(final CameraManagerListener listener) { 420 Assert.isMainThread(); 421 mListener = listener; 422 if (!mIsHardwareAccelerationSupported && mListener != null) { 423 mListener.onCameraError(ERROR_HARDWARE_ACCELERATION_DISABLED, null); 424 } 425 } 426 takePicture(final float heightPercent, @NonNull final MediaCallback callback)427 public void takePicture(final float heightPercent, @NonNull final MediaCallback callback) { 428 Assert.checkState(!mTakingPicture); 429 Assert.isNotNull(callback); 430 mCameraPreview.setFocusable(false); 431 mFocusOverlayManager.cancelAutoFocus(); 432 if (mCamera == null) { 433 // The caller should have checked isCameraAvailable first, but just in case, protect 434 // against a null camera by notifying the callback that taking the picture didn't work 435 callback.onMediaFailed(null); 436 return; 437 } 438 final Camera.PictureCallback jpegCallback = 439 new Camera.PictureCallback() { 440 @Override 441 public void onPictureTaken(final byte[] bytes, final Camera camera) { 442 mTakingPicture = false; 443 if (mCamera != camera) { 444 // This may happen if the camera was changed between front/back while the 445 // picture is being taken. 446 callback.onMediaInfo(MediaCallback.MEDIA_CAMERA_CHANGED); 447 return; 448 } 449 450 if (bytes == null) { 451 callback.onMediaInfo(MediaCallback.MEDIA_NO_DATA); 452 return; 453 } 454 455 final Camera.Size size = camera.getParameters().getPictureSize(); 456 int width; 457 int height; 458 if (mRotation == 90 || mRotation == 270) { 459 // Is rotated, so swapping dimensions is desired 460 //noinspection SuspiciousNameCombination 461 width = size.height; 462 //noinspection SuspiciousNameCombination 463 height = size.width; 464 } else { 465 width = size.width; 466 height = size.height; 467 } 468 LogUtil.i( 469 "CameraManager.onPictureTaken", "taken picture size: " + bytes.length + " bytes"); 470 new ImagePersistTask( 471 width, height, heightPercent, bytes, mCameraPreview.getContext(), callback) 472 .execute(); 473 } 474 }; 475 476 mTakingPicture = true; 477 try { 478 mCamera.takePicture( 479 // A shutter callback is required to enable shutter sound 480 DUMMY_SHUTTER_CALLBACK, null /* raw */, null /* postView */, jpegCallback); 481 } catch (final RuntimeException e) { 482 LogUtil.e("CameraManager.takePicture", "RuntimeException in CameraManager.takePicture", e); 483 mTakingPicture = false; 484 if (mListener != null) { 485 mListener.onCameraError(ERROR_TAKING_PICTURE, e); 486 } 487 } 488 } 489 490 /** 491 * Asynchronously releases a camera 492 * 493 * @param camera The camera to release 494 */ releaseCamera(final Camera camera)495 private void releaseCamera(final Camera camera) { 496 if (camera == null) { 497 return; 498 } 499 500 mFocusOverlayManager.onCameraReleased(); 501 502 new AsyncTask<Void, Void, Void>() { 503 @Override 504 protected Void doInBackground(final Void... params) { 505 LogUtil.v("CameraManager.doInBackground", "Releasing camera " + mCameraIndex); 506 camera.release(); 507 return null; 508 } 509 }.execute(); 510 } 511 512 /** Updates the orientation of the camera to match the orientation of the device */ updateCameraOrientation()513 private void updateCameraOrientation() { 514 if (mCamera == null || mCameraPreview == null || mTakingPicture) { 515 return; 516 } 517 518 final WindowManager windowManager = 519 (WindowManager) mCameraPreview.getContext().getSystemService(Context.WINDOW_SERVICE); 520 521 int degrees; 522 switch (windowManager.getDefaultDisplay().getRotation()) { 523 case Surface.ROTATION_0: 524 degrees = 0; 525 break; 526 case Surface.ROTATION_90: 527 degrees = 90; 528 break; 529 case Surface.ROTATION_180: 530 degrees = 180; 531 break; 532 case Surface.ROTATION_270: 533 degrees = 270; 534 break; 535 default: 536 throw Assert.createAssertionFailException(""); 537 } 538 539 // The display orientation of the camera (this controls the preview image). 540 int orientation; 541 542 // The clockwise rotation angle relative to the orientation of the camera. This affects 543 // pictures returned by the camera in Camera.PictureCallback. 544 int rotation; 545 if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 546 orientation = (mCameraInfo.orientation + degrees) % 360; 547 rotation = orientation; 548 // compensate the mirror but only for orientation 549 orientation = (360 - orientation) % 360; 550 } else { // back-facing 551 orientation = (mCameraInfo.orientation - degrees + 360) % 360; 552 rotation = orientation; 553 } 554 mRotation = rotation; 555 try { 556 mCamera.setDisplayOrientation(orientation); 557 final Camera.Parameters params = mCamera.getParameters(); 558 params.setRotation(rotation); 559 mCamera.setParameters(params); 560 } catch (final RuntimeException e) { 561 LogUtil.e( 562 "CameraManager.updateCameraOrientation", 563 "RuntimeException in CameraManager.updateCameraOrientation", 564 e); 565 if (mListener != null) { 566 mListener.onCameraError(ERROR_OPENING_CAMERA, e); 567 } 568 } 569 } 570 571 /** Sets the current camera, releasing any previously opened camera */ setCamera(final Camera camera)572 private void setCamera(final Camera camera) { 573 if (mCamera == camera) { 574 return; 575 } 576 577 releaseCamera(mCamera); 578 mCamera = camera; 579 tryShowPreview(); 580 if (mListener != null) { 581 mListener.onCameraChanged(); 582 } 583 } 584 585 /** Shows the preview if the camera is open and the preview is loaded */ tryShowPreview()586 private void tryShowPreview() { 587 if (mCameraPreview == null || mCamera == null) { 588 if (mOrientationHandler != null) { 589 mOrientationHandler.disable(); 590 mOrientationHandler = null; 591 } 592 // releaseMediaRecorder(true /* cleanupFile */); 593 mFocusOverlayManager.onPreviewStopped(); 594 return; 595 } 596 try { 597 mCamera.stopPreview(); 598 updateCameraOrientation(); 599 600 final Camera.Parameters params = mCamera.getParameters(); 601 final Camera.Size pictureSize = chooseBestPictureSize(); 602 final Camera.Size previewSize = chooseBestPreviewSize(pictureSize); 603 params.setPreviewSize(previewSize.width, previewSize.height); 604 params.setPictureSize(pictureSize.width, pictureSize.height); 605 logCameraSize("Setting preview size: ", previewSize); 606 logCameraSize("Setting picture size: ", pictureSize); 607 mCameraPreview.setSize(previewSize, mCameraInfo.orientation); 608 for (final String focusMode : params.getSupportedFocusModes()) { 609 if (TextUtils.equals(focusMode, Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { 610 // Use continuous focus if available 611 params.setFocusMode(focusMode); 612 break; 613 } 614 } 615 616 mCamera.setParameters(params); 617 mCameraPreview.startPreview(mCamera); 618 mCamera.startPreview(); 619 mCamera.setAutoFocusMoveCallback( 620 new Camera.AutoFocusMoveCallback() { 621 @Override 622 public void onAutoFocusMoving(final boolean start, final Camera camera) { 623 mFocusOverlayManager.onAutoFocusMoving(start); 624 } 625 }); 626 mFocusOverlayManager.setParameters(mCamera.getParameters()); 627 mFocusOverlayManager.setMirror(mCameraInfo.facing == CameraInfo.CAMERA_FACING_BACK); 628 mFocusOverlayManager.onPreviewStarted(); 629 if (mOrientationHandler == null) { 630 mOrientationHandler = new OrientationHandler(mCameraPreview.getContext()); 631 mOrientationHandler.enable(); 632 } 633 } catch (final IOException e) { 634 LogUtil.e("CameraManager.tryShowPreview", "IOException in CameraManager.tryShowPreview", e); 635 if (mListener != null) { 636 mListener.onCameraError(ERROR_SHOWING_PREVIEW, e); 637 } 638 } catch (final RuntimeException e) { 639 LogUtil.e( 640 "CameraManager.tryShowPreview", "RuntimeException in CameraManager.tryShowPreview", e); 641 if (mListener != null) { 642 mListener.onCameraError(ERROR_SHOWING_PREVIEW, e); 643 } 644 } 645 } 646 isCameraAvailable()647 public boolean isCameraAvailable() { 648 return mCamera != null && !mTakingPicture && mIsHardwareAccelerationSupported; 649 } 650 651 /** 652 * Choose the best picture size by trying to find a size close to the MmsConfig's max size, which 653 * is closest to the screen aspect ratio. In case of RCS conversation returns default size. 654 */ chooseBestPictureSize()655 private Camera.Size chooseBestPictureSize() { 656 return mCamera.getParameters().getPictureSize(); 657 } 658 659 /** 660 * Chose the best preview size based on the picture size. Try to find a size with the same aspect 661 * ratio and size as the picture if possible 662 */ chooseBestPreviewSize(final Camera.Size pictureSize)663 private Camera.Size chooseBestPreviewSize(final Camera.Size pictureSize) { 664 final List<Camera.Size> sizes = 665 new ArrayList<Camera.Size>(mCamera.getParameters().getSupportedPreviewSizes()); 666 final float aspectRatio = pictureSize.width / (float) pictureSize.height; 667 final int capturePixels = pictureSize.width * pictureSize.height; 668 669 // Sort the sizes so the best size is first 670 Collections.sort( 671 sizes, 672 new SizeComparator(Integer.MAX_VALUE, Integer.MAX_VALUE, aspectRatio, capturePixels)); 673 674 return sizes.get(0); 675 } 676 677 private class OrientationHandler extends OrientationEventListener { OrientationHandler(final Context context)678 OrientationHandler(final Context context) { 679 super(context); 680 } 681 682 @Override onOrientationChanged(final int orientation)683 public void onOrientationChanged(final int orientation) { 684 updateCameraOrientation(); 685 } 686 } 687 688 private static class SizeComparator implements Comparator<Camera.Size> { 689 private static final int PREFER_LEFT = -1; 690 private static final int PREFER_RIGHT = 1; 691 692 // The max width/height for the preferred size. Integer.MAX_VALUE if no size limit 693 private final int mMaxWidth; 694 private final int mMaxHeight; 695 696 // The desired aspect ratio 697 private final float mTargetAspectRatio; 698 699 // The desired size (width x height) to try to match 700 private final int mTargetPixels; 701 SizeComparator( final int maxWidth, final int maxHeight, final float targetAspectRatio, final int targetPixels)702 public SizeComparator( 703 final int maxWidth, 704 final int maxHeight, 705 final float targetAspectRatio, 706 final int targetPixels) { 707 mMaxWidth = maxWidth; 708 mMaxHeight = maxHeight; 709 mTargetAspectRatio = targetAspectRatio; 710 mTargetPixels = targetPixels; 711 } 712 713 /** 714 * Returns a negative value if left is a better choice than right, or a positive value if right 715 * is a better choice is better than left. 0 if they are equal 716 */ 717 @Override compare(final Camera.Size left, final Camera.Size right)718 public int compare(final Camera.Size left, final Camera.Size right) { 719 // If one size is less than the max size prefer it over the other 720 if ((left.width <= mMaxWidth && left.height <= mMaxHeight) 721 != (right.width <= mMaxWidth && right.height <= mMaxHeight)) { 722 return left.width <= mMaxWidth ? PREFER_LEFT : PREFER_RIGHT; 723 } 724 725 // If one is closer to the target aspect ratio, prefer it. 726 final float leftAspectRatio = left.width / (float) left.height; 727 final float rightAspectRatio = right.width / (float) right.height; 728 final float leftAspectRatioDiff = Math.abs(leftAspectRatio - mTargetAspectRatio); 729 final float rightAspectRatioDiff = Math.abs(rightAspectRatio - mTargetAspectRatio); 730 if (leftAspectRatioDiff != rightAspectRatioDiff) { 731 return (leftAspectRatioDiff - rightAspectRatioDiff) < 0 ? PREFER_LEFT : PREFER_RIGHT; 732 } 733 734 // At this point they have the same aspect ratio diff and are either both bigger 735 // than the max size or both smaller than the max size, so prefer the one closest 736 // to target size 737 final int leftDiff = Math.abs((left.width * left.height) - mTargetPixels); 738 final int rightDiff = Math.abs((right.width * right.height) - mTargetPixels); 739 return leftDiff - rightDiff; 740 } 741 } 742 743 @Override // From FocusOverlayManager.Listener autoFocus()744 public void autoFocus() { 745 if (mCamera == null) { 746 return; 747 } 748 749 try { 750 mCamera.autoFocus( 751 new Camera.AutoFocusCallback() { 752 @Override 753 public void onAutoFocus(final boolean success, final Camera camera) { 754 mFocusOverlayManager.onAutoFocus(success, false /* shutterDown */); 755 } 756 }); 757 } catch (final RuntimeException e) { 758 LogUtil.e("CameraManager.autoFocus", "RuntimeException in CameraManager.autoFocus", e); 759 // If autofocus fails, the camera should have called the callback with success=false, 760 // but some throw an exception here 761 mFocusOverlayManager.onAutoFocus(false /*success*/, false /*shutterDown*/); 762 } 763 } 764 765 @Override // From FocusOverlayManager.Listener cancelAutoFocus()766 public void cancelAutoFocus() { 767 if (mCamera == null) { 768 return; 769 } 770 try { 771 mCamera.cancelAutoFocus(); 772 } catch (final RuntimeException e) { 773 // Ignore 774 LogUtil.e( 775 "CameraManager.cancelAutoFocus", "RuntimeException in CameraManager.cancelAutoFocus", e); 776 } 777 } 778 779 @Override // From FocusOverlayManager.Listener capture()780 public boolean capture() { 781 return false; 782 } 783 784 @Override // From FocusOverlayManager.Listener setFocusParameters()785 public void setFocusParameters() { 786 if (mCamera == null) { 787 return; 788 } 789 try { 790 final Camera.Parameters parameters = mCamera.getParameters(); 791 parameters.setFocusMode(mFocusOverlayManager.getFocusMode()); 792 if (parameters.getMaxNumFocusAreas() > 0) { 793 // Don't set focus areas (even to null) if focus areas aren't supported, camera may 794 // crash 795 parameters.setFocusAreas(mFocusOverlayManager.getFocusAreas()); 796 } 797 parameters.setMeteringAreas(mFocusOverlayManager.getMeteringAreas()); 798 mCamera.setParameters(parameters); 799 } catch (final RuntimeException e) { 800 // This occurs when the device is out of space or when the camera is locked 801 LogUtil.e( 802 "CameraManager.setFocusParameters", 803 "RuntimeException in CameraManager setFocusParameters"); 804 } 805 } 806 resetPreview()807 public void resetPreview() { 808 mCamera.startPreview(); 809 if (mCameraPreview != null) { 810 mCameraPreview.setFocusable(true); 811 } 812 } 813 logCameraSize(final String prefix, final Camera.Size size)814 private void logCameraSize(final String prefix, final Camera.Size size) { 815 // Log the camera size and aspect ratio for help when examining bug reports for camera 816 // failures 817 LogUtil.i( 818 "CameraManager.logCameraSize", 819 prefix + size.width + "x" + size.height + " (" + (size.width / (float) size.height) + ")"); 820 } 821 822 @VisibleForTesting resetCameraManager()823 public void resetCameraManager() { 824 sInstance = null; 825 } 826 } 827