Home | History | Annotate | Download | only in camera
      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 {
     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   /** Updates the orientation of the camera to match the orientation of the device */
    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 */
    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 */
    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 
    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    */
    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    */
    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 {
    678     OrientationHandler(final Context context) {
    679       super(context);
    680     }
    681 
    682     @Override
    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 
    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
    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
    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
    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
    780   public boolean capture() {
    781     return false;
    782   }
    783 
    784   @Override // From FocusOverlayManager.Listener
    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 
    807   public void resetPreview() {
    808     mCamera.startPreview();
    809     if (mCameraPreview != null) {
    810       mCameraPreview.setFocusable(true);
    811     }
    812   }
    813 
    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
    823   public void resetCameraManager() {
    824     sInstance = null;
    825   }
    826 }
    827