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 { onCameraError(int errorCode, Exception e)63 void onCameraError(int errorCode, Exception e); 64 onCameraChanged()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 onMediaReady(Uri uriToMedia, String contentType, int width, int height)73 void onMediaReady(Uri uriToMedia, String contentType, int width, int height); 74 onMediaFailed(Exception exception)75 void onMediaFailed(Exception exception); 76 onMediaInfo(int what)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 CameraManager()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 */ get()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 */ setSurface(final CameraPreview preview)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 setRenderOverlay(final RenderOverlay renderOverlay)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 */ swapCamera()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 */ selectCamera(final int desiredFacing)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 getCameraIndex()284 public int getCameraIndex() { 285 return cameraIndex; 286 } 287 selectCameraByIndex(final int cameraIndex)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 getCameraInfo()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 */ hasFrontAndBackCamera()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 */ openCamera()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 */ closeCamera()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 */ setListener(final CameraManagerListener listener)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 takePicture(final float heightPercent, @NonNull final MediaCallback callback)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 */ releaseCamera(final Camera camera)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 updateCameraRotation( @onNull Camera camera, int screenRotation, int cameraOrientation, boolean cameraIsFrontFacing)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 screenRotationToDegress(int screenRotation)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 */ setCamera(final Camera 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 */ tryShowPreview()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 getScreenRotation()668 private int getScreenRotation() { 669 return cameraPreview 670 .getContext() 671 .getSystemService(WindowManager.class) 672 .getDefaultDisplay() 673 .getRotation(); 674 } 675 isCameraAvailable()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 */ chooseBestPictureSize()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 */ chooseBestPreviewSize(final Camera.Size pictureSize)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 { OrientationHandler(final Context context)707 OrientationHandler(final Context context) { 708 super(context); 709 } 710 711 @Override onOrientationChanged(final int orientation)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 SizeComparator( final int maxWidth, final int maxHeight, final float targetAspectRatio, final int targetPixels)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 compare(final Camera.Size left, final Camera.Size right)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 autoFocus()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 cancelAutoFocus()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 capture()816 public boolean capture() { 817 return false; 818 } 819 820 @Override // From FocusOverlayManager.Listener setFocusParameters()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 resetPreview()843 public void resetPreview() { 844 camera.startPreview(); 845 if (cameraPreview != null) { 846 cameraPreview.setFocusable(true); 847 } 848 } 849 logCameraSize(final String prefix, final Camera.Size size)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 resetCameraManager()859 public void resetCameraManager() { 860 instance = null; 861 } 862 } 863