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