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 17package com.android.dialer.callcomposer.camera; 18 19import android.content.Context; 20import android.hardware.Camera; 21import android.hardware.Camera.CameraInfo; 22import android.net.Uri; 23import android.os.AsyncTask; 24import android.os.Looper; 25import android.support.annotation.NonNull; 26import android.support.annotation.Nullable; 27import android.support.annotation.VisibleForTesting; 28import android.text.TextUtils; 29import android.view.MotionEvent; 30import android.view.OrientationEventListener; 31import android.view.Surface; 32import android.view.View; 33import android.view.WindowManager; 34import com.android.dialer.callcomposer.camera.camerafocus.FocusOverlayManager; 35import com.android.dialer.callcomposer.camera.camerafocus.RenderOverlay; 36import com.android.dialer.common.Assert; 37import com.android.dialer.common.LogUtil; 38import java.io.IOException; 39import java.util.ArrayList; 40import java.util.Collections; 41import java.util.Comparator; 42import 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 */ 59public 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 /** 513 * Updates the orientation of the {@link Camera} w.r.t. the orientation of the device and the 514 * orientation that the physical camera is mounted on the device. 515 * 516 * @param camera that needs to be reorientated 517 * @param screenRotation rotation of the physical device 518 * @param cameraOrientation {@link CameraInfo#orientation} 519 * @param cameraIsFrontFacing {@link CameraInfo#CAMERA_FACING_FRONT} 520 * @return rotation that images returned from {@link 521 * android.hardware.Camera.PictureCallback#onPictureTaken(byte[], Camera)} will be rotated. 522 */ 523 @VisibleForTesting 524 static int updateCameraRotation( 525 @NonNull Camera camera, 526 int screenRotation, 527 int cameraOrientation, 528 boolean cameraIsFrontFacing) { 529 Assert.isNotNull(camera); 530 Assert.checkArgument(cameraOrientation % 90 == 0); 531 532 int rotation = screenRotationToDegress(screenRotation); 533 boolean portrait = rotation == 0 || rotation == 180; 534 535 if (!portrait && !cameraIsFrontFacing) { 536 rotation += 180; 537 } 538 rotation += cameraOrientation; 539 rotation %= 360; 540 541 // Rotate the camera 542 if (portrait && cameraIsFrontFacing) { 543 camera.setDisplayOrientation((rotation + 180) % 360); 544 } else { 545 camera.setDisplayOrientation(rotation); 546 } 547 548 // Rotate the images returned when a picture is taken 549 Camera.Parameters params = camera.getParameters(); 550 params.setRotation(rotation); 551 camera.setParameters(params); 552 return rotation; 553 } 554 555 private static int screenRotationToDegress(int screenRotation) { 556 switch (screenRotation) { 557 case Surface.ROTATION_0: 558 return 0; 559 case Surface.ROTATION_90: 560 return 90; 561 case Surface.ROTATION_180: 562 return 180; 563 case Surface.ROTATION_270: 564 return 270; 565 default: 566 throw Assert.createIllegalStateFailException("Invalid surface rotation."); 567 } 568 } 569 570 /** Sets the current camera, releasing any previously opened camera */ 571 private void setCamera(final Camera camera) { 572 if (mCamera == camera) { 573 return; 574 } 575 576 releaseCamera(mCamera); 577 mCamera = camera; 578 tryShowPreview(); 579 if (mListener != null) { 580 mListener.onCameraChanged(); 581 } 582 } 583 584 /** Shows the preview if the camera is open and the preview is loaded */ 585 private void tryShowPreview() { 586 if (mCameraPreview == null || mCamera == null) { 587 if (mOrientationHandler != null) { 588 mOrientationHandler.disable(); 589 mOrientationHandler = null; 590 } 591 mFocusOverlayManager.onPreviewStopped(); 592 return; 593 } 594 try { 595 mCamera.stopPreview(); 596 if (!mTakingPicture) { 597 mRotation = 598 updateCameraRotation( 599 mCamera, 600 getScreenRotation(), 601 mCameraInfo.orientation, 602 mCameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT); 603 } 604 605 final Camera.Parameters params = mCamera.getParameters(); 606 final Camera.Size pictureSize = chooseBestPictureSize(); 607 final Camera.Size previewSize = chooseBestPreviewSize(pictureSize); 608 params.setPreviewSize(previewSize.width, previewSize.height); 609 params.setPictureSize(pictureSize.width, pictureSize.height); 610 logCameraSize("Setting preview size: ", previewSize); 611 logCameraSize("Setting picture size: ", pictureSize); 612 mCameraPreview.setSize(previewSize, mCameraInfo.orientation); 613 for (final String focusMode : params.getSupportedFocusModes()) { 614 if (TextUtils.equals(focusMode, Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { 615 // Use continuous focus if available 616 params.setFocusMode(focusMode); 617 break; 618 } 619 } 620 621 mCamera.setParameters(params); 622 mCameraPreview.startPreview(mCamera); 623 mCamera.startPreview(); 624 mCamera.setAutoFocusMoveCallback( 625 new Camera.AutoFocusMoveCallback() { 626 @Override 627 public void onAutoFocusMoving(final boolean start, final Camera camera) { 628 mFocusOverlayManager.onAutoFocusMoving(start); 629 } 630 }); 631 mFocusOverlayManager.setParameters(mCamera.getParameters()); 632 mFocusOverlayManager.setMirror(mCameraInfo.facing == CameraInfo.CAMERA_FACING_BACK); 633 mFocusOverlayManager.onPreviewStarted(); 634 if (mOrientationHandler == null) { 635 mOrientationHandler = new OrientationHandler(mCameraPreview.getContext()); 636 mOrientationHandler.enable(); 637 } 638 } catch (final IOException e) { 639 LogUtil.e("CameraManager.tryShowPreview", "IOException in CameraManager.tryShowPreview", e); 640 if (mListener != null) { 641 mListener.onCameraError(ERROR_SHOWING_PREVIEW, e); 642 } 643 } catch (final RuntimeException e) { 644 LogUtil.e( 645 "CameraManager.tryShowPreview", "RuntimeException in CameraManager.tryShowPreview", e); 646 if (mListener != null) { 647 mListener.onCameraError(ERROR_SHOWING_PREVIEW, e); 648 } 649 } 650 } 651 652 private int getScreenRotation() { 653 return mCameraPreview 654 .getContext() 655 .getSystemService(WindowManager.class) 656 .getDefaultDisplay() 657 .getRotation(); 658 } 659 660 public boolean isCameraAvailable() { 661 return mCamera != null && !mTakingPicture && mIsHardwareAccelerationSupported; 662 } 663 664 /** 665 * Choose the best picture size by trying to find a size close to the MmsConfig's max size, which 666 * is closest to the screen aspect ratio. In case of RCS conversation returns default size. 667 */ 668 private Camera.Size chooseBestPictureSize() { 669 return mCamera.getParameters().getPictureSize(); 670 } 671 672 /** 673 * Chose the best preview size based on the picture size. Try to find a size with the same aspect 674 * ratio and size as the picture if possible 675 */ 676 private Camera.Size chooseBestPreviewSize(final Camera.Size pictureSize) { 677 final List<Camera.Size> sizes = 678 new ArrayList<Camera.Size>(mCamera.getParameters().getSupportedPreviewSizes()); 679 final float aspectRatio = pictureSize.width / (float) pictureSize.height; 680 final int capturePixels = pictureSize.width * pictureSize.height; 681 682 // Sort the sizes so the best size is first 683 Collections.sort( 684 sizes, 685 new SizeComparator(Integer.MAX_VALUE, Integer.MAX_VALUE, aspectRatio, capturePixels)); 686 687 return sizes.get(0); 688 } 689 690 private class OrientationHandler extends OrientationEventListener { 691 OrientationHandler(final Context context) { 692 super(context); 693 } 694 695 @Override 696 public void onOrientationChanged(final int orientation) { 697 if (!mTakingPicture) { 698 mRotation = 699 updateCameraRotation( 700 mCamera, 701 getScreenRotation(), 702 mCameraInfo.orientation, 703 mCameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT); 704 } 705 } 706 } 707 708 private static class SizeComparator implements Comparator<Camera.Size> { 709 private static final int PREFER_LEFT = -1; 710 private static final int PREFER_RIGHT = 1; 711 712 // The max width/height for the preferred size. Integer.MAX_VALUE if no size limit 713 private final int mMaxWidth; 714 private final int mMaxHeight; 715 716 // The desired aspect ratio 717 private final float mTargetAspectRatio; 718 719 // The desired size (width x height) to try to match 720 private final int mTargetPixels; 721 722 public SizeComparator( 723 final int maxWidth, 724 final int maxHeight, 725 final float targetAspectRatio, 726 final int targetPixels) { 727 mMaxWidth = maxWidth; 728 mMaxHeight = maxHeight; 729 mTargetAspectRatio = targetAspectRatio; 730 mTargetPixels = targetPixels; 731 } 732 733 /** 734 * Returns a negative value if left is a better choice than right, or a positive value if right 735 * is a better choice is better than left. 0 if they are equal 736 */ 737 @Override 738 public int compare(final Camera.Size left, final Camera.Size right) { 739 // If one size is less than the max size prefer it over the other 740 if ((left.width <= mMaxWidth && left.height <= mMaxHeight) 741 != (right.width <= mMaxWidth && right.height <= mMaxHeight)) { 742 return left.width <= mMaxWidth ? PREFER_LEFT : PREFER_RIGHT; 743 } 744 745 // If one is closer to the target aspect ratio, prefer it. 746 final float leftAspectRatio = left.width / (float) left.height; 747 final float rightAspectRatio = right.width / (float) right.height; 748 final float leftAspectRatioDiff = Math.abs(leftAspectRatio - mTargetAspectRatio); 749 final float rightAspectRatioDiff = Math.abs(rightAspectRatio - mTargetAspectRatio); 750 if (leftAspectRatioDiff != rightAspectRatioDiff) { 751 return (leftAspectRatioDiff - rightAspectRatioDiff) < 0 ? PREFER_LEFT : PREFER_RIGHT; 752 } 753 754 // At this point they have the same aspect ratio diff and are either both bigger 755 // than the max size or both smaller than the max size, so prefer the one closest 756 // to target size 757 final int leftDiff = Math.abs((left.width * left.height) - mTargetPixels); 758 final int rightDiff = Math.abs((right.width * right.height) - mTargetPixels); 759 return leftDiff - rightDiff; 760 } 761 } 762 763 @Override // From FocusOverlayManager.Listener 764 public void autoFocus() { 765 if (mCamera == null) { 766 return; 767 } 768 769 try { 770 mCamera.autoFocus( 771 new Camera.AutoFocusCallback() { 772 @Override 773 public void onAutoFocus(final boolean success, final Camera camera) { 774 mFocusOverlayManager.onAutoFocus(success, false /* shutterDown */); 775 } 776 }); 777 } catch (final RuntimeException e) { 778 LogUtil.e("CameraManager.autoFocus", "RuntimeException in CameraManager.autoFocus", e); 779 // If autofocus fails, the camera should have called the callback with success=false, 780 // but some throw an exception here 781 mFocusOverlayManager.onAutoFocus(false /*success*/, false /*shutterDown*/); 782 } 783 } 784 785 @Override // From FocusOverlayManager.Listener 786 public void cancelAutoFocus() { 787 if (mCamera == null) { 788 return; 789 } 790 try { 791 mCamera.cancelAutoFocus(); 792 } catch (final RuntimeException e) { 793 // Ignore 794 LogUtil.e( 795 "CameraManager.cancelAutoFocus", "RuntimeException in CameraManager.cancelAutoFocus", e); 796 } 797 } 798 799 @Override // From FocusOverlayManager.Listener 800 public boolean capture() { 801 return false; 802 } 803 804 @Override // From FocusOverlayManager.Listener 805 public void setFocusParameters() { 806 if (mCamera == null) { 807 return; 808 } 809 try { 810 final Camera.Parameters parameters = mCamera.getParameters(); 811 parameters.setFocusMode(mFocusOverlayManager.getFocusMode()); 812 if (parameters.getMaxNumFocusAreas() > 0) { 813 // Don't set focus areas (even to null) if focus areas aren't supported, camera may 814 // crash 815 parameters.setFocusAreas(mFocusOverlayManager.getFocusAreas()); 816 } 817 parameters.setMeteringAreas(mFocusOverlayManager.getMeteringAreas()); 818 mCamera.setParameters(parameters); 819 } catch (final RuntimeException e) { 820 // This occurs when the device is out of space or when the camera is locked 821 LogUtil.e( 822 "CameraManager.setFocusParameters", 823 "RuntimeException in CameraManager setFocusParameters"); 824 } 825 } 826 827 public void resetPreview() { 828 mCamera.startPreview(); 829 if (mCameraPreview != null) { 830 mCameraPreview.setFocusable(true); 831 } 832 } 833 834 private void logCameraSize(final String prefix, final Camera.Size size) { 835 // Log the camera size and aspect ratio for help when examining bug reports for camera 836 // failures 837 LogUtil.i( 838 "CameraManager.logCameraSize", 839 prefix + size.width + "x" + size.height + " (" + (size.width / (float) size.height) + ")"); 840 } 841 842 @VisibleForTesting 843 public void resetCameraManager() { 844 sInstance = null; 845 } 846} 847