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   /**
    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