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