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 { 62 void onCameraError(int errorCode, Exception e); 63 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 72 void onMediaReady(Uri uriToMedia, String contentType, int width, int height); 73 74 void onMediaFailed(Exception exception); 75 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 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 */ 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 */ 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 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 */ 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 */ 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 283 public int getCameraIndex() { 284 return mCameraIndex; 285 } 286 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 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 { 678 OrientationHandler(final Context context) { 679 super(context); 680 } 681 682 @Override 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 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 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 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 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 780 public boolean capture() { 781 return false; 782 } 783 784 @Override // From FocusOverlayManager.Listener 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 807 public void resetPreview() { 808 mCamera.startPreview(); 809 if (mCameraPreview != null) { 810 mCameraPreview.setFocusable(true); 811 } 812 } 813 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 823 public void resetCameraManager() { 824 sInstance = null; 825 } 826 } 827