diff options
Diffstat (limited to 'src/com/android/camera/FocusOverlayManager.java')
-rw-r--r-- | src/com/android/camera/FocusOverlayManager.java | 310 |
1 files changed, 198 insertions, 112 deletions
diff --git a/src/com/android/camera/FocusOverlayManager.java b/src/com/android/camera/FocusOverlayManager.java index 253c08fb9..287890cbd 100644 --- a/src/com/android/camera/FocusOverlayManager.java +++ b/src/com/android/camera/FocusOverlayManager.java @@ -16,18 +16,22 @@ package com.android.camera; -import android.annotation.TargetApi; -import android.graphics.Matrix; +import android.content.Context; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.hardware.Camera.Area; import android.hardware.Camera.Parameters; -import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; +import org.codeaurora.snapcam.R; +import com.android.camera.app.CameraApp; +import com.android.camera.ui.focus.CameraCoordinateTransformer; +import com.android.camera.ui.focus.FocusRing; +import com.android.camera.ui.motion.LinearScale; import com.android.camera.util.CameraUtil; import com.android.camera.util.UsageStatistics; @@ -60,7 +64,9 @@ public class FocusOverlayManager { private static final String TAG = "CAM_FocusManager"; private static final int RESET_TOUCH_FOCUS = 0; - private static final int RESET_TOUCH_FOCUS_DELAY = 3000; + + public static final float AF_REGION_BOX = 0.2f; + public static final float AE_REGION_BOX = 0.3f; private int mState = STATE_IDLE; public static final int STATE_IDLE = 0; // Focus is not active. @@ -75,7 +81,7 @@ public class FocusOverlayManager { private boolean mMeteringAreaSupported; private boolean mLockAeAwbNeeded; private boolean mAeAwbLock; - private Matrix mMatrix; + private CameraCoordinateTransformer mCoordinateTransformer; private boolean mMirror; // true if the camera is front-facing. private int mDisplayOrientation; @@ -93,19 +99,13 @@ public class FocusOverlayManager { private boolean mTouchAFRunning = false; private boolean mIsAFRunning = false; - private FocusUI mUI; + private FocusRing mFocusRing; private final Rect mPreviewRect = new Rect(0, 0, 0, 0); - public interface FocusUI { - public boolean hasFaces(); - public void clearFocus(); - public void setFocusPosition(int x, int y); - public void onFocusStarted(); - public void onFocusSucceeded(boolean timeOut); - public void onFocusFailed(boolean timeOut); - public void pauseFaceDetection(); - public void resumeFaceDetection(); - } + private int mFocusTime; // time after touch-to-focus + private Point mDispSize; + private int mBottomMargin; + private int mTopMargin; public interface Listener { public void autoFocus(); @@ -114,6 +114,7 @@ public class FocusOverlayManager { public void startFaceDetection(); public void stopFaceDetection(); public void setFocusParameters(); + public void setFocusRatio(float ratio); } private class MainHandler extends Handler { @@ -135,19 +136,25 @@ public class FocusOverlayManager { public FocusOverlayManager(ComboPreferences preferences, String[] defaultFocusModes, Parameters parameters, Listener listener, - boolean mirror, Looper looper, FocusUI ui) { + boolean mirror, Looper looper, FocusRing focusRing, CameraActivity activity) { mHandler = new MainHandler(looper); - mMatrix = new Matrix(); mPreferences = preferences; mDefaultFocusModes = defaultFocusModes; setParameters(parameters); mListener = listener; setMirror(mirror); - mUI = ui; + mDispSize = new Point(); + activity.getWindowManager().getDefaultDisplay().getRealSize(mDispSize); + Context context = CameraApp.getContext(); + mBottomMargin = + context.getResources().getDimensionPixelSize(R.dimen.preview_bottom_margin); + mTopMargin = + context.getResources().getDimensionPixelSize(R.dimen.preview_top_margin); + mFocusRing = focusRing; } - public void setPhotoUI(FocusUI ui) { - mUI = ui; + public void setFocusRing(FocusRing focusRing) { + mFocusRing = focusRing; } public void setParameters(Parameters parameters) { @@ -173,35 +180,29 @@ public class FocusOverlayManager { public void setPreviewRect(Rect previewRect) { if (!mPreviewRect.equals(previewRect)) { mPreviewRect.set(previewRect); - setMatrix(); + mFocusRing.configurePreviewDimensions(CameraUtil.rectToRectF(mPreviewRect)); + resetCoordinateTransformer(); + mInitialized = true; } } - /** Returns a copy of mPreviewRect so that outside class cannot modify preview - * rect except deliberately doing so through the setter. */ - public Rect getPreviewRect() { - return new Rect(mPreviewRect); - } - public void setMirror(boolean mirror) { mMirror = mirror; - setMatrix(); + resetCoordinateTransformer(); } public void setDisplayOrientation(int displayOrientation) { mDisplayOrientation = displayOrientation; - setMatrix(); + resetCoordinateTransformer(); } - private void setMatrix() { - if (mPreviewRect.width() != 0 && mPreviewRect.height() != 0) { - Matrix matrix = new Matrix(); - CameraUtil.prepareMatrix(matrix, mMirror, mDisplayOrientation, getPreviewRect()); - // In face detection, the matrix converts the driver coordinates to UI - // coordinates. In tap focus, the inverted matrix converts the UI - // coordinates to driver coordinates. - matrix.invert(mMatrix); - mInitialized = true; + private void resetCoordinateTransformer() { + if (mPreviewRect.width() > 0 && mPreviewRect.height() > 0) { + mCoordinateTransformer = new CameraCoordinateTransformer(mMirror, mDisplayOrientation, + CameraUtil.rectToRectF(mPreviewRect)); + } else { + Log.w(TAG, "The coordinate transformer could not be built because the preview rect" + + "did not have a width and height"); } } @@ -272,17 +273,27 @@ public class FocusOverlayManager { } } + // set touch-to-focus duration + public void setFocusTime(int time) { + mFocusTime = time; + } + public void onAutoFocus(boolean focused, boolean shutterButtonPressed) { + updateFocusDistance(); if (mState == STATE_FOCUSING_SNAP_ON_FINISH) { // Take the picture no matter focus succeeds or fails. No need // to play the AF sound if we're about to play the shutter // sound. if (focused) { mState = STATE_SUCCESS; + // Lock exposure and white balance + if (mFocusTime != 200) { + setAeAwbLock(true); + mListener.setFocusParameters(); + } } else { mState = STATE_FAIL; } - updateFocusUI(); capture(); } else if (mState == STATE_FOCUSING) { // This happens when (1) user is half-pressing the focus key or @@ -290,14 +301,18 @@ public class FocusOverlayManager { // take the picture now. if (focused) { mState = STATE_SUCCESS; + // Lock exposure and white balance + if (mFocusTime != 200) { + setAeAwbLock(true); + mListener.setFocusParameters(); + } } else { mState = STATE_FAIL; } - updateFocusUI(); // If this is triggered by touch focus, cancel focus after a // while. if (mFocusArea != null) { - mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY); + mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, mFocusTime); } if (shutterButtonPressed) { // Lock AE & AWB so users can half-press shutter and recompose. @@ -313,32 +328,33 @@ public class FocusOverlayManager { if (!mInitialized) return; - // Ignore if the camera has detected some faces. - if (mUI.hasFaces()) { - mUI.clearFocus(); - if (mIsAFRunning) { - mUI.onFocusSucceeded(true); - mIsAFRunning = false; - } - return; - } - // Ignore if we have requested autofocus. This method only handles // continuous autofocus. if (mState != STATE_IDLE) return; // animate on false->true trasition only b/8219520 if (moving && !mPreviousMoving) { - mUI.onFocusStarted(); + mFocusRing.startPassiveFocus(); mIsAFRunning = true; } else if (!moving) { - mUI.onFocusSucceeded(true); + mFocusRing.stopFocusAnimations(); mIsAFRunning = false; } + + updateFocusDistance(); mPreviousMoving = moving; } - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + /** Returns width of auto focus region in pixels. */ + private int getAFRegionSizePx() { + return (int) (Math.min(mPreviewRect.width(), mPreviewRect.height()) * AF_REGION_BOX); + } + + /** Returns width of metering region in pixels. */ + private int getAERegionSizePx() { + return (int) (Math.min(mPreviewRect.width(), mPreviewRect.height()) * AE_REGION_BOX); + } + private void initializeFocusAreas(int x, int y) { if (mFocusArea == null) { mFocusArea = new ArrayList<Object>(); @@ -346,10 +362,9 @@ public class FocusOverlayManager { } // Convert the coordinates to driver format. - calculateTapArea(x, y, 1f, ((Area) mFocusArea.get(0)).rect); + ((Area) mFocusArea.get(0)).rect = computeCameraRectFromPreviewCoordinates(x, y, getAFRegionSizePx()); } - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) private void initializeMeteringAreas(int x, int y) { if (mMeteringArea == null) { mMeteringArea = new ArrayList<Object>(); @@ -359,7 +374,7 @@ public class FocusOverlayManager { // Convert the coordinates to driver format. // AE area is bigger because exposure is sensitive and // easy to over- or underexposure if area is too small. - calculateTapArea(x, y, 1.5f, ((Area) mMeteringArea.get(0)).rect); + ((Area) mMeteringArea.get(0)).rect = computeCameraRectFromPreviewCoordinates(x, y, getAERegionSizePx()); } private void resetMeteringAreas() { @@ -377,7 +392,10 @@ public class FocusOverlayManager { mState == STATE_SUCCESS || mState == STATE_FAIL)) { cancelAutoFocus(); } - if (mPreviewRect.width() == 0 || mPreviewRect.height() == 0) return; + if (mPreviewRect.width() == 0 || mPreviewRect.height() == 0 || + (y > (mDispSize.y - mBottomMargin) || y < mTopMargin)) { + return; + } // Initialize variables. // Initialize mFocusArea. if (mFocusAreaSupported) { @@ -388,8 +406,8 @@ public class FocusOverlayManager { initializeMeteringAreas(x, y); } - // Use margin to set the focus indicator to the touched area. - mUI.setFocusPosition(x, y); + mFocusRing.startActiveFocus(); + mFocusRing.setFocusLocation(x, y); if (mZslEnabled) { mTouchAFRunning = true; @@ -398,15 +416,18 @@ public class FocusOverlayManager { // Stop face detection because we want to specify focus and metering area. mListener.stopFaceDetection(); + if (mFocusTime == 200) { + setAeAwbLock(true); + mListener.setFocusParameters(); + } + // Set the focus area and metering area. mListener.setFocusParameters(); if (mFocusAreaSupported) { autoFocus(); } else { // Just show the indicator in all other cases. - updateFocusUI(); - // Reset the metering area in 3 seconds. mHandler.removeMessages(RESET_TOUCH_FOCUS); - mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY); + mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, mFocusTime); } } @@ -418,7 +439,6 @@ public class FocusOverlayManager { // If auto focus was in progress, it would have been stopped. mState = STATE_IDLE; resetTouchFocus(); - updateFocusUI(); } public void onCameraReleased() { @@ -430,7 +450,8 @@ public class FocusOverlayManager { Log.v(TAG, "Start autofocus."); mListener.autoFocus(); mState = STATE_FOCUSING; - updateFocusUI(); + // Pause the face view because the driver will keep sending face + // callbacks after the focus completes. mHandler.removeMessages(RESET_TOUCH_FOCUS); } @@ -441,10 +462,9 @@ public class FocusOverlayManager { // Otherwise, focus mode stays at auto and the tap area passed to the // driver is not reset. resetTouchFocus(); + setAeAwbLock(false); mListener.cancelAutoFocus(); - mUI.resumeFaceDetection(); mState = STATE_IDLE; - updateFocusUI(); mHandler.removeMessages(RESET_TOUCH_FOCUS); } @@ -455,7 +475,7 @@ public class FocusOverlayManager { } } - public String getFocusMode() { + public String getFocusMode(boolean fromVideo) { if (mOverrideFocusMode != null) return mOverrideFocusMode; if (mParameters == null) return Parameters.FOCUS_MODE_AUTO; List<String> supportedFocusModes = mParameters.getSupportedFocusModes(); @@ -465,8 +485,13 @@ public class FocusOverlayManager { mFocusMode = Parameters.FOCUS_MODE_AUTO; } else { // The default is continuous autofocus. - mFocusMode = mPreferences.getString( - CameraSettings.KEY_FOCUS_MODE, null); + if (fromVideo) { + mFocusMode = mPreferences.getString( + CameraSettings.KEY_VIDEOCAMERA_FOCUS_MODE, null); + } else { + mFocusMode = mPreferences.getString( + CameraSettings.KEY_FOCUS_MODE, null); + } // Try to find a supported focus mode from the default list. if (mFocusMode == null) { @@ -500,41 +525,18 @@ public class FocusOverlayManager { return mMeteringArea; } - public void updateFocusUI() { - if (!mInitialized) return; - // Show only focus indicator or face indicator. - - if (mState == STATE_IDLE) { - if (mFocusArea == null) { - mUI.clearFocus(); - } else { - // Users touch on the preview and the indicator represents the - // metering area. Either focus area is not supported or - // autoFocus call is not required. - mUI.onFocusStarted(); - } - } else if (mState == STATE_FOCUSING || mState == STATE_FOCUSING_SNAP_ON_FINISH) { - mUI.onFocusStarted(); + public void restartTouchFocusTimer() { + if (mZslEnabled && (mFocusArea != null) && (mFocusTime != 0x7FFFFFFF)) { + mHandler.removeMessages(RESET_TOUCH_FOCUS); + mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, mFocusTime); } else { - if (CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusMode)) { - // TODO: check HAL behavior and decide if this can be removed. - mUI.onFocusSucceeded(false); - } else if (mState == STATE_SUCCESS) { - mUI.onFocusSucceeded(false); - } else if (mState == STATE_FAIL) { - mUI.onFocusFailed(false); - } + resetTouchFocus(); } } public void resetTouchFocus() { if (!mInitialized) return; - // Put focus indicator to the center. clear reset position - if (mUI != null) { - mUI.clearFocus(); - } - // Initialize mFocusArea. mFocusArea = null; // Initialize mMeteringArea. mMeteringArea = null; @@ -549,16 +551,14 @@ public class FocusOverlayManager { } } - private void calculateTapArea(int x, int y, float areaMultiple, Rect rect) { - int areaSize = (int) (getAreaSize() * areaMultiple); - int left = CameraUtil.clamp(x - areaSize / 2, mPreviewRect.left, - mPreviewRect.right - areaSize); - int top = CameraUtil.clamp(y - areaSize / 2, mPreviewRect.top, - mPreviewRect.bottom - areaSize); + private Rect computeCameraRectFromPreviewCoordinates(int x, int y, int size) { + int left = CameraUtil.clamp(x - size / 2, mPreviewRect.left, + mPreviewRect.right - size); + int top = CameraUtil.clamp(y - size / 2, mPreviewRect.top, + mPreviewRect.bottom - size); - RectF rectF = new RectF(left, top, left + areaSize, top + areaSize); - mMatrix.mapRect(rectF); - CameraUtil.rectFToRect(rectF, rect); + RectF rectF = new RectF(left, top, left + size, top + size); + return CameraUtil.rectFToRect(mCoordinateTransformer.toCameraSpace(rectF)); } private int getAreaSize() { @@ -600,10 +600,8 @@ public class FocusOverlayManager { } private boolean needAutoFocusCall() { - String focusMode = getFocusMode(); - return !(focusMode.equals(Parameters.FOCUS_MODE_INFINITY) - || focusMode.equals(Parameters.FOCUS_MODE_FIXED) - || focusMode.equals(Parameters.FOCUS_MODE_EDOF)); + return getFocusMode(false).equals(Parameters.FOCUS_MODE_AUTO) && + !(mZslEnabled && (mHandler.hasMessages(RESET_TOUCH_FOCUS))); } public void setZslEnable(boolean value) { @@ -618,4 +616,92 @@ public class FocusOverlayManager { return mTouchAFRunning; } + private static class FocusInfo { + public final float near; + public final float far; + public final float current; + + public FocusInfo(float _near, float _far, float _current) { + near = _near; + far = _far; + current = _current; + } + } + + private FocusInfo getFocusInfoFromParameters( + String currentParam, String minParam, String maxParam) { + try { + String current = mParameters.get(currentParam); + if (current != null) { + float min = Float.parseFloat(mParameters.get(minParam)); + float max = Float.parseFloat(mParameters.get(maxParam)); + if (!(min == 0.0f && max == 0.0f)) { + return new FocusInfo(min, max, Float.parseFloat(current)); + } + } + } catch (Exception e) { + // skip it + } + return null; + } + + private FocusInfo getFocusInfo() { + // focus positon is horrifically buggy on some HALs. try to + // make the best of it and attempt a few different techniques + // to get an accurate measurement + + // older QCOM (Bacon) + FocusInfo info = getFocusInfoFromParameters("current-focus-position", + "min-focus-pos-index", "max-focus-pos-index"); + if (info != null) { + return info; + } + + // newer QCOM (Crackling) + info = getFocusInfoFromParameters("cur-focus-scale", + "min-focus-pos-ratio", "max-focus-pos-ratio"); + if (info != null) { + return info; + } + + return null; + } + + /** + * Compute the focus range from the camera characteristics and build + * a linear scale model that maps a focus distance to a ratio between + * the min and max range. + */ + private LinearScale getDiopterToRatioCalculator(FocusInfo focusInfo) { + // From the android documentation: + // + // 0.0f represents farthest focus, and LENS_INFO_MINIMUM_FOCUS_DISTANCE + // represents the nearest focus the device can achieve. + // + // Example: + // + // Infinity Hyperfocal Minimum Camera + // <----------|-----------------------------| | + // [0.0] [0.31] [14.29] + if (focusInfo.near == 0.0f && focusInfo.far == 0.0f) { + return new LinearScale(0, 0, 0, 0); + } + + if (focusInfo.near > focusInfo.far) { + return new LinearScale(focusInfo.far, focusInfo.near, 0, 1); + } + + return new LinearScale(focusInfo.near, focusInfo.far, 0, 1); + } + + private void updateFocusDistance() { + final FocusInfo focusInfo = getFocusInfo(); + if (focusInfo != null) { + LinearScale range = getDiopterToRatioCalculator(focusInfo); + if (range.isInDomain(focusInfo.current) && (mFocusRing.isPassiveFocusRunning() || + mFocusRing.isActiveFocusRunning())) { + mListener.setFocusRatio(range.scale(focusInfo.current)); + } + } + } } |