Home | History | Annotate | Download | only in mediapicker
      1 /*
      2  * Copyright (C) 2015 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.messaging.ui.mediapicker;
     18 
     19 import android.Manifest;
     20 import android.app.Activity;
     21 import android.content.Context;
     22 import android.content.pm.ActivityInfo;
     23 import android.content.res.Configuration;
     24 import android.content.res.Resources;
     25 import android.hardware.Camera;
     26 import android.hardware.Camera.CameraInfo;
     27 import android.media.MediaRecorder;
     28 import android.net.Uri;
     29 import android.os.AsyncTask;
     30 import android.os.Looper;
     31 import android.support.annotation.NonNull;
     32 import android.text.TextUtils;
     33 import android.util.DisplayMetrics;
     34 import android.view.MotionEvent;
     35 import android.view.OrientationEventListener;
     36 import android.view.Surface;
     37 import android.view.View;
     38 import android.view.WindowManager;
     39 
     40 import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
     41 import com.android.messaging.Factory;
     42 import com.android.messaging.datamodel.data.ParticipantData;
     43 import com.android.messaging.datamodel.media.ImageRequest;
     44 import com.android.messaging.sms.MmsConfig;
     45 import com.android.messaging.ui.mediapicker.camerafocus.FocusOverlayManager;
     46 import com.android.messaging.ui.mediapicker.camerafocus.RenderOverlay;
     47 import com.android.messaging.util.Assert;
     48 import com.android.messaging.util.BugleGservices;
     49 import com.android.messaging.util.BugleGservicesKeys;
     50 import com.android.messaging.util.LogUtil;
     51 import com.android.messaging.util.OsUtil;
     52 import com.android.messaging.util.UiUtils;
     53 import com.google.common.annotations.VisibleForTesting;
     54 
     55 import java.io.FileNotFoundException;
     56 import java.io.IOException;
     57 import java.util.ArrayList;
     58 import java.util.Collections;
     59 import java.util.Comparator;
     60 import java.util.List;
     61 
     62 /**
     63  * Class which manages interactions with the camera, but does not do any UI.  This class is
     64  * designed to be a singleton to ensure there is one component managing the camera and releasing
     65  * the native resources.
     66  * In order to acquire a camera, a caller must:
     67  * <ul>
     68  *     <li>Call selectCamera to select front or back camera
     69  *     <li>Call setSurface to control where the preview is shown
     70  *     <li>Call openCamera to request the camera start preview
     71  * </ul>
     72  * Callers should call onPause and onResume to ensure that the camera is release while the activity
     73  * is not active.
     74  * This class is not thread safe.  It should only be called from one thread (the UI thread or test
     75  * thread)
     76  */
     77 class CameraManager implements FocusOverlayManager.Listener {
     78     /**
     79      * Wrapper around the framework camera API to allow mocking different hardware scenarios while
     80      * unit testing
     81      */
     82     interface CameraWrapper {
     83         int getNumberOfCameras();
     84         void getCameraInfo(int index, CameraInfo cameraInfo);
     85         Camera open(int cameraId);
     86         /** Add a wrapper for release because a final method cannot be mocked */
     87         void release(Camera camera);
     88     }
     89 
     90     /**
     91      * Callbacks for the camera manager listener
     92      */
     93     interface CameraManagerListener {
     94         void onCameraError(int errorCode, Exception e);
     95         void onCameraChanged();
     96     }
     97 
     98     /**
     99      * Callback when taking image or video
    100      */
    101     interface MediaCallback {
    102         static final int MEDIA_CAMERA_CHANGED = 1;
    103         static final int MEDIA_NO_DATA = 2;
    104 
    105         void onMediaReady(Uri uriToMedia, String contentType, int width, int height);
    106         void onMediaFailed(Exception exception);
    107         void onMediaInfo(int what);
    108     }
    109 
    110     // Error codes
    111     static final int ERROR_OPENING_CAMERA = 1;
    112     static final int ERROR_SHOWING_PREVIEW = 2;
    113     static final int ERROR_INITIALIZING_VIDEO = 3;
    114     static final int ERROR_STORAGE_FAILURE = 4;
    115     static final int ERROR_RECORDING_VIDEO = 5;
    116     static final int ERROR_HARDWARE_ACCELERATION_DISABLED = 6;
    117     static final int ERROR_TAKING_PICTURE = 7;
    118 
    119     private static final String TAG = LogUtil.BUGLE_TAG;
    120     private static final int NO_CAMERA_SELECTED = -1;
    121 
    122     private static CameraManager sInstance;
    123 
    124     /** Default camera wrapper which directs calls to the framework APIs */
    125     private static CameraWrapper sCameraWrapper = new CameraWrapper() {
    126         @Override
    127         public int getNumberOfCameras() {
    128             return Camera.getNumberOfCameras();
    129         }
    130 
    131         @Override
    132         public void getCameraInfo(final int index, final CameraInfo cameraInfo) {
    133             Camera.getCameraInfo(index, cameraInfo);
    134         }
    135 
    136         @Override
    137         public Camera open(final int cameraId) {
    138             return Camera.open(cameraId);
    139         }
    140 
    141         @Override
    142         public void release(final Camera camera) {
    143             camera.release();
    144         }
    145     };
    146 
    147     /** The CameraInfo for the currently selected camera */
    148     private final CameraInfo mCameraInfo;
    149 
    150     /**
    151      * The index of the selected camera or NO_CAMERA_SELECTED if a camera hasn't been selected yet
    152      */
    153     private int mCameraIndex;
    154 
    155     /** True if the device has front and back cameras */
    156     private final boolean mHasFrontAndBackCamera;
    157 
    158     /** True if the camera should be open (may not yet be actually open) */
    159     private boolean mOpenRequested;
    160 
    161     /** True if the camera is requested to be in video mode */
    162     private boolean mVideoModeRequested;
    163 
    164     /** The media recorder for video mode */
    165     private MmsVideoRecorder mMediaRecorder;
    166 
    167     /** Callback to call with video recording updates */
    168     private MediaCallback mVideoCallback;
    169 
    170     /** The preview view to show the preview on */
    171     private CameraPreview mCameraPreview;
    172 
    173     /** The helper classs to handle orientation changes */
    174     private OrientationHandler mOrientationHandler;
    175 
    176     /** Tracks whether the preview has hardware acceleration */
    177     private boolean mIsHardwareAccelerationSupported;
    178 
    179     /**
    180      * The task for opening the camera, so it doesn't block the UI thread
    181      * Using AsyncTask rather than SafeAsyncTask because the tasks need to be serialized, but don't
    182      * need to be on the UI thread
    183      * TODO: If we have other AyncTasks (not SafeAsyncTasks) this may contend and we may
    184      * need to create a dedicated thread, or synchronize the threads in the thread pool
    185      */
    186     private AsyncTask<Integer, Void, Camera> mOpenCameraTask;
    187 
    188     /**
    189      * The camera index that is queued to be opened, but not completed yet, or NO_CAMERA_SELECTED if
    190      * no open task is pending
    191      */
    192     private int mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
    193 
    194     /** The instance of the currently opened camera */
    195     private Camera mCamera;
    196 
    197     /** The rotation of the screen relative to the camera's natural orientation */
    198     private int mRotation;
    199 
    200     /** The callback to notify when errors or other events occur */
    201     private CameraManagerListener mListener;
    202 
    203     /** True if the camera is currently in the process of taking an image */
    204     private boolean mTakingPicture;
    205 
    206     /** Provides subscription-related data to access per-subscription configurations. */
    207     private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;
    208 
    209     /** Manages auto focus visual and behavior */
    210     private final FocusOverlayManager mFocusOverlayManager;
    211 
    212     private CameraManager() {
    213         mCameraInfo = new CameraInfo();
    214         mCameraIndex = NO_CAMERA_SELECTED;
    215 
    216         // Check to see if a front and back camera exist
    217         boolean hasFrontCamera = false;
    218         boolean hasBackCamera = false;
    219         final CameraInfo cameraInfo = new CameraInfo();
    220         final int cameraCount = sCameraWrapper.getNumberOfCameras();
    221         try {
    222             for (int i = 0; i < cameraCount; i++) {
    223                 sCameraWrapper.getCameraInfo(i, cameraInfo);
    224                 if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) {
    225                     hasFrontCamera = true;
    226                 } else if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
    227                     hasBackCamera = true;
    228                 }
    229                 if (hasFrontCamera && hasBackCamera) {
    230                     break;
    231                 }
    232             }
    233         } catch (final RuntimeException e) {
    234             LogUtil.e(TAG, "Unable to load camera info", e);
    235         }
    236         mHasFrontAndBackCamera = hasFrontCamera && hasBackCamera;
    237         mFocusOverlayManager = new FocusOverlayManager(this, Looper.getMainLooper());
    238 
    239         // Assume the best until we are proven otherwise
    240         mIsHardwareAccelerationSupported = true;
    241     }
    242 
    243     /** Gets the singleton instance */
    244     static CameraManager get() {
    245         if (sInstance == null) {
    246             sInstance = new CameraManager();
    247         }
    248         return sInstance;
    249     }
    250 
    251     /** Allows tests to inject a custom camera wrapper */
    252     @VisibleForTesting
    253     static void setCameraWrapper(final CameraWrapper cameraWrapper) {
    254         sCameraWrapper = cameraWrapper;
    255         sInstance = null;
    256     }
    257 
    258     /**
    259      * Sets the surface to use to display the preview
    260      * This must only be called AFTER the CameraPreview has a texture ready
    261      * @param preview The preview surface view
    262      */
    263     void setSurface(final CameraPreview preview) {
    264         if (preview == mCameraPreview) {
    265             return;
    266         }
    267 
    268         if (preview != null) {
    269             Assert.isTrue(preview.isValid());
    270             preview.setOnTouchListener(new View.OnTouchListener() {
    271                 @Override
    272                 public boolean onTouch(final View view, final MotionEvent motionEvent) {
    273                     if ((motionEvent.getActionMasked() & MotionEvent.ACTION_UP) ==
    274                             MotionEvent.ACTION_UP) {
    275                         mFocusOverlayManager.setPreviewSize(view.getWidth(), view.getHeight());
    276                         mFocusOverlayManager.onSingleTapUp(
    277                                 (int) motionEvent.getX() + view.getLeft(),
    278                                 (int) motionEvent.getY() + view.getTop());
    279                     }
    280                     return true;
    281                 }
    282             });
    283         }
    284         mCameraPreview = preview;
    285         tryShowPreview();
    286     }
    287 
    288     void setRenderOverlay(final RenderOverlay renderOverlay) {
    289         mFocusOverlayManager.setFocusRenderer(renderOverlay != null ?
    290                 renderOverlay.getPieRenderer() : null);
    291     }
    292 
    293     /** Convenience function to swap between front and back facing cameras */
    294     void swapCamera() {
    295         Assert.isTrue(mCameraIndex >= 0);
    296         selectCamera(mCameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT ?
    297                 CameraInfo.CAMERA_FACING_BACK :
    298                 CameraInfo.CAMERA_FACING_FRONT);
    299     }
    300 
    301     /**
    302      * Selects the first camera facing the desired direction, or the first camera if there is no
    303      * camera in the desired direction
    304      * @param desiredFacing One of the CameraInfo.CAMERA_FACING_* constants
    305      * @return True if a camera was selected, or false if selecting a camera failed
    306      */
    307     boolean selectCamera(final int desiredFacing) {
    308         try {
    309             // We already selected a camera facing that direction
    310             if (mCameraIndex >= 0 && mCameraInfo.facing == desiredFacing) {
    311                 return true;
    312             }
    313 
    314             final int cameraCount = sCameraWrapper.getNumberOfCameras();
    315             Assert.isTrue(cameraCount > 0);
    316 
    317             mCameraIndex = NO_CAMERA_SELECTED;
    318             setCamera(null);
    319             final CameraInfo cameraInfo = new CameraInfo();
    320             for (int i = 0; i < cameraCount; i++) {
    321                 sCameraWrapper.getCameraInfo(i, cameraInfo);
    322                 if (cameraInfo.facing == desiredFacing) {
    323                     mCameraIndex = i;
    324                     sCameraWrapper.getCameraInfo(i, mCameraInfo);
    325                     break;
    326                 }
    327             }
    328 
    329             // There's no camera in the desired facing direction, just select the first camera
    330             // regardless of direction
    331             if (mCameraIndex < 0) {
    332                 mCameraIndex = 0;
    333                 sCameraWrapper.getCameraInfo(0, mCameraInfo);
    334             }
    335 
    336             if (mOpenRequested) {
    337                 // The camera is open, so reopen with the newly selected camera
    338                 openCamera();
    339             }
    340             return true;
    341         } catch (final RuntimeException e) {
    342             LogUtil.e(TAG, "RuntimeException in CameraManager.selectCamera", e);
    343             if (mListener != null) {
    344                 mListener.onCameraError(ERROR_OPENING_CAMERA, e);
    345             }
    346             return false;
    347         }
    348     }
    349 
    350     int getCameraIndex() {
    351         return mCameraIndex;
    352     }
    353 
    354     void selectCameraByIndex(final int cameraIndex) {
    355         if (mCameraIndex == cameraIndex) {
    356             return;
    357         }
    358 
    359         try {
    360             mCameraIndex = cameraIndex;
    361             sCameraWrapper.getCameraInfo(mCameraIndex, mCameraInfo);
    362             if (mOpenRequested) {
    363                 openCamera();
    364             }
    365         } catch (final RuntimeException e) {
    366             LogUtil.e(TAG, "RuntimeException in CameraManager.selectCameraByIndex", e);
    367             if (mListener != null) {
    368                 mListener.onCameraError(ERROR_OPENING_CAMERA, e);
    369             }
    370         }
    371     }
    372 
    373     @VisibleForTesting
    374     CameraInfo getCameraInfo() {
    375         if (mCameraIndex == NO_CAMERA_SELECTED) {
    376             return null;
    377         }
    378         return mCameraInfo;
    379     }
    380 
    381     /** @return True if this device has camera capabilities */
    382     boolean hasAnyCamera() {
    383         return sCameraWrapper.getNumberOfCameras() > 0;
    384     }
    385 
    386     /** @return True if the device has both a front and back camera */
    387     boolean hasFrontAndBackCamera() {
    388         return mHasFrontAndBackCamera;
    389     }
    390 
    391     /**
    392      * Opens the camera on a separate thread and initiates the preview if one is available
    393      */
    394     void openCamera() {
    395         if (mCameraIndex == NO_CAMERA_SELECTED) {
    396             // Ensure a selected camera if none is currently selected. This may happen if the
    397             // camera chooser is not the default media chooser.
    398             selectCamera(CameraInfo.CAMERA_FACING_BACK);
    399         }
    400         mOpenRequested = true;
    401         // We're already opening the camera or already have the camera handle, nothing more to do
    402         if (mPendingOpenCameraIndex == mCameraIndex || mCamera != null) {
    403             return;
    404         }
    405 
    406         // True if the task to open the camera has to be delayed until the current one completes
    407         boolean delayTask = false;
    408 
    409         // Cancel any previous open camera tasks
    410         if (mOpenCameraTask != null) {
    411             mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
    412             delayTask = true;
    413         }
    414 
    415         mPendingOpenCameraIndex = mCameraIndex;
    416         mOpenCameraTask = new AsyncTask<Integer, Void, Camera>() {
    417             private Exception mException;
    418 
    419             @Override
    420             protected Camera doInBackground(final Integer... params) {
    421                 try {
    422                     final int cameraIndex = params[0];
    423                     if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
    424                         LogUtil.v(TAG, "Opening camera " + mCameraIndex);
    425                     }
    426                     return sCameraWrapper.open(cameraIndex);
    427                 } catch (final Exception e) {
    428                     LogUtil.e(TAG, "Exception while opening camera", e);
    429                     mException = e;
    430                     return null;
    431                 }
    432             }
    433 
    434             @Override
    435             protected void onPostExecute(final Camera camera) {
    436                 // If we completed, but no longer want this camera, then release the camera
    437                 if (mOpenCameraTask != this || !mOpenRequested) {
    438                     releaseCamera(camera);
    439                     cleanup();
    440                     return;
    441                 }
    442 
    443                 cleanup();
    444 
    445                 if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
    446                     LogUtil.v(TAG, "Opened camera " + mCameraIndex + " " + (camera != null));
    447                 }
    448 
    449                 setCamera(camera);
    450                 if (camera == null) {
    451                     if (mListener != null) {
    452                         mListener.onCameraError(ERROR_OPENING_CAMERA, mException);
    453                     }
    454                     LogUtil.e(TAG, "Error opening camera");
    455                 }
    456             }
    457 
    458             @Override
    459             protected void onCancelled() {
    460                 super.onCancelled();
    461                 cleanup();
    462             }
    463 
    464             private void cleanup() {
    465                 mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
    466                 if (mOpenCameraTask != null && mOpenCameraTask.getStatus() == Status.PENDING) {
    467                     // If there's another task waiting on this one to complete, start it now
    468                     mOpenCameraTask.execute(mCameraIndex);
    469                 } else {
    470                     mOpenCameraTask = null;
    471                 }
    472 
    473             }
    474         };
    475         if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
    476             LogUtil.v(TAG, "Start opening camera " + mCameraIndex);
    477         }
    478 
    479         if (!delayTask) {
    480             mOpenCameraTask.execute(mCameraIndex);
    481         }
    482     }
    483 
    484     boolean isVideoMode() {
    485         return mVideoModeRequested;
    486     }
    487 
    488     boolean isRecording() {
    489         return mVideoModeRequested && mVideoCallback != null;
    490     }
    491 
    492     void setVideoMode(final boolean videoMode) {
    493         if (mVideoModeRequested == videoMode) {
    494             return;
    495         }
    496         mVideoModeRequested = videoMode;
    497         tryInitOrCleanupVideoMode();
    498     }
    499 
    500     /** Closes the camera releasing the resources it uses */
    501     void closeCamera() {
    502         mOpenRequested = false;
    503         setCamera(null);
    504     }
    505 
    506     /** Temporarily closes the camera if it is open */
    507     void onPause() {
    508         setCamera(null);
    509     }
    510 
    511     /** Reopens the camera if it was opened when onPause was called */
    512     void onResume() {
    513         if (mOpenRequested) {
    514             openCamera();
    515         }
    516     }
    517 
    518     /**
    519      * Sets the listener which will be notified of errors or other events in the camera
    520      * @param listener The listener to notify
    521      */
    522     void setListener(final CameraManagerListener listener) {
    523         Assert.isMainThread();
    524         mListener = listener;
    525         if (!mIsHardwareAccelerationSupported && mListener != null) {
    526             mListener.onCameraError(ERROR_HARDWARE_ACCELERATION_DISABLED, null);
    527         }
    528     }
    529 
    530     void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) {
    531         mSubscriptionDataProvider = provider;
    532     }
    533 
    534     void takePicture(final float heightPercent, @NonNull final MediaCallback callback) {
    535         Assert.isTrue(!mVideoModeRequested);
    536         Assert.isTrue(!mTakingPicture);
    537         Assert.notNull(callback);
    538         if (mCamera == null) {
    539             // The caller should have checked isCameraAvailable first, but just in case, protect
    540             // against a null camera by notifying the callback that taking the picture didn't work
    541             callback.onMediaFailed(null);
    542             return;
    543         }
    544         final Camera.PictureCallback jpegCallback = new Camera.PictureCallback() {
    545             @Override
    546             public void onPictureTaken(final byte[] bytes, final Camera camera) {
    547                 mTakingPicture = false;
    548                 if (mCamera != camera) {
    549                     // This may happen if the camera was changed between front/back while the
    550                     // picture is being taken.
    551                     callback.onMediaInfo(MediaCallback.MEDIA_CAMERA_CHANGED);
    552                     return;
    553                 }
    554 
    555                 if (bytes == null) {
    556                     callback.onMediaInfo(MediaCallback.MEDIA_NO_DATA);
    557                     return;
    558                 }
    559 
    560                 final Camera.Size size = camera.getParameters().getPictureSize();
    561                 int width;
    562                 int height;
    563                 if (mRotation == 90 || mRotation == 270) {
    564                     width = size.height;
    565                     height = size.width;
    566                 } else {
    567                     width = size.width;
    568                     height = size.height;
    569                 }
    570                 new ImagePersistTask(
    571                         width, height, heightPercent, bytes, mCameraPreview.getContext(), callback)
    572                         .executeOnThreadPool();
    573             }
    574         };
    575 
    576         mTakingPicture = true;
    577         try {
    578             mCamera.takePicture(
    579                     null /* shutter */,
    580                     null /* raw */,
    581                     null /* postView */,
    582                     jpegCallback);
    583         } catch (final RuntimeException e) {
    584             LogUtil.e(TAG, "RuntimeException in CameraManager.takePicture", e);
    585             mTakingPicture = false;
    586             if (mListener != null) {
    587                 mListener.onCameraError(ERROR_TAKING_PICTURE, e);
    588             }
    589         }
    590     }
    591 
    592     void startVideo(final MediaCallback callback) {
    593         Assert.notNull(callback);
    594         Assert.isTrue(!isRecording());
    595         mVideoCallback = callback;
    596         tryStartVideoCapture();
    597     }
    598 
    599     /**
    600      * Asynchronously releases a camera
    601      * @param camera The camera to release
    602      */
    603     private void releaseCamera(final Camera camera) {
    604         if (camera == null) {
    605             return;
    606         }
    607 
    608         mFocusOverlayManager.onCameraReleased();
    609 
    610         new AsyncTask<Void, Void, Void>() {
    611             @Override
    612             protected Void doInBackground(final Void... params) {
    613                 if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
    614                     LogUtil.v(TAG, "Releasing camera " + mCameraIndex);
    615                 }
    616                 sCameraWrapper.release(camera);
    617                 return null;
    618             }
    619         }.execute();
    620     }
    621 
    622     private void releaseMediaRecorder(final boolean cleanupFile) {
    623         if (mMediaRecorder == null) {
    624             return;
    625         }
    626         mVideoModeRequested = false;
    627 
    628         if (cleanupFile) {
    629             mMediaRecorder.cleanupTempFile();
    630             if (mVideoCallback != null) {
    631                 final MediaCallback callback = mVideoCallback;
    632                 mVideoCallback = null;
    633                 // Notify the callback that we've stopped recording
    634                 callback.onMediaReady(null /*uri*/, null /*contentType*/, 0 /*width*/,
    635                         0 /*height*/);
    636             }
    637         }
    638 
    639         mMediaRecorder.release();
    640         mMediaRecorder = null;
    641 
    642         if (mCamera != null) {
    643             try {
    644                 mCamera.reconnect();
    645             } catch (final IOException e) {
    646                 LogUtil.e(TAG, "IOException in CameraManager.releaseMediaRecorder", e);
    647                 if (mListener != null) {
    648                     mListener.onCameraError(ERROR_OPENING_CAMERA, e);
    649                 }
    650             } catch (final RuntimeException e) {
    651                 LogUtil.e(TAG, "RuntimeException in CameraManager.releaseMediaRecorder", e);
    652                 if (mListener != null) {
    653                     mListener.onCameraError(ERROR_OPENING_CAMERA, e);
    654                 }
    655             }
    656         }
    657         restoreRequestedOrientation();
    658     }
    659 
    660     /** Updates the orientation of the camera to match the orientation of the device */
    661     private void updateCameraOrientation() {
    662         if (mCamera == null || mCameraPreview == null || mTakingPicture) {
    663             return;
    664         }
    665 
    666         final WindowManager windowManager =
    667                 (WindowManager) mCameraPreview.getContext().getSystemService(
    668                         Context.WINDOW_SERVICE);
    669 
    670         int degrees = 0;
    671         switch (windowManager.getDefaultDisplay().getRotation()) {
    672             case Surface.ROTATION_0: degrees = 0; break;
    673             case Surface.ROTATION_90: degrees = 90; break;
    674             case Surface.ROTATION_180: degrees = 180; break;
    675             case Surface.ROTATION_270: degrees = 270; break;
    676         }
    677 
    678         // The display orientation of the camera (this controls the preview image).
    679         int orientation;
    680 
    681         // The clockwise rotation angle relative to the orientation of the camera. This affects
    682         // pictures returned by the camera in Camera.PictureCallback.
    683         int rotation;
    684         if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
    685             orientation = (mCameraInfo.orientation + degrees) % 360;
    686             rotation = orientation;
    687             // compensate the mirror but only for orientation
    688             orientation = (360 - orientation) % 360;
    689         } else {  // back-facing
    690             orientation = (mCameraInfo.orientation - degrees + 360) % 360;
    691             rotation = orientation;
    692         }
    693         mRotation = rotation;
    694         if (mMediaRecorder == null) {
    695             try {
    696                 mCamera.setDisplayOrientation(orientation);
    697                 final Camera.Parameters params = mCamera.getParameters();
    698                 params.setRotation(rotation);
    699                 mCamera.setParameters(params);
    700             } catch (final RuntimeException e) {
    701                 LogUtil.e(TAG, "RuntimeException in CameraManager.updateCameraOrientation", e);
    702                 if (mListener != null) {
    703                     mListener.onCameraError(ERROR_OPENING_CAMERA, e);
    704                 }
    705             }
    706         }
    707     }
    708 
    709     /** Sets the current camera, releasing any previously opened camera */
    710     private void setCamera(final Camera camera) {
    711         if (mCamera == camera) {
    712             return;
    713         }
    714 
    715         releaseMediaRecorder(true /* cleanupFile */);
    716         releaseCamera(mCamera);
    717         mCamera = camera;
    718         tryShowPreview();
    719         if (mListener != null) {
    720             mListener.onCameraChanged();
    721         }
    722     }
    723 
    724     /** Shows the preview if the camera is open and the preview is loaded */
    725     private void tryShowPreview() {
    726         if (mCameraPreview == null || mCamera == null) {
    727             if (mOrientationHandler != null) {
    728                 mOrientationHandler.disable();
    729                 mOrientationHandler = null;
    730             }
    731             releaseMediaRecorder(true /* cleanupFile */);
    732             mFocusOverlayManager.onPreviewStopped();
    733             return;
    734         }
    735         try {
    736             mCamera.stopPreview();
    737             updateCameraOrientation();
    738 
    739             final Camera.Parameters params = mCamera.getParameters();
    740             final Camera.Size pictureSize = chooseBestPictureSize();
    741             final Camera.Size previewSize = chooseBestPreviewSize(pictureSize);
    742             params.setPreviewSize(previewSize.width, previewSize.height);
    743             params.setPictureSize(pictureSize.width, pictureSize.height);
    744             logCameraSize("Setting preview size: ", previewSize);
    745             logCameraSize("Setting picture size: ", pictureSize);
    746             mCameraPreview.setSize(previewSize, mCameraInfo.orientation);
    747             for (final String focusMode : params.getSupportedFocusModes()) {
    748                 if (TextUtils.equals(focusMode, Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
    749                     // Use continuous focus if available
    750                     params.setFocusMode(focusMode);
    751                     break;
    752                 }
    753             }
    754 
    755             mCamera.setParameters(params);
    756             mCameraPreview.startPreview(mCamera);
    757             mCamera.startPreview();
    758             mCamera.setAutoFocusMoveCallback(new Camera.AutoFocusMoveCallback() {
    759                 @Override
    760                 public void onAutoFocusMoving(final boolean start, final Camera camera) {
    761                     mFocusOverlayManager.onAutoFocusMoving(start);
    762                 }
    763             });
    764             mFocusOverlayManager.setParameters(mCamera.getParameters());
    765             mFocusOverlayManager.setMirror(mCameraInfo.facing == CameraInfo.CAMERA_FACING_BACK);
    766             mFocusOverlayManager.onPreviewStarted();
    767             tryInitOrCleanupVideoMode();
    768             if (mOrientationHandler == null) {
    769                 mOrientationHandler = new OrientationHandler(mCameraPreview.getContext());
    770                 mOrientationHandler.enable();
    771             }
    772         } catch (final IOException e) {
    773             LogUtil.e(TAG, "IOException in CameraManager.tryShowPreview", e);
    774             if (mListener != null) {
    775                 mListener.onCameraError(ERROR_SHOWING_PREVIEW, e);
    776             }
    777         } catch (final RuntimeException e) {
    778             LogUtil.e(TAG, "RuntimeException in CameraManager.tryShowPreview", e);
    779             if (mListener != null) {
    780                 mListener.onCameraError(ERROR_SHOWING_PREVIEW, e);
    781             }
    782         }
    783     }
    784 
    785     private void tryInitOrCleanupVideoMode() {
    786         if (!mVideoModeRequested || mCamera == null || mCameraPreview == null) {
    787             releaseMediaRecorder(true /* cleanupFile */);
    788             return;
    789         }
    790 
    791         if (mMediaRecorder != null) {
    792             return;
    793         }
    794 
    795         try {
    796             mCamera.unlock();
    797             final int maxMessageSize = getMmsConfig().getMaxMessageSize();
    798             mMediaRecorder = new MmsVideoRecorder(mCamera, mCameraIndex, mRotation, maxMessageSize);
    799             mMediaRecorder.prepare();
    800         } catch (final FileNotFoundException e) {
    801             LogUtil.e(TAG, "FileNotFoundException in CameraManager.tryInitOrCleanupVideoMode", e);
    802             if (mListener != null) {
    803                 mListener.onCameraError(ERROR_STORAGE_FAILURE, e);
    804             }
    805             setVideoMode(false);
    806             return;
    807         } catch (final IOException e) {
    808             LogUtil.e(TAG, "IOException in CameraManager.tryInitOrCleanupVideoMode", e);
    809             if (mListener != null) {
    810                 mListener.onCameraError(ERROR_INITIALIZING_VIDEO, e);
    811             }
    812             setVideoMode(false);
    813             return;
    814         } catch (final RuntimeException e) {
    815             LogUtil.e(TAG, "RuntimeException in CameraManager.tryInitOrCleanupVideoMode", e);
    816             if (mListener != null) {
    817                 mListener.onCameraError(ERROR_INITIALIZING_VIDEO, e);
    818             }
    819             setVideoMode(false);
    820             return;
    821         }
    822 
    823         tryStartVideoCapture();
    824     }
    825 
    826     private void tryStartVideoCapture() {
    827         if (mMediaRecorder == null || mVideoCallback == null) {
    828             return;
    829         }
    830 
    831         mMediaRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() {
    832             @Override
    833             public void onError(final MediaRecorder mediaRecorder, final int what,
    834                     final int extra) {
    835                 if (mListener != null) {
    836                     mListener.onCameraError(ERROR_RECORDING_VIDEO, null);
    837                 }
    838                 restoreRequestedOrientation();
    839             }
    840         });
    841 
    842         mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
    843             @Override
    844             public void onInfo(final MediaRecorder mediaRecorder, final int what, final int extra) {
    845                 if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED ||
    846                         what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
    847                     stopVideo();
    848                 }
    849             }
    850         });
    851 
    852         try {
    853             mMediaRecorder.start();
    854             final Activity activity = UiUtils.getActivity(mCameraPreview.getContext());
    855             activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    856             lockOrientation();
    857         } catch (final IllegalStateException e) {
    858             LogUtil.e(TAG, "IllegalStateException in CameraManager.tryStartVideoCapture", e);
    859             if (mListener != null) {
    860                 mListener.onCameraError(ERROR_RECORDING_VIDEO, e);
    861             }
    862             setVideoMode(false);
    863             restoreRequestedOrientation();
    864         } catch (final RuntimeException e) {
    865             LogUtil.e(TAG, "RuntimeException in CameraManager.tryStartVideoCapture", e);
    866             if (mListener != null) {
    867                 mListener.onCameraError(ERROR_RECORDING_VIDEO, e);
    868             }
    869             setVideoMode(false);
    870             restoreRequestedOrientation();
    871         }
    872     }
    873 
    874     void stopVideo() {
    875         int width = ImageRequest.UNSPECIFIED_SIZE;
    876         int height = ImageRequest.UNSPECIFIED_SIZE;
    877         Uri uri = null;
    878         String contentType = null;
    879         try {
    880             final Activity activity = UiUtils.getActivity(mCameraPreview.getContext());
    881             activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    882             mMediaRecorder.stop();
    883             width = mMediaRecorder.getVideoWidth();
    884             height = mMediaRecorder.getVideoHeight();
    885             uri = mMediaRecorder.getVideoUri();
    886             contentType = mMediaRecorder.getContentType();
    887         } catch (final RuntimeException e) {
    888             // MediaRecorder.stop will throw a RuntimeException if the video was too short, let the
    889             // finally clause call the callback with null uri and handle cleanup
    890             LogUtil.e(TAG, "RuntimeException in CameraManager.stopVideo", e);
    891         } finally {
    892             final MediaCallback videoCallback = mVideoCallback;
    893             mVideoCallback = null;
    894             releaseMediaRecorder(false /* cleanupFile */);
    895             if (uri == null) {
    896                 tryInitOrCleanupVideoMode();
    897             }
    898             videoCallback.onMediaReady(uri, contentType, width, height);
    899         }
    900     }
    901 
    902     boolean isCameraAvailable() {
    903         return mCamera != null && !mTakingPicture && mIsHardwareAccelerationSupported;
    904     }
    905 
    906     /**
    907      * External components call into this to report if hardware acceleration is supported.  When
    908      * hardware acceleration isn't supported, we need to report an error through the listener
    909      * interface
    910      * @param isHardwareAccelerationSupported True if the preview is rendering in a hardware
    911      *                                        accelerated view.
    912      */
    913     void reportHardwareAccelerationSupported(final boolean isHardwareAccelerationSupported) {
    914         Assert.isMainThread();
    915         if (mIsHardwareAccelerationSupported == isHardwareAccelerationSupported) {
    916             // If the value hasn't changed nothing more to do
    917             return;
    918         }
    919 
    920         mIsHardwareAccelerationSupported = isHardwareAccelerationSupported;
    921         if (!isHardwareAccelerationSupported) {
    922             LogUtil.e(TAG, "Software rendering - cannot open camera");
    923             if (mListener != null) {
    924                 mListener.onCameraError(ERROR_HARDWARE_ACCELERATION_DISABLED, null);
    925             }
    926         }
    927     }
    928 
    929     /** Returns the scale factor to scale the width/height to max allowed in MmsConfig */
    930     private float getScaleFactorForMaxAllowedSize(final int width, final int height,
    931             final int maxWidth, final int maxHeight) {
    932         if (maxWidth <= 0 || maxHeight <= 0) {
    933             // MmsConfig initialization runs asynchronously on application startup, so there's a
    934             // chance (albeit a very slight one) that we don't have it yet.
    935             LogUtil.w(LogUtil.BUGLE_TAG, "Max image size not loaded in MmsConfig");
    936             return 1.0f;
    937         }
    938 
    939         if (width <= maxWidth && height <= maxHeight) {
    940             // Already meeting requirements.
    941             return 1.0f;
    942         }
    943 
    944         return Math.min(maxWidth * 1.0f / width, maxHeight * 1.0f / height);
    945     }
    946 
    947     private MmsConfig getMmsConfig() {
    948         final int subId = mSubscriptionDataProvider != null ?
    949                 mSubscriptionDataProvider.getConversationSelfSubId() :
    950                 ParticipantData.DEFAULT_SELF_SUB_ID;
    951         return MmsConfig.get(subId);
    952     }
    953 
    954     /**
    955      * Choose the best picture size by trying to find a size close to the MmsConfig's max size,
    956      * which is closest to the screen aspect ratio
    957      */
    958     private Camera.Size chooseBestPictureSize() {
    959         final Context context = mCameraPreview.getContext();
    960         final Resources resources = context.getResources();
    961         final DisplayMetrics displayMetrics = resources.getDisplayMetrics();
    962         final int displayOrientation = resources.getConfiguration().orientation;
    963         int cameraOrientation = mCameraInfo.orientation;
    964 
    965         int screenWidth;
    966         int screenHeight;
    967         if (displayOrientation == Configuration.ORIENTATION_LANDSCAPE) {
    968             // Rotate the camera orientation 90 degrees to compensate for the rotated display
    969             // metrics. Direction doesn't matter because we're just using it for width/height
    970             cameraOrientation += 90;
    971         }
    972 
    973         // Check the camera orientation relative to the display.
    974         // For 0, 180, 360, the screen width/height are the display width/height
    975         // For 90, 270, the screen width/height are inverted from the display
    976         if (cameraOrientation % 180 == 0) {
    977             screenWidth = displayMetrics.widthPixels;
    978             screenHeight = displayMetrics.heightPixels;
    979         } else {
    980             screenWidth = displayMetrics.heightPixels;
    981             screenHeight = displayMetrics.widthPixels;
    982         }
    983 
    984         final MmsConfig mmsConfig = getMmsConfig();
    985         final int maxWidth = mmsConfig.getMaxImageWidth();
    986         final int maxHeight = mmsConfig.getMaxImageHeight();
    987 
    988         // Constrain the size within the max width/height defined by MmsConfig.
    989         final float scaleFactor = getScaleFactorForMaxAllowedSize(screenWidth, screenHeight,
    990                 maxWidth, maxHeight);
    991         screenWidth *= scaleFactor;
    992         screenHeight *= scaleFactor;
    993 
    994         final float aspectRatio = BugleGservices.get().getFloat(
    995                 BugleGservicesKeys.CAMERA_ASPECT_RATIO,
    996                 screenWidth / (float) screenHeight);
    997         final List<Camera.Size> sizes = new ArrayList<Camera.Size>(
    998                 mCamera.getParameters().getSupportedPictureSizes());
    999         final int maxPixels = maxWidth * maxHeight;
   1000 
   1001         // Sort the sizes so the best size is first
   1002         Collections.sort(sizes, new SizeComparator(maxWidth, maxHeight, aspectRatio, maxPixels));
   1003 
   1004         return sizes.get(0);
   1005     }
   1006 
   1007     /**
   1008      * Chose the best preview size based on the picture size.  Try to find a size with the same
   1009      * aspect ratio and size as the picture if possible
   1010      */
   1011     private Camera.Size chooseBestPreviewSize(final Camera.Size pictureSize) {
   1012         final List<Camera.Size> sizes = new ArrayList<Camera.Size>(
   1013                 mCamera.getParameters().getSupportedPreviewSizes());
   1014         final float aspectRatio = pictureSize.width / (float) pictureSize.height;
   1015         final int capturePixels = pictureSize.width * pictureSize.height;
   1016 
   1017         // Sort the sizes so the best size is first
   1018         Collections.sort(sizes, new SizeComparator(Integer.MAX_VALUE, Integer.MAX_VALUE,
   1019                 aspectRatio, capturePixels));
   1020 
   1021         return sizes.get(0);
   1022     }
   1023 
   1024     private class OrientationHandler extends OrientationEventListener {
   1025         OrientationHandler(final Context context) {
   1026             super(context);
   1027         }
   1028 
   1029         @Override
   1030         public void onOrientationChanged(final int orientation) {
   1031             updateCameraOrientation();
   1032         }
   1033     }
   1034 
   1035     private static class SizeComparator implements Comparator<Camera.Size> {
   1036         private static final int PREFER_LEFT = -1;
   1037         private static final int PREFER_RIGHT = 1;
   1038 
   1039         // The max width/height for the preferred size. Integer.MAX_VALUE if no size limit
   1040         private final int mMaxWidth;
   1041         private final int mMaxHeight;
   1042 
   1043         // The desired aspect ratio
   1044         private final float mTargetAspectRatio;
   1045 
   1046         // The desired size (width x height) to try to match
   1047         private final int mTargetPixels;
   1048 
   1049         public SizeComparator(final int maxWidth, final int maxHeight,
   1050                               final float targetAspectRatio, final int targetPixels) {
   1051             mMaxWidth = maxWidth;
   1052             mMaxHeight = maxHeight;
   1053             mTargetAspectRatio = targetAspectRatio;
   1054             mTargetPixels = targetPixels;
   1055         }
   1056 
   1057         /**
   1058          * Returns a negative value if left is a better choice than right, or a positive value if
   1059          * right is a better choice is better than left.  0 if they are equal
   1060          */
   1061         @Override
   1062         public int compare(final Camera.Size left, final Camera.Size right) {
   1063             // If one size is less than the max size prefer it over the other
   1064             if ((left.width <= mMaxWidth && left.height <= mMaxHeight) !=
   1065                     (right.width <= mMaxWidth && right.height <= mMaxHeight)) {
   1066                 return left.width <= mMaxWidth ? PREFER_LEFT : PREFER_RIGHT;
   1067             }
   1068 
   1069             // If one is closer to the target aspect ratio, prefer it.
   1070             final float leftAspectRatio = left.width / (float) left.height;
   1071             final float rightAspectRatio = right.width / (float) right.height;
   1072             final float leftAspectRatioDiff = Math.abs(leftAspectRatio - mTargetAspectRatio);
   1073             final float rightAspectRatioDiff = Math.abs(rightAspectRatio - mTargetAspectRatio);
   1074             if (leftAspectRatioDiff != rightAspectRatioDiff) {
   1075                 return (leftAspectRatioDiff - rightAspectRatioDiff) < 0 ?
   1076                         PREFER_LEFT : PREFER_RIGHT;
   1077             }
   1078 
   1079             // At this point they have the same aspect ratio diff and are either both bigger
   1080             // than the max size or both smaller than the max size, so prefer the one closest
   1081             // to target size
   1082             final int leftDiff = Math.abs((left.width * left.height) - mTargetPixels);
   1083             final int rightDiff = Math.abs((right.width * right.height) - mTargetPixels);
   1084             return leftDiff - rightDiff;
   1085         }
   1086     }
   1087 
   1088     @Override // From FocusOverlayManager.Listener
   1089     public void autoFocus() {
   1090         if (mCamera == null) {
   1091             return;
   1092         }
   1093 
   1094         try {
   1095             mCamera.autoFocus(new Camera.AutoFocusCallback() {
   1096                 @Override
   1097                 public void onAutoFocus(final boolean success, final Camera camera) {
   1098                     mFocusOverlayManager.onAutoFocus(success, false /* shutterDown */);
   1099                 }
   1100             });
   1101         } catch (final RuntimeException e) {
   1102             LogUtil.e(TAG, "RuntimeException in CameraManager.autoFocus", e);
   1103             // If autofocus fails, the camera should have called the callback with success=false,
   1104             // but some throw an exception here
   1105             mFocusOverlayManager.onAutoFocus(false /*success*/, false /*shutterDown*/);
   1106         }
   1107     }
   1108 
   1109     @Override // From FocusOverlayManager.Listener
   1110     public void cancelAutoFocus() {
   1111         if (mCamera == null) {
   1112             return;
   1113         }
   1114         try {
   1115             mCamera.cancelAutoFocus();
   1116         } catch (final RuntimeException e) {
   1117             // Ignore
   1118             LogUtil.e(TAG, "RuntimeException in CameraManager.cancelAutoFocus", e);
   1119         }
   1120     }
   1121 
   1122     @Override // From FocusOverlayManager.Listener
   1123     public boolean capture() {
   1124         return false;
   1125     }
   1126 
   1127     @Override // From FocusOverlayManager.Listener
   1128     public void setFocusParameters() {
   1129         if (mCamera == null) {
   1130             return;
   1131         }
   1132         try {
   1133             final Camera.Parameters parameters = mCamera.getParameters();
   1134             parameters.setFocusMode(mFocusOverlayManager.getFocusMode());
   1135             if (parameters.getMaxNumFocusAreas() > 0) {
   1136                 // Don't set focus areas (even to null) if focus areas aren't supported, camera may
   1137                 // crash
   1138                 parameters.setFocusAreas(mFocusOverlayManager.getFocusAreas());
   1139             }
   1140             parameters.setMeteringAreas(mFocusOverlayManager.getMeteringAreas());
   1141             mCamera.setParameters(parameters);
   1142         } catch (final RuntimeException e) {
   1143             // This occurs when the device is out of space or when the camera is locked
   1144             LogUtil.e(TAG, "RuntimeException in CameraManager setFocusParameters");
   1145         }
   1146     }
   1147 
   1148     private void logCameraSize(final String prefix, final Camera.Size size) {
   1149         // Log the camera size and aspect ratio for help when examining bug reports for camera
   1150         // failures
   1151         LogUtil.i(TAG, prefix + size.width + "x" + size.height +
   1152                 " (" + (size.width / (float) size.height) + ")");
   1153     }
   1154 
   1155 
   1156     private Integer mSavedOrientation = null;
   1157 
   1158     private void lockOrientation() {
   1159         // when we start recording, lock our orientation
   1160         final Activity a = UiUtils.getActivity(mCameraPreview.getContext());
   1161         final WindowManager windowManager =
   1162                 (WindowManager) a.getSystemService(Context.WINDOW_SERVICE);
   1163         final int rotation = windowManager.getDefaultDisplay().getRotation();
   1164 
   1165         mSavedOrientation = a.getRequestedOrientation();
   1166         switch (rotation) {
   1167             case Surface.ROTATION_0:
   1168                 a.setRequestedOrientation(
   1169                         ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
   1170                 break;
   1171             case Surface.ROTATION_90:
   1172                 a.setRequestedOrientation(
   1173                         ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
   1174                 break;
   1175             case Surface.ROTATION_180:
   1176                 a.setRequestedOrientation(
   1177                         ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
   1178                 break;
   1179             case Surface.ROTATION_270:
   1180                 a.setRequestedOrientation(
   1181                         ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
   1182                 break;
   1183         }
   1184 
   1185     }
   1186 
   1187     private void restoreRequestedOrientation() {
   1188         if (mSavedOrientation != null) {
   1189             final Activity a = UiUtils.getActivity(mCameraPreview.getContext());
   1190             if (a != null) {
   1191                 a.setRequestedOrientation(mSavedOrientation);
   1192             }
   1193             mSavedOrientation = null;
   1194         }
   1195     }
   1196 
   1197     static boolean hasCameraPermission() {
   1198         return OsUtil.hasPermission(Manifest.permission.CAMERA);
   1199     }
   1200 }
   1201