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