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 NOOP_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               NOOP_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