diff options
Diffstat (limited to 'java/com/android/dialer/callcomposer/camera/CameraManager.java')
-rw-r--r-- | java/com/android/dialer/callcomposer/camera/CameraManager.java | 822 |
1 files changed, 822 insertions, 0 deletions
diff --git a/java/com/android/dialer/callcomposer/camera/CameraManager.java b/java/com/android/dialer/callcomposer/camera/CameraManager.java new file mode 100644 index 000000000..87cd16a99 --- /dev/null +++ b/java/com/android/dialer/callcomposer/camera/CameraManager.java @@ -0,0 +1,822 @@ +/* + * Copyright (C) 2016 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.dialer.callcomposer.camera; + +import android.content.Context; +import android.hardware.Camera; +import android.hardware.Camera.CameraInfo; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Looper; +import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; +import android.text.TextUtils; +import android.view.MotionEvent; +import android.view.OrientationEventListener; +import android.view.Surface; +import android.view.View; +import android.view.WindowManager; +import com.android.dialer.callcomposer.camera.camerafocus.FocusOverlayManager; +import com.android.dialer.callcomposer.camera.camerafocus.RenderOverlay; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +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) + */ +public class CameraManager implements FocusOverlayManager.Listener { + /** Callbacks for the camera manager listener */ + public interface CameraManagerListener { + void onCameraError(int errorCode, Exception e); + + void onCameraChanged(); + } + + /** Callback when taking image or video */ + public interface MediaCallback { + int MEDIA_CAMERA_CHANGED = 1; + 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 + private static final int ERROR_OPENING_CAMERA = 1; + private static final int ERROR_SHOWING_PREVIEW = 2; + private static final int ERROR_HARDWARE_ACCELERATION_DISABLED = 3; + private static final int ERROR_TAKING_PICTURE = 4; + + private static final int NO_CAMERA_SELECTED = -1; + + private static final Camera.ShutterCallback DUMMY_SHUTTER_CALLBACK = + new Camera.ShutterCallback() { + @Override + public void onShutter() { + // Do nothing + } + }; + + private static CameraManager sInstance; + + /** 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; + + /** 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; + + /** 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 = Camera.getNumberOfCameras(); + try { + for (int i = 0; i < cameraCount; i++) { + Camera.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("CameraManager.CameraManager", "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 */ + public static CameraManager get() { + if (sInstance == null) { + sInstance = new CameraManager(); + } + return sInstance; + } + + /** + * 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.checkArgument(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()); + } + view.performClick(); + return true; + } + }); + } + mCameraPreview = preview; + tryShowPreview(); + } + + public void setRenderOverlay(final RenderOverlay renderOverlay) { + mFocusOverlayManager.setFocusRenderer( + renderOverlay != null ? renderOverlay.getPieRenderer() : null); + } + + /** Convenience function to swap between front and back facing cameras */ + public void swapCamera() { + Assert.checkState(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 + */ + public 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 = Camera.getNumberOfCameras(); + Assert.checkState(cameraCount > 0); + + mCameraIndex = NO_CAMERA_SELECTED; + setCamera(null); + final CameraInfo cameraInfo = new CameraInfo(); + for (int i = 0; i < cameraCount; i++) { + Camera.getCameraInfo(i, cameraInfo); + if (cameraInfo.facing == desiredFacing) { + mCameraIndex = i; + Camera.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; + Camera.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("CameraManager.selectCamera", "RuntimeException in CameraManager.selectCamera", e); + if (mListener != null) { + mListener.onCameraError(ERROR_OPENING_CAMERA, e); + } + return false; + } + } + + public int getCameraIndex() { + return mCameraIndex; + } + + public void selectCameraByIndex(final int cameraIndex) { + if (mCameraIndex == cameraIndex) { + return; + } + + try { + mCameraIndex = cameraIndex; + Camera.getCameraInfo(mCameraIndex, mCameraInfo); + if (mOpenRequested) { + openCamera(); + } + } catch (final RuntimeException e) { + LogUtil.e( + "CameraManager.selectCameraByIndex", + "RuntimeException in CameraManager.selectCameraByIndex", + e); + if (mListener != null) { + mListener.onCameraError(ERROR_OPENING_CAMERA, e); + } + } + } + + @VisibleForTesting + public CameraInfo getCameraInfo() { + if (mCameraIndex == NO_CAMERA_SELECTED) { + return null; + } + return mCameraInfo; + } + + /** @return True if the device has both a front and back camera */ + public 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]; + LogUtil.v("CameraManager.doInBackground", "Opening camera " + mCameraIndex); + return Camera.open(cameraIndex); + } catch (final Exception e) { + LogUtil.e("CameraManager.doInBackground", "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(); + + LogUtil.v( + "CameraManager.onPostExecute", + "Opened camera " + mCameraIndex + " " + (camera != null)); + setCamera(camera); + if (camera == null) { + if (mListener != null) { + mListener.onCameraError(ERROR_OPENING_CAMERA, mException); + } + LogUtil.e("CameraManager.onPostExecute", "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; + } + } + }; + LogUtil.v("CameraManager.openCamera", "Start opening camera " + mCameraIndex); + if (!delayTask) { + mOpenCameraTask.execute(mCameraIndex); + } + } + + /** Closes the camera releasing the resources it uses */ + void closeCamera() { + mOpenRequested = false; + setCamera(null); + } + + /** + * Sets the listener which will be notified of errors or other events in the camera + * + * @param listener The listener to notify + */ + public void setListener(final CameraManagerListener listener) { + Assert.isMainThread(); + mListener = listener; + if (!mIsHardwareAccelerationSupported && mListener != null) { + mListener.onCameraError(ERROR_HARDWARE_ACCELERATION_DISABLED, null); + } + } + + public void takePicture(final float heightPercent, @NonNull final MediaCallback callback) { + Assert.checkState(!mTakingPicture); + Assert.isNotNull(callback); + mCameraPreview.setFocusable(false); + mFocusOverlayManager.cancelAutoFocus(); + 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) { + // Is rotated, so swapping dimensions is desired + //noinspection SuspiciousNameCombination + width = size.height; + //noinspection SuspiciousNameCombination + height = size.width; + } else { + width = size.width; + height = size.height; + } + LogUtil.i( + "CameraManager.onPictureTaken", "taken picture size: " + bytes.length + " bytes"); + new ImagePersistTask( + width, height, heightPercent, bytes, mCameraPreview.getContext(), callback) + .execute(); + } + }; + + mTakingPicture = true; + try { + mCamera.takePicture( + // A shutter callback is required to enable shutter sound + DUMMY_SHUTTER_CALLBACK, null /* raw */, null /* postView */, jpegCallback); + } catch (final RuntimeException e) { + LogUtil.e("CameraManager.takePicture", "RuntimeException in CameraManager.takePicture", e); + mTakingPicture = false; + if (mListener != null) { + mListener.onCameraError(ERROR_TAKING_PICTURE, e); + } + } + } + + /** + * 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) { + LogUtil.v("CameraManager.doInBackground", "Releasing camera " + mCameraIndex); + camera.release(); + return null; + } + }.execute(); + } + + /** 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; + try { + mCamera.setDisplayOrientation(orientation); + final Camera.Parameters params = mCamera.getParameters(); + params.setRotation(rotation); + mCamera.setParameters(params); + } catch (final RuntimeException e) { + LogUtil.e( + "CameraManager.updateCameraOrientation", + "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; + } + + 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(); + if (mOrientationHandler == null) { + mOrientationHandler = new OrientationHandler(mCameraPreview.getContext()); + mOrientationHandler.enable(); + } + } catch (final IOException e) { + LogUtil.e("CameraManager.tryShowPreview", "IOException in CameraManager.tryShowPreview", e); + if (mListener != null) { + mListener.onCameraError(ERROR_SHOWING_PREVIEW, e); + } + } catch (final RuntimeException e) { + LogUtil.e( + "CameraManager.tryShowPreview", "RuntimeException in CameraManager.tryShowPreview", e); + if (mListener != null) { + mListener.onCameraError(ERROR_SHOWING_PREVIEW, e); + } + } + } + + public boolean isCameraAvailable() { + return mCamera != null && !mTakingPicture && mIsHardwareAccelerationSupported; + } + + /** + * 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. In case of RCS conversation returns default size. + */ + private Camera.Size chooseBestPictureSize() { + return mCamera.getParameters().getPictureSize(); + } + + /** + * 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("CameraManager.autoFocus", "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( + "CameraManager.cancelAutoFocus", "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( + "CameraManager.setFocusParameters", + "RuntimeException in CameraManager setFocusParameters"); + } + } + + public void resetPreview() { + mCamera.startPreview(); + if (mCameraPreview != null) { + mCameraPreview.setFocusable(true); + } + } + + 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( + "CameraManager.logCameraSize", + prefix + size.width + "x" + size.height + " (" + (size.width / (float) size.height) + ")"); + } + + @VisibleForTesting + public void resetCameraManager() { + sInstance = null; + } +} |