summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSvetoslav Ganov <svetoslavganov@google.com>2012-09-10 10:54:25 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2012-09-10 10:54:26 -0700
commit7e8f6c4cef8d65c5b470fc8700214e28d8cd4d43 (patch)
treeef33216d9801e71f20d37a9dcaddae2476b819dc
parent3c1a20118739fd6765e128c5d9c39c01d22a89e7 (diff)
parent0c381504a8fce293b3b9ef8ad0333849c43eb6a4 (diff)
downloadframeworks_base-7e8f6c4cef8d65c5b470fc8700214e28d8cd4d43.zip
frameworks_base-7e8f6c4cef8d65c5b470fc8700214e28d8cd4d43.tar.gz
frameworks_base-7e8f6c4cef8d65c5b470fc8700214e28d8cd4d43.tar.bz2
Merge "Improve scaling vs pan in screen magnifier." into jb-mr1-dev
-rw-r--r--services/java/com/android/server/accessibility/ScreenMagnifier.java489
1 files changed, 483 insertions, 6 deletions
diff --git a/services/java/com/android/server/accessibility/ScreenMagnifier.java b/services/java/com/android/server/accessibility/ScreenMagnifier.java
index f33517b..62d410b 100644
--- a/services/java/com/android/server/accessibility/ScreenMagnifier.java
+++ b/services/java/com/android/server/accessibility/ScreenMagnifier.java
@@ -46,10 +46,9 @@ import android.view.Gravity;
import android.view.IDisplayContentChangeListener;
import android.view.IWindowManager;
import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
-import android.view.ScaleGestureDetector.OnScaleGestureListener;
+import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener;
import android.view.Surface;
import android.view.View;
import android.view.ViewConfiguration;
@@ -370,7 +369,7 @@ public final class ScreenMagnifier implements EventStreamTransformation {
public GestureDetector(Context context) {
final float density = context.getResources().getDisplayMetrics().density;
mScaledDetectPanningThreshold = DETECT_PANNING_THRESHOLD_DIP * density;
- mScaleGestureDetector = new ScaleGestureDetector(context, this);
+ mScaleGestureDetector = new ScaleGestureDetector(this);
}
public void onMotionEvent(MotionEvent event) {
@@ -409,7 +408,7 @@ public final class ScreenMagnifier implements EventStreamTransformation {
performScale(detector, true);
clear();
transitionToState(STATE_SCALING);
- return false;
+ return true;
}
mCurrPan = (float) MathUtils.dist(
mScaleGestureDetector.getFocusX(),
@@ -423,7 +422,7 @@ public final class ScreenMagnifier implements EventStreamTransformation {
performPan(detector, true);
clear();
transitionToState(STATE_PANNING);
- return false;
+ return true;
}
} break;
case STATE_SCALING: {
@@ -460,7 +459,7 @@ public final class ScreenMagnifier implements EventStreamTransformation {
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
- /* do nothing */
+ clear();
}
public void clear() {
@@ -1763,4 +1762,482 @@ public final class ScreenMagnifier implements EventStreamTransformation {
updateDisplayInfo();
}
}
+
+ /**
+ * The listener for receiving notifications when gestures occur.
+ * If you want to listen for all the different gestures then implement
+ * this interface. If you only want to listen for a subset it might
+ * be easier to extend {@link SimpleOnScaleGestureListener}.
+ *
+ * An application will receive events in the following order:
+ * <ul>
+ * <li>One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)}
+ * <li>Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)}
+ * <li>One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)}
+ * </ul>
+ */
+ interface OnScaleGestureListener {
+ /**
+ * Responds to scaling events for a gesture in progress.
+ * Reported by pointer motion.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return Whether or not the detector should consider this event
+ * as handled. If an event was not handled, the detector
+ * will continue to accumulate movement until an event is
+ * handled. This can be useful if an application, for example,
+ * only wants to update scaling factors if the change is
+ * greater than 0.01.
+ */
+ public boolean onScale(ScaleGestureDetector detector);
+
+ /**
+ * Responds to the beginning of a scaling gesture. Reported by
+ * new pointers going down.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return Whether or not the detector should continue recognizing
+ * this gesture. For example, if a gesture is beginning
+ * with a focal point outside of a region where it makes
+ * sense, onScaleBegin() may return false to ignore the
+ * rest of the gesture.
+ */
+ public boolean onScaleBegin(ScaleGestureDetector detector);
+
+ /**
+ * Responds to the end of a scale gesture. Reported by existing
+ * pointers going up.
+ *
+ * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
+ * and {@link ScaleGestureDetector#getFocusY()} will return the location
+ * of the pointer remaining on the screen.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ */
+ public void onScaleEnd(ScaleGestureDetector detector);
+ }
+
+ class ScaleGestureDetector {
+
+ private final MinCircleFinder mMinCircleFinder = new MinCircleFinder();
+
+ private final OnScaleGestureListener mListener;
+
+ private float mFocusX;
+ private float mFocusY;
+
+ private float mCurrSpan;
+ private float mPrevSpan;
+ private float mCurrSpanX;
+ private float mCurrSpanY;
+ private float mPrevSpanX;
+ private float mPrevSpanY;
+ private long mCurrTime;
+ private long mPrevTime;
+ private boolean mInProgress;
+
+ public ScaleGestureDetector(OnScaleGestureListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
+ * when appropriate.
+ *
+ * <p>Applications should pass a complete and consistent event stream to this method.
+ * A complete and consistent event stream involves all MotionEvents from the initial
+ * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p>
+ *
+ * @param event The event to process
+ * @return true if the event was processed and the detector wants to receive the
+ * rest of the MotionEvents in this event stream.
+ */
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean streamEnded = false;
+ boolean contextChanged = false;
+ int excludedPtrIdx = -1;
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ contextChanged = true;
+ } break;
+ case MotionEvent.ACTION_POINTER_UP: {
+ contextChanged = true;
+ excludedPtrIdx = event.getActionIndex();
+ } break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL: {
+ streamEnded = true;
+ } break;
+ }
+
+ if (mInProgress && (contextChanged || streamEnded)) {
+ mListener.onScaleEnd(this);
+ mInProgress = false;
+ mPrevSpan = 0;
+ mPrevSpanX = 0;
+ mPrevSpanY = 0;
+ return true;
+ }
+
+ final long currTime = mCurrTime;
+
+ mFocusX = 0;
+ mFocusY = 0;
+ mCurrSpan = 0;
+ mCurrSpanX = 0;
+ mCurrSpanY = 0;
+ mCurrTime = 0;
+ mPrevTime = 0;
+
+ if (!streamEnded) {
+ MinCircleFinder.Circle circle =
+ mMinCircleFinder.computeMinCircleAroundPointers(event);
+ mFocusX = circle.centerX;
+ mFocusY = circle.centerY;
+
+ double sumSlope = 0;
+ final int pointerCount = event.getPointerCount();
+ for (int i = 0; i < pointerCount; i++) {
+ if (i == excludedPtrIdx) {
+ continue;
+ }
+ float x = event.getX(i) - mFocusX;
+ float y = event.getY(i) - mFocusY;
+ if (x == 0) {
+ x += 0.1f;
+ }
+ sumSlope += y / x;
+ }
+ final double avgSlope = sumSlope
+ / ((excludedPtrIdx < 0) ? pointerCount : pointerCount - 1);
+
+ double angle = Math.atan(avgSlope);
+ mCurrSpan = 2 * circle.radius;
+ mCurrSpanX = (float) Math.abs((Math.cos(angle) * mCurrSpan));
+ mCurrSpanY = (float) Math.abs((Math.sin(angle) * mCurrSpan));
+ }
+
+ if (contextChanged || mPrevSpan == 0 || mPrevSpanX == 0 || mPrevSpanY == 0) {
+ mPrevSpan = mCurrSpan;
+ mPrevSpanX = mCurrSpanX;
+ mPrevSpanY = mCurrSpanY;
+ }
+
+ if (!mInProgress && mCurrSpan != 0 && !streamEnded) {
+ mInProgress = mListener.onScaleBegin(this);
+ }
+
+ if (mInProgress) {
+ mPrevTime = (currTime != 0) ? currTime : event.getEventTime();
+ mCurrTime = event.getEventTime();
+ if (mCurrSpan == 0) {
+ mListener.onScaleEnd(this);
+ mInProgress = false;
+ } else {
+ if (mListener.onScale(this)) {
+ mPrevSpanX = mCurrSpanX;
+ mPrevSpanY = mCurrSpanY;
+ mPrevSpan = mCurrSpan;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns {@code true} if a scale gesture is in progress.
+ */
+ public boolean isInProgress() {
+ return mInProgress;
+ }
+
+ /**
+ * Get the X coordinate of the current gesture's focal point.
+ * If a gesture is in progress, the focal point is between
+ * each of the pointers forming the gesture.
+ *
+ * If {@link #isInProgress()} would return false, the result of this
+ * function is undefined.
+ *
+ * @return X coordinate of the focal point in pixels.
+ */
+ public float getFocusX() {
+ return mFocusX;
+ }
+
+ /**
+ * Get the Y coordinate of the current gesture's focal point.
+ * If a gesture is in progress, the focal point is between
+ * each of the pointers forming the gesture.
+ *
+ * If {@link #isInProgress()} would return false, the result of this
+ * function is undefined.
+ *
+ * @return Y coordinate of the focal point in pixels.
+ */
+ public float getFocusY() {
+ return mFocusY;
+ }
+
+ /**
+ * Return the average distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public float getCurrentSpan() {
+ return mCurrSpan;
+ }
+
+ /**
+ * Return the average X distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public float getCurrentSpanX() {
+ return mCurrSpanX;
+ }
+
+ /**
+ * Return the average Y distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public float getCurrentSpanY() {
+ return mCurrSpanY;
+ }
+
+ /**
+ * Return the previous average distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public float getPreviousSpan() {
+ return mPrevSpan;
+ }
+
+ /**
+ * Return the previous average X distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public float getPreviousSpanX() {
+ return mPrevSpanX;
+ }
+
+ /**
+ * Return the previous average Y distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public float getPreviousSpanY() {
+ return mPrevSpanY;
+ }
+
+ /**
+ * Return the scaling factor from the previous scale event to the current
+ * event. This value is defined as
+ * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
+ *
+ * @return The current scaling factor.
+ */
+ public float getScaleFactor() {
+ return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
+ }
+
+ /**
+ * Return the time difference in milliseconds between the previous
+ * accepted scaling event and the current scaling event.
+ *
+ * @return Time difference since the last scaling event in milliseconds.
+ */
+ public long getTimeDelta() {
+ return mCurrTime - mPrevTime;
+ }
+
+ /**
+ * Return the event time of the current event being processed.
+ *
+ * @return Current event time in milliseconds.
+ */
+ public long getEventTime() {
+ return mCurrTime;
+ }
+ }
+
+ private static final class MinCircleFinder {
+ private final ArrayList<PointHolder> mPoints = new ArrayList<PointHolder>();
+ private final ArrayList<PointHolder> sBoundary = new ArrayList<PointHolder>();
+ private final Circle mMinCircle = new Circle();
+
+ /**
+ * Finds the minimal circle that contains all pointers of a motion event.
+ *
+ * @param event A motion event.
+ * @return The minimal circle.
+ */
+ public Circle computeMinCircleAroundPointers(MotionEvent event) {
+ ArrayList<PointHolder> points = mPoints;
+ points.clear();
+ final int pointerCount = event.getPointerCount();
+ for (int i = 0; i < pointerCount; i++) {
+ PointHolder point = PointHolder.obtain(event.getX(i), event.getY(i));
+ points.add(point);
+ }
+ ArrayList<PointHolder> boundary = sBoundary;
+ boundary.clear();
+ computeMinCircleAroundPointsRecursive(points, boundary, mMinCircle);
+ for (int i = points.size() - 1; i >= 0; i--) {
+ points.remove(i).recycle();
+ }
+ boundary.clear();
+ return mMinCircle;
+ }
+
+ private static void computeMinCircleAroundPointsRecursive(ArrayList<PointHolder> points,
+ ArrayList<PointHolder> boundary, Circle outCircle) {
+ if (points.isEmpty()) {
+ if (boundary.size() == 0) {
+ outCircle.initialize();
+ } else if (boundary.size() == 1) {
+ outCircle.initialize(boundary.get(0).mData, boundary.get(0).mData);
+ } else if (boundary.size() == 2) {
+ outCircle.initialize(boundary.get(0).mData, boundary.get(1).mData);
+ } else if (boundary.size() == 3) {
+ outCircle.initialize(boundary.get(0).mData, boundary.get(1).mData,
+ boundary.get(2).mData);
+ }
+ return;
+ }
+ PointHolder point = points.remove(points.size() - 1);
+ computeMinCircleAroundPointsRecursive(points, boundary, outCircle);
+ if (!outCircle.contains(point.mData)) {
+ boundary.add(point);
+ computeMinCircleAroundPointsRecursive(points, boundary, outCircle);
+ boundary.remove(point);
+ }
+ points.add(point);
+ }
+
+ private static final class PointHolder {
+ private static final int MAX_POOL_SIZE = 20;
+ private static PointHolder sPool;
+ private static int sPoolSize;
+
+ private PointHolder mNext;
+ private boolean mIsInPool;
+
+ private final PointF mData = new PointF();
+
+ public static PointHolder obtain(float x, float y) {
+ PointHolder holder;
+ if (sPoolSize > 0) {
+ sPoolSize--;
+ holder = sPool;
+ sPool = sPool.mNext;
+ holder.mNext = null;
+ holder.mIsInPool = false;
+ } else {
+ holder = new PointHolder();
+ }
+ holder.mData.set(x, y);
+ return holder;
+ }
+
+ public void recycle() {
+ if (mIsInPool) {
+ throw new IllegalStateException("Already recycled.");
+ }
+ clear();
+ if (sPoolSize < MAX_POOL_SIZE) {
+ sPoolSize++;
+ mNext = sPool;
+ sPool = this;
+ mIsInPool = true;
+ }
+ }
+
+ private void clear() {
+ mData.set(0, 0);
+ }
+ }
+
+ public static final class Circle {
+ public float centerX;
+ public float centerY;
+ public float radius;
+
+ private void initialize() {
+ centerX = 0;
+ centerY = 0;
+ radius = 0;
+ }
+
+ private void initialize(PointF first, PointF second, PointF third) {
+ if (!hasLineWithInfiniteSlope(first, second, third)) {
+ initializeInternal(first, second, third);
+ } else if (!hasLineWithInfiniteSlope(first, third, second)) {
+ initializeInternal(first, third, second);
+ } else if (!hasLineWithInfiniteSlope(second, first, third)) {
+ initializeInternal(second, first, third);
+ } else if (!hasLineWithInfiniteSlope(second, third, first)) {
+ initializeInternal(second, third, first);
+ } else if (!hasLineWithInfiniteSlope(third, first, second)) {
+ initializeInternal(third, first, second);
+ } else if (!hasLineWithInfiniteSlope(third, second, first)) {
+ initializeInternal(third, second, first);
+ } else {
+ initialize();
+ }
+ }
+
+ private void initialize(PointF first, PointF second) {
+ radius = (float) (Math.hypot(second.x - first.x, second.y - first.y) / 2);
+ centerX = (float) (second.x + first.x) / 2;
+ centerY = (float) (second.y + first.y) / 2;
+ }
+
+ public boolean contains(PointF point) {
+ return (int) (Math.hypot(point.x - centerX, point.y - centerY)) <= radius;
+ }
+
+ private void initializeInternal(PointF first, PointF second, PointF third) {
+ final float x1 = first.x;
+ final float y1 = first.y;
+ final float x2 = second.x;
+ final float y2 = second.y;
+ final float x3 = third.x;
+ final float y3 = third.y;
+
+ final float sl1 = (y2 - y1) / (x2 - x1);
+ final float sl2 = (y3 - y2) / (x3 - x2);
+
+ centerX = (int) ((sl1 * sl2 * (y1 - y3) + sl2 * (x1 + x2) - sl1 * (x2 + x3))
+ / (2 * (sl2 - sl1)));
+ centerY = (int) (-1 / sl1 * (centerX - (x1 + x2) / 2) + (y1 + y2) / 2);
+ radius = (int) Math.hypot(x1 - centerX, y1 - centerY);
+ }
+
+ private boolean hasLineWithInfiniteSlope(PointF first, PointF second, PointF third) {
+ return (second.x - first.x == 0 || third.x - second.x == 0
+ || second.y - first.y == 0 || third.y - second.y == 0);
+ }
+
+ @Override
+ public String toString() {
+ return "cetner: [" + centerX + ", " + centerY + "] radius: " + radius;
+ }
+ }
+ }
}