summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/ui/mediapicker/CameraManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/messaging/ui/mediapicker/CameraManager.java')
-rw-r--r--src/com/android/messaging/ui/mediapicker/CameraManager.java1200
1 files changed, 0 insertions, 1200 deletions
diff --git a/src/com/android/messaging/ui/mediapicker/CameraManager.java b/src/com/android/messaging/ui/mediapicker/CameraManager.java
deleted file mode 100644
index 166ebd7..0000000
--- a/src/com/android/messaging/ui/mediapicker/CameraManager.java
+++ /dev/null
@@ -1,1200 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.hardware.Camera;
-import android.hardware.Camera.CameraInfo;
-import android.media.MediaRecorder;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Looper;
-import android.support.annotation.NonNull;
-import android.text.TextUtils;
-import android.util.DisplayMetrics;
-import android.view.MotionEvent;
-import android.view.OrientationEventListener;
-import android.view.Surface;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.media.ImageRequest;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.ui.mediapicker.camerafocus.FocusOverlayManager;
-import com.android.messaging.ui.mediapicker.camerafocus.RenderOverlay;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * Class which manages interactions with the camera, but does not do any UI. This class is
- * designed to be a singleton to ensure there is one component managing the camera and releasing
- * the native resources.
- * In order to acquire a camera, a caller must:
- * <ul>
- * <li>Call selectCamera to select front or back camera
- * <li>Call setSurface to control where the preview is shown
- * <li>Call openCamera to request the camera start preview
- * </ul>
- * Callers should call onPause and onResume to ensure that the camera is release while the activity
- * is not active.
- * This class is not thread safe. It should only be called from one thread (the UI thread or test
- * thread)
- */
-class CameraManager implements FocusOverlayManager.Listener {
- /**
- * Wrapper around the framework camera API to allow mocking different hardware scenarios while
- * unit testing
- */
- interface CameraWrapper {
- int getNumberOfCameras();
- void getCameraInfo(int index, CameraInfo cameraInfo);
- Camera open(int cameraId);
- /** Add a wrapper for release because a final method cannot be mocked */
- void release(Camera camera);
- }
-
- /**
- * Callbacks for the camera manager listener
- */
- interface CameraManagerListener {
- void onCameraError(int errorCode, Exception e);
- void onCameraChanged();
- }
-
- /**
- * Callback when taking image or video
- */
- interface MediaCallback {
- static final int MEDIA_CAMERA_CHANGED = 1;
- static final int MEDIA_NO_DATA = 2;
-
- void onMediaReady(Uri uriToMedia, String contentType, int width, int height);
- void onMediaFailed(Exception exception);
- void onMediaInfo(int what);
- }
-
- // Error codes
- static final int ERROR_OPENING_CAMERA = 1;
- static final int ERROR_SHOWING_PREVIEW = 2;
- static final int ERROR_INITIALIZING_VIDEO = 3;
- static final int ERROR_STORAGE_FAILURE = 4;
- static final int ERROR_RECORDING_VIDEO = 5;
- static final int ERROR_HARDWARE_ACCELERATION_DISABLED = 6;
- static final int ERROR_TAKING_PICTURE = 7;
-
- private static final String TAG = LogUtil.BUGLE_TAG;
- private static final int NO_CAMERA_SELECTED = -1;
-
- private static CameraManager sInstance;
-
- /** Default camera wrapper which directs calls to the framework APIs */
- private static CameraWrapper sCameraWrapper = new CameraWrapper() {
- @Override
- public int getNumberOfCameras() {
- return Camera.getNumberOfCameras();
- }
-
- @Override
- public void getCameraInfo(final int index, final CameraInfo cameraInfo) {
- Camera.getCameraInfo(index, cameraInfo);
- }
-
- @Override
- public Camera open(final int cameraId) {
- return Camera.open(cameraId);
- }
-
- @Override
- public void release(final Camera camera) {
- camera.release();
- }
- };
-
- /** The CameraInfo for the currently selected camera */
- private final CameraInfo mCameraInfo;
-
- /**
- * The index of the selected camera or NO_CAMERA_SELECTED if a camera hasn't been selected yet
- */
- private int mCameraIndex;
-
- /** True if the device has front and back cameras */
- private final boolean mHasFrontAndBackCamera;
-
- /** True if the camera should be open (may not yet be actually open) */
- private boolean mOpenRequested;
-
- /** True if the camera is requested to be in video mode */
- private boolean mVideoModeRequested;
-
- /** The media recorder for video mode */
- private MmsVideoRecorder mMediaRecorder;
-
- /** Callback to call with video recording updates */
- private MediaCallback mVideoCallback;
-
- /** The preview view to show the preview on */
- private CameraPreview mCameraPreview;
-
- /** The helper classs to handle orientation changes */
- private OrientationHandler mOrientationHandler;
-
- /** Tracks whether the preview has hardware acceleration */
- private boolean mIsHardwareAccelerationSupported;
-
- /**
- * The task for opening the camera, so it doesn't block the UI thread
- * Using AsyncTask rather than SafeAsyncTask because the tasks need to be serialized, but don't
- * need to be on the UI thread
- * TODO: If we have other AyncTasks (not SafeAsyncTasks) this may contend and we may
- * need to create a dedicated thread, or synchronize the threads in the thread pool
- */
- private AsyncTask<Integer, Void, Camera> mOpenCameraTask;
-
- /**
- * The camera index that is queued to be opened, but not completed yet, or NO_CAMERA_SELECTED if
- * no open task is pending
- */
- private int mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
-
- /** The instance of the currently opened camera */
- private Camera mCamera;
-
- /** The rotation of the screen relative to the camera's natural orientation */
- private int mRotation;
-
- /** The callback to notify when errors or other events occur */
- private CameraManagerListener mListener;
-
- /** True if the camera is currently in the process of taking an image */
- private boolean mTakingPicture;
-
- /** Provides subscription-related data to access per-subscription configurations. */
- private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;
-
- /** Manages auto focus visual and behavior */
- private final FocusOverlayManager mFocusOverlayManager;
-
- private CameraManager() {
- mCameraInfo = new CameraInfo();
- mCameraIndex = NO_CAMERA_SELECTED;
-
- // Check to see if a front and back camera exist
- boolean hasFrontCamera = false;
- boolean hasBackCamera = false;
- final CameraInfo cameraInfo = new CameraInfo();
- final int cameraCount = sCameraWrapper.getNumberOfCameras();
- try {
- for (int i = 0; i < cameraCount; i++) {
- sCameraWrapper.getCameraInfo(i, cameraInfo);
- if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) {
- hasFrontCamera = true;
- } else if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
- hasBackCamera = true;
- }
- if (hasFrontCamera && hasBackCamera) {
- break;
- }
- }
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "Unable to load camera info", e);
- }
- mHasFrontAndBackCamera = hasFrontCamera && hasBackCamera;
- mFocusOverlayManager = new FocusOverlayManager(this, Looper.getMainLooper());
-
- // Assume the best until we are proven otherwise
- mIsHardwareAccelerationSupported = true;
- }
-
- /** Gets the singleton instance */
- static CameraManager get() {
- if (sInstance == null) {
- sInstance = new CameraManager();
- }
- return sInstance;
- }
-
- /** Allows tests to inject a custom camera wrapper */
- @VisibleForTesting
- static void setCameraWrapper(final CameraWrapper cameraWrapper) {
- sCameraWrapper = cameraWrapper;
- sInstance = null;
- }
-
- /**
- * Sets the surface to use to display the preview
- * This must only be called AFTER the CameraPreview has a texture ready
- * @param preview The preview surface view
- */
- void setSurface(final CameraPreview preview) {
- if (preview == mCameraPreview) {
- return;
- }
-
- if (preview != null) {
- Assert.isTrue(preview.isValid());
- preview.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(final View view, final MotionEvent motionEvent) {
- if ((motionEvent.getActionMasked() & MotionEvent.ACTION_UP) ==
- MotionEvent.ACTION_UP) {
- mFocusOverlayManager.setPreviewSize(view.getWidth(), view.getHeight());
- mFocusOverlayManager.onSingleTapUp(
- (int) motionEvent.getX() + view.getLeft(),
- (int) motionEvent.getY() + view.getTop());
- }
- return true;
- }
- });
- }
- mCameraPreview = preview;
- tryShowPreview();
- }
-
- void setRenderOverlay(final RenderOverlay renderOverlay) {
- mFocusOverlayManager.setFocusRenderer(renderOverlay != null ?
- renderOverlay.getPieRenderer() : null);
- }
-
- /** Convenience function to swap between front and back facing cameras */
- void swapCamera() {
- Assert.isTrue(mCameraIndex >= 0);
- selectCamera(mCameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT ?
- CameraInfo.CAMERA_FACING_BACK :
- CameraInfo.CAMERA_FACING_FRONT);
- }
-
- /**
- * Selects the first camera facing the desired direction, or the first camera if there is no
- * camera in the desired direction
- * @param desiredFacing One of the CameraInfo.CAMERA_FACING_* constants
- * @return True if a camera was selected, or false if selecting a camera failed
- */
- boolean selectCamera(final int desiredFacing) {
- try {
- // We already selected a camera facing that direction
- if (mCameraIndex >= 0 && mCameraInfo.facing == desiredFacing) {
- return true;
- }
-
- final int cameraCount = sCameraWrapper.getNumberOfCameras();
- Assert.isTrue(cameraCount > 0);
-
- mCameraIndex = NO_CAMERA_SELECTED;
- setCamera(null);
- final CameraInfo cameraInfo = new CameraInfo();
- for (int i = 0; i < cameraCount; i++) {
- sCameraWrapper.getCameraInfo(i, cameraInfo);
- if (cameraInfo.facing == desiredFacing) {
- mCameraIndex = i;
- sCameraWrapper.getCameraInfo(i, mCameraInfo);
- break;
- }
- }
-
- // There's no camera in the desired facing direction, just select the first camera
- // regardless of direction
- if (mCameraIndex < 0) {
- mCameraIndex = 0;
- sCameraWrapper.getCameraInfo(0, mCameraInfo);
- }
-
- if (mOpenRequested) {
- // The camera is open, so reopen with the newly selected camera
- openCamera();
- }
- return true;
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.selectCamera", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- return false;
- }
- }
-
- int getCameraIndex() {
- return mCameraIndex;
- }
-
- void selectCameraByIndex(final int cameraIndex) {
- if (mCameraIndex == cameraIndex) {
- return;
- }
-
- try {
- mCameraIndex = cameraIndex;
- sCameraWrapper.getCameraInfo(mCameraIndex, mCameraInfo);
- if (mOpenRequested) {
- openCamera();
- }
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.selectCameraByIndex", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- }
- }
-
- @VisibleForTesting
- CameraInfo getCameraInfo() {
- if (mCameraIndex == NO_CAMERA_SELECTED) {
- return null;
- }
- return mCameraInfo;
- }
-
- /** @return True if this device has camera capabilities */
- boolean hasAnyCamera() {
- return sCameraWrapper.getNumberOfCameras() > 0;
- }
-
- /** @return True if the device has both a front and back camera */
- boolean hasFrontAndBackCamera() {
- return mHasFrontAndBackCamera;
- }
-
- /**
- * Opens the camera on a separate thread and initiates the preview if one is available
- */
- void openCamera() {
- if (mCameraIndex == NO_CAMERA_SELECTED) {
- // Ensure a selected camera if none is currently selected. This may happen if the
- // camera chooser is not the default media chooser.
- selectCamera(CameraInfo.CAMERA_FACING_BACK);
- }
- mOpenRequested = true;
- // We're already opening the camera or already have the camera handle, nothing more to do
- if (mPendingOpenCameraIndex == mCameraIndex || mCamera != null) {
- return;
- }
-
- // True if the task to open the camera has to be delayed until the current one completes
- boolean delayTask = false;
-
- // Cancel any previous open camera tasks
- if (mOpenCameraTask != null) {
- mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
- delayTask = true;
- }
-
- mPendingOpenCameraIndex = mCameraIndex;
- mOpenCameraTask = new AsyncTask<Integer, Void, Camera>() {
- private Exception mException;
-
- @Override
- protected Camera doInBackground(final Integer... params) {
- try {
- final int cameraIndex = params[0];
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Opening camera " + mCameraIndex);
- }
- return sCameraWrapper.open(cameraIndex);
- } catch (final Exception e) {
- LogUtil.e(TAG, "Exception while opening camera", e);
- mException = e;
- return null;
- }
- }
-
- @Override
- protected void onPostExecute(final Camera camera) {
- // If we completed, but no longer want this camera, then release the camera
- if (mOpenCameraTask != this || !mOpenRequested) {
- releaseCamera(camera);
- cleanup();
- return;
- }
-
- cleanup();
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Opened camera " + mCameraIndex + " " + (camera != null));
- }
-
- setCamera(camera);
- if (camera == null) {
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, mException);
- }
- LogUtil.e(TAG, "Error opening camera");
- }
- }
-
- @Override
- protected void onCancelled() {
- super.onCancelled();
- cleanup();
- }
-
- private void cleanup() {
- mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
- if (mOpenCameraTask != null && mOpenCameraTask.getStatus() == Status.PENDING) {
- // If there's another task waiting on this one to complete, start it now
- mOpenCameraTask.execute(mCameraIndex);
- } else {
- mOpenCameraTask = null;
- }
-
- }
- };
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Start opening camera " + mCameraIndex);
- }
-
- if (!delayTask) {
- mOpenCameraTask.execute(mCameraIndex);
- }
- }
-
- boolean isVideoMode() {
- return mVideoModeRequested;
- }
-
- boolean isRecording() {
- return mVideoModeRequested && mVideoCallback != null;
- }
-
- void setVideoMode(final boolean videoMode) {
- if (mVideoModeRequested == videoMode) {
- return;
- }
- mVideoModeRequested = videoMode;
- tryInitOrCleanupVideoMode();
- }
-
- /** Closes the camera releasing the resources it uses */
- void closeCamera() {
- mOpenRequested = false;
- setCamera(null);
- }
-
- /** Temporarily closes the camera if it is open */
- void onPause() {
- setCamera(null);
- }
-
- /** Reopens the camera if it was opened when onPause was called */
- void onResume() {
- if (mOpenRequested) {
- openCamera();
- }
- }
-
- /**
- * Sets the listener which will be notified of errors or other events in the camera
- * @param listener The listener to notify
- */
- void setListener(final CameraManagerListener listener) {
- Assert.isMainThread();
- mListener = listener;
- if (!mIsHardwareAccelerationSupported && mListener != null) {
- mListener.onCameraError(ERROR_HARDWARE_ACCELERATION_DISABLED, null);
- }
- }
-
- void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) {
- mSubscriptionDataProvider = provider;
- }
-
- void takePicture(final float heightPercent, @NonNull final MediaCallback callback) {
- Assert.isTrue(!mVideoModeRequested);
- Assert.isTrue(!mTakingPicture);
- Assert.notNull(callback);
- if (mCamera == null) {
- // The caller should have checked isCameraAvailable first, but just in case, protect
- // against a null camera by notifying the callback that taking the picture didn't work
- callback.onMediaFailed(null);
- return;
- }
- final Camera.PictureCallback jpegCallback = new Camera.PictureCallback() {
- @Override
- public void onPictureTaken(final byte[] bytes, final Camera camera) {
- mTakingPicture = false;
- if (mCamera != camera) {
- // This may happen if the camera was changed between front/back while the
- // picture is being taken.
- callback.onMediaInfo(MediaCallback.MEDIA_CAMERA_CHANGED);
- return;
- }
-
- if (bytes == null) {
- callback.onMediaInfo(MediaCallback.MEDIA_NO_DATA);
- return;
- }
-
- final Camera.Size size = camera.getParameters().getPictureSize();
- int width;
- int height;
- if (mRotation == 90 || mRotation == 270) {
- width = size.height;
- height = size.width;
- } else {
- width = size.width;
- height = size.height;
- }
- new ImagePersistTask(
- width, height, heightPercent, bytes, mCameraPreview.getContext(), callback)
- .executeOnThreadPool();
- }
- };
-
- mTakingPicture = true;
- try {
- mCamera.takePicture(
- null /* shutter */,
- null /* raw */,
- null /* postView */,
- jpegCallback);
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.takePicture", e);
- mTakingPicture = false;
- if (mListener != null) {
- mListener.onCameraError(ERROR_TAKING_PICTURE, e);
- }
- }
- }
-
- void startVideo(final MediaCallback callback) {
- Assert.notNull(callback);
- Assert.isTrue(!isRecording());
- mVideoCallback = callback;
- tryStartVideoCapture();
- }
-
- /**
- * Asynchronously releases a camera
- * @param camera The camera to release
- */
- private void releaseCamera(final Camera camera) {
- if (camera == null) {
- return;
- }
-
- mFocusOverlayManager.onCameraReleased();
-
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(final Void... params) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Releasing camera " + mCameraIndex);
- }
- sCameraWrapper.release(camera);
- return null;
- }
- }.execute();
- }
-
- private void releaseMediaRecorder(final boolean cleanupFile) {
- if (mMediaRecorder == null) {
- return;
- }
- mVideoModeRequested = false;
-
- if (cleanupFile) {
- mMediaRecorder.cleanupTempFile();
- if (mVideoCallback != null) {
- final MediaCallback callback = mVideoCallback;
- mVideoCallback = null;
- // Notify the callback that we've stopped recording
- callback.onMediaReady(null /*uri*/, null /*contentType*/, 0 /*width*/,
- 0 /*height*/);
- }
- }
-
- mMediaRecorder.release();
- mMediaRecorder = null;
-
- if (mCamera != null) {
- try {
- mCamera.reconnect();
- } catch (final IOException e) {
- LogUtil.e(TAG, "IOException in CameraManager.releaseMediaRecorder", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.releaseMediaRecorder", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- }
- }
- restoreRequestedOrientation();
- }
-
- /** Updates the orientation of the camera to match the orientation of the device */
- private void updateCameraOrientation() {
- if (mCamera == null || mCameraPreview == null || mTakingPicture) {
- return;
- }
-
- final WindowManager windowManager =
- (WindowManager) mCameraPreview.getContext().getSystemService(
- Context.WINDOW_SERVICE);
-
- int degrees = 0;
- switch (windowManager.getDefaultDisplay().getRotation()) {
- case Surface.ROTATION_0: degrees = 0; break;
- case Surface.ROTATION_90: degrees = 90; break;
- case Surface.ROTATION_180: degrees = 180; break;
- case Surface.ROTATION_270: degrees = 270; break;
- }
-
- // The display orientation of the camera (this controls the preview image).
- int orientation;
-
- // The clockwise rotation angle relative to the orientation of the camera. This affects
- // pictures returned by the camera in Camera.PictureCallback.
- int rotation;
- if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
- orientation = (mCameraInfo.orientation + degrees) % 360;
- rotation = orientation;
- // compensate the mirror but only for orientation
- orientation = (360 - orientation) % 360;
- } else { // back-facing
- orientation = (mCameraInfo.orientation - degrees + 360) % 360;
- rotation = orientation;
- }
- mRotation = rotation;
- if (mMediaRecorder == null) {
- try {
- mCamera.setDisplayOrientation(orientation);
- final Camera.Parameters params = mCamera.getParameters();
- params.setRotation(rotation);
- mCamera.setParameters(params);
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.updateCameraOrientation", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- }
- }
- }
-
- /** Sets the current camera, releasing any previously opened camera */
- private void setCamera(final Camera camera) {
- if (mCamera == camera) {
- return;
- }
-
- releaseMediaRecorder(true /* cleanupFile */);
- releaseCamera(mCamera);
- mCamera = camera;
- tryShowPreview();
- if (mListener != null) {
- mListener.onCameraChanged();
- }
- }
-
- /** Shows the preview if the camera is open and the preview is loaded */
- private void tryShowPreview() {
- if (mCameraPreview == null || mCamera == null) {
- if (mOrientationHandler != null) {
- mOrientationHandler.disable();
- mOrientationHandler = null;
- }
- releaseMediaRecorder(true /* cleanupFile */);
- mFocusOverlayManager.onPreviewStopped();
- return;
- }
- try {
- mCamera.stopPreview();
- updateCameraOrientation();
-
- final Camera.Parameters params = mCamera.getParameters();
- final Camera.Size pictureSize = chooseBestPictureSize();
- final Camera.Size previewSize = chooseBestPreviewSize(pictureSize);
- params.setPreviewSize(previewSize.width, previewSize.height);
- params.setPictureSize(pictureSize.width, pictureSize.height);
- logCameraSize("Setting preview size: ", previewSize);
- logCameraSize("Setting picture size: ", pictureSize);
- mCameraPreview.setSize(previewSize, mCameraInfo.orientation);
- for (final String focusMode : params.getSupportedFocusModes()) {
- if (TextUtils.equals(focusMode, Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
- // Use continuous focus if available
- params.setFocusMode(focusMode);
- break;
- }
- }
-
- mCamera.setParameters(params);
- mCameraPreview.startPreview(mCamera);
- mCamera.startPreview();
- mCamera.setAutoFocusMoveCallback(new Camera.AutoFocusMoveCallback() {
- @Override
- public void onAutoFocusMoving(final boolean start, final Camera camera) {
- mFocusOverlayManager.onAutoFocusMoving(start);
- }
- });
- mFocusOverlayManager.setParameters(mCamera.getParameters());
- mFocusOverlayManager.setMirror(mCameraInfo.facing == CameraInfo.CAMERA_FACING_BACK);
- mFocusOverlayManager.onPreviewStarted();
- tryInitOrCleanupVideoMode();
- if (mOrientationHandler == null) {
- mOrientationHandler = new OrientationHandler(mCameraPreview.getContext());
- mOrientationHandler.enable();
- }
- } catch (final IOException e) {
- LogUtil.e(TAG, "IOException in CameraManager.tryShowPreview", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_SHOWING_PREVIEW, e);
- }
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.tryShowPreview", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_SHOWING_PREVIEW, e);
- }
- }
- }
-
- private void tryInitOrCleanupVideoMode() {
- if (!mVideoModeRequested || mCamera == null || mCameraPreview == null) {
- releaseMediaRecorder(true /* cleanupFile */);
- return;
- }
-
- if (mMediaRecorder != null) {
- return;
- }
-
- try {
- mCamera.unlock();
- final int maxMessageSize = getMmsConfig().getMaxMessageSize();
- mMediaRecorder = new MmsVideoRecorder(mCamera, mCameraIndex, mRotation, maxMessageSize);
- mMediaRecorder.prepare();
- } catch (final FileNotFoundException e) {
- LogUtil.e(TAG, "FileNotFoundException in CameraManager.tryInitOrCleanupVideoMode", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_STORAGE_FAILURE, e);
- }
- setVideoMode(false);
- return;
- } catch (final IOException e) {
- LogUtil.e(TAG, "IOException in CameraManager.tryInitOrCleanupVideoMode", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_INITIALIZING_VIDEO, e);
- }
- setVideoMode(false);
- return;
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.tryInitOrCleanupVideoMode", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_INITIALIZING_VIDEO, e);
- }
- setVideoMode(false);
- return;
- }
-
- tryStartVideoCapture();
- }
-
- private void tryStartVideoCapture() {
- if (mMediaRecorder == null || mVideoCallback == null) {
- return;
- }
-
- mMediaRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() {
- @Override
- public void onError(final MediaRecorder mediaRecorder, final int what,
- final int extra) {
- if (mListener != null) {
- mListener.onCameraError(ERROR_RECORDING_VIDEO, null);
- }
- restoreRequestedOrientation();
- }
- });
-
- mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
- @Override
- public void onInfo(final MediaRecorder mediaRecorder, final int what, final int extra) {
- if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED ||
- what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
- stopVideo();
- }
- }
- });
-
- try {
- mMediaRecorder.start();
- final Activity activity = UiUtils.getActivity(mCameraPreview.getContext());
- activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- lockOrientation();
- } catch (final IllegalStateException e) {
- LogUtil.e(TAG, "IllegalStateException in CameraManager.tryStartVideoCapture", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_RECORDING_VIDEO, e);
- }
- setVideoMode(false);
- restoreRequestedOrientation();
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.tryStartVideoCapture", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_RECORDING_VIDEO, e);
- }
- setVideoMode(false);
- restoreRequestedOrientation();
- }
- }
-
- void stopVideo() {
- int width = ImageRequest.UNSPECIFIED_SIZE;
- int height = ImageRequest.UNSPECIFIED_SIZE;
- Uri uri = null;
- String contentType = null;
- try {
- final Activity activity = UiUtils.getActivity(mCameraPreview.getContext());
- activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- mMediaRecorder.stop();
- width = mMediaRecorder.getVideoWidth();
- height = mMediaRecorder.getVideoHeight();
- uri = mMediaRecorder.getVideoUri();
- contentType = mMediaRecorder.getContentType();
- } catch (final RuntimeException e) {
- // MediaRecorder.stop will throw a RuntimeException if the video was too short, let the
- // finally clause call the callback with null uri and handle cleanup
- LogUtil.e(TAG, "RuntimeException in CameraManager.stopVideo", e);
- } finally {
- final MediaCallback videoCallback = mVideoCallback;
- mVideoCallback = null;
- releaseMediaRecorder(false /* cleanupFile */);
- if (uri == null) {
- tryInitOrCleanupVideoMode();
- }
- videoCallback.onMediaReady(uri, contentType, width, height);
- }
- }
-
- boolean isCameraAvailable() {
- return mCamera != null && !mTakingPicture && mIsHardwareAccelerationSupported;
- }
-
- /**
- * External components call into this to report if hardware acceleration is supported. When
- * hardware acceleration isn't supported, we need to report an error through the listener
- * interface
- * @param isHardwareAccelerationSupported True if the preview is rendering in a hardware
- * accelerated view.
- */
- void reportHardwareAccelerationSupported(final boolean isHardwareAccelerationSupported) {
- Assert.isMainThread();
- if (mIsHardwareAccelerationSupported == isHardwareAccelerationSupported) {
- // If the value hasn't changed nothing more to do
- return;
- }
-
- mIsHardwareAccelerationSupported = isHardwareAccelerationSupported;
- if (!isHardwareAccelerationSupported) {
- LogUtil.e(TAG, "Software rendering - cannot open camera");
- if (mListener != null) {
- mListener.onCameraError(ERROR_HARDWARE_ACCELERATION_DISABLED, null);
- }
- }
- }
-
- /** Returns the scale factor to scale the width/height to max allowed in MmsConfig */
- private float getScaleFactorForMaxAllowedSize(final int width, final int height,
- final int maxWidth, final int maxHeight) {
- if (maxWidth <= 0 || maxHeight <= 0) {
- // MmsConfig initialization runs asynchronously on application startup, so there's a
- // chance (albeit a very slight one) that we don't have it yet.
- LogUtil.w(LogUtil.BUGLE_TAG, "Max image size not loaded in MmsConfig");
- return 1.0f;
- }
-
- if (width <= maxWidth && height <= maxHeight) {
- // Already meeting requirements.
- return 1.0f;
- }
-
- return Math.min(maxWidth * 1.0f / width, maxHeight * 1.0f / height);
- }
-
- private MmsConfig getMmsConfig() {
- final int subId = mSubscriptionDataProvider != null ?
- mSubscriptionDataProvider.getConversationSelfSubId() :
- ParticipantData.DEFAULT_SELF_SUB_ID;
- return MmsConfig.get(subId);
- }
-
- /**
- * Choose the best picture size by trying to find a size close to the MmsConfig's max size,
- * which is closest to the screen aspect ratio
- */
- private Camera.Size chooseBestPictureSize() {
- final Context context = mCameraPreview.getContext();
- final Resources resources = context.getResources();
- final DisplayMetrics displayMetrics = resources.getDisplayMetrics();
- final int displayOrientation = resources.getConfiguration().orientation;
- int cameraOrientation = mCameraInfo.orientation;
-
- int screenWidth;
- int screenHeight;
- if (displayOrientation == Configuration.ORIENTATION_LANDSCAPE) {
- // Rotate the camera orientation 90 degrees to compensate for the rotated display
- // metrics. Direction doesn't matter because we're just using it for width/height
- cameraOrientation += 90;
- }
-
- // Check the camera orientation relative to the display.
- // For 0, 180, 360, the screen width/height are the display width/height
- // For 90, 270, the screen width/height are inverted from the display
- if (cameraOrientation % 180 == 0) {
- screenWidth = displayMetrics.widthPixels;
- screenHeight = displayMetrics.heightPixels;
- } else {
- screenWidth = displayMetrics.heightPixels;
- screenHeight = displayMetrics.widthPixels;
- }
-
- final MmsConfig mmsConfig = getMmsConfig();
- final int maxWidth = mmsConfig.getMaxImageWidth();
- final int maxHeight = mmsConfig.getMaxImageHeight();
-
- // Constrain the size within the max width/height defined by MmsConfig.
- final float scaleFactor = getScaleFactorForMaxAllowedSize(screenWidth, screenHeight,
- maxWidth, maxHeight);
- screenWidth *= scaleFactor;
- screenHeight *= scaleFactor;
-
- final float aspectRatio = BugleGservices.get().getFloat(
- BugleGservicesKeys.CAMERA_ASPECT_RATIO,
- screenWidth / (float) screenHeight);
- final List<Camera.Size> sizes = new ArrayList<Camera.Size>(
- mCamera.getParameters().getSupportedPictureSizes());
- final int maxPixels = maxWidth * maxHeight;
-
- // Sort the sizes so the best size is first
- Collections.sort(sizes, new SizeComparator(maxWidth, maxHeight, aspectRatio, maxPixels));
-
- return sizes.get(0);
- }
-
- /**
- * Chose the best preview size based on the picture size. Try to find a size with the same
- * aspect ratio and size as the picture if possible
- */
- private Camera.Size chooseBestPreviewSize(final Camera.Size pictureSize) {
- final List<Camera.Size> sizes = new ArrayList<Camera.Size>(
- mCamera.getParameters().getSupportedPreviewSizes());
- final float aspectRatio = pictureSize.width / (float) pictureSize.height;
- final int capturePixels = pictureSize.width * pictureSize.height;
-
- // Sort the sizes so the best size is first
- Collections.sort(sizes, new SizeComparator(Integer.MAX_VALUE, Integer.MAX_VALUE,
- aspectRatio, capturePixels));
-
- return sizes.get(0);
- }
-
- private class OrientationHandler extends OrientationEventListener {
- OrientationHandler(final Context context) {
- super(context);
- }
-
- @Override
- public void onOrientationChanged(final int orientation) {
- updateCameraOrientation();
- }
- }
-
- private static class SizeComparator implements Comparator<Camera.Size> {
- private static final int PREFER_LEFT = -1;
- private static final int PREFER_RIGHT = 1;
-
- // The max width/height for the preferred size. Integer.MAX_VALUE if no size limit
- private final int mMaxWidth;
- private final int mMaxHeight;
-
- // The desired aspect ratio
- private final float mTargetAspectRatio;
-
- // The desired size (width x height) to try to match
- private final int mTargetPixels;
-
- public SizeComparator(final int maxWidth, final int maxHeight,
- final float targetAspectRatio, final int targetPixels) {
- mMaxWidth = maxWidth;
- mMaxHeight = maxHeight;
- mTargetAspectRatio = targetAspectRatio;
- mTargetPixels = targetPixels;
- }
-
- /**
- * Returns a negative value if left is a better choice than right, or a positive value if
- * right is a better choice is better than left. 0 if they are equal
- */
- @Override
- public int compare(final Camera.Size left, final Camera.Size right) {
- // If one size is less than the max size prefer it over the other
- if ((left.width <= mMaxWidth && left.height <= mMaxHeight) !=
- (right.width <= mMaxWidth && right.height <= mMaxHeight)) {
- return left.width <= mMaxWidth ? PREFER_LEFT : PREFER_RIGHT;
- }
-
- // If one is closer to the target aspect ratio, prefer it.
- final float leftAspectRatio = left.width / (float) left.height;
- final float rightAspectRatio = right.width / (float) right.height;
- final float leftAspectRatioDiff = Math.abs(leftAspectRatio - mTargetAspectRatio);
- final float rightAspectRatioDiff = Math.abs(rightAspectRatio - mTargetAspectRatio);
- if (leftAspectRatioDiff != rightAspectRatioDiff) {
- return (leftAspectRatioDiff - rightAspectRatioDiff) < 0 ?
- PREFER_LEFT : PREFER_RIGHT;
- }
-
- // At this point they have the same aspect ratio diff and are either both bigger
- // than the max size or both smaller than the max size, so prefer the one closest
- // to target size
- final int leftDiff = Math.abs((left.width * left.height) - mTargetPixels);
- final int rightDiff = Math.abs((right.width * right.height) - mTargetPixels);
- return leftDiff - rightDiff;
- }
- }
-
- @Override // From FocusOverlayManager.Listener
- public void autoFocus() {
- if (mCamera == null) {
- return;
- }
-
- try {
- mCamera.autoFocus(new Camera.AutoFocusCallback() {
- @Override
- public void onAutoFocus(final boolean success, final Camera camera) {
- mFocusOverlayManager.onAutoFocus(success, false /* shutterDown */);
- }
- });
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.autoFocus", e);
- // If autofocus fails, the camera should have called the callback with success=false,
- // but some throw an exception here
- mFocusOverlayManager.onAutoFocus(false /*success*/, false /*shutterDown*/);
- }
- }
-
- @Override // From FocusOverlayManager.Listener
- public void cancelAutoFocus() {
- if (mCamera == null) {
- return;
- }
- try {
- mCamera.cancelAutoFocus();
- } catch (final RuntimeException e) {
- // Ignore
- LogUtil.e(TAG, "RuntimeException in CameraManager.cancelAutoFocus", e);
- }
- }
-
- @Override // From FocusOverlayManager.Listener
- public boolean capture() {
- return false;
- }
-
- @Override // From FocusOverlayManager.Listener
- public void setFocusParameters() {
- if (mCamera == null) {
- return;
- }
- try {
- final Camera.Parameters parameters = mCamera.getParameters();
- parameters.setFocusMode(mFocusOverlayManager.getFocusMode());
- if (parameters.getMaxNumFocusAreas() > 0) {
- // Don't set focus areas (even to null) if focus areas aren't supported, camera may
- // crash
- parameters.setFocusAreas(mFocusOverlayManager.getFocusAreas());
- }
- parameters.setMeteringAreas(mFocusOverlayManager.getMeteringAreas());
- mCamera.setParameters(parameters);
- } catch (final RuntimeException e) {
- // This occurs when the device is out of space or when the camera is locked
- LogUtil.e(TAG, "RuntimeException in CameraManager setFocusParameters");
- }
- }
-
- private void logCameraSize(final String prefix, final Camera.Size size) {
- // Log the camera size and aspect ratio for help when examining bug reports for camera
- // failures
- LogUtil.i(TAG, prefix + size.width + "x" + size.height +
- " (" + (size.width / (float) size.height) + ")");
- }
-
-
- private Integer mSavedOrientation = null;
-
- private void lockOrientation() {
- // when we start recording, lock our orientation
- final Activity a = UiUtils.getActivity(mCameraPreview.getContext());
- final WindowManager windowManager =
- (WindowManager) a.getSystemService(Context.WINDOW_SERVICE);
- final int rotation = windowManager.getDefaultDisplay().getRotation();
-
- mSavedOrientation = a.getRequestedOrientation();
- switch (rotation) {
- case Surface.ROTATION_0:
- a.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- break;
- case Surface.ROTATION_90:
- a.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
- break;
- case Surface.ROTATION_180:
- a.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
- break;
- case Surface.ROTATION_270:
- a.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
- break;
- }
-
- }
-
- private void restoreRequestedOrientation() {
- if (mSavedOrientation != null) {
- final Activity a = UiUtils.getActivity(mCameraPreview.getContext());
- if (a != null) {
- a.setRequestedOrientation(mSavedOrientation);
- }
- mSavedOrientation = null;
- }
- }
-
- static boolean hasCameraPermission() {
- return OsUtil.hasPermission(Manifest.permission.CAMERA);
- }
-}