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