summaryrefslogtreecommitdiffstats
path: root/src/com/android/camera/FocusOverlayManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/camera/FocusOverlayManager.java')
-rw-r--r--src/com/android/camera/FocusOverlayManager.java310
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));
+ }
+ }
+ }
}