summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server/accessibility/TouchExplorer.java
diff options
context:
space:
mode:
authorSvetoslav Ganov <svetoslavganov@google.com>2012-08-06 10:53:34 -0700
committerSvetoslav Ganov <svetoslavganov@google.com>2012-09-06 18:56:17 -0700
commit1cf70bbf96930662cab0e699d70b62865766ff52 (patch)
tree2173762d4e4d7be76f5691cebd74e1bd8f2b8543 /services/java/com/android/server/accessibility/TouchExplorer.java
parentfa8d83d90444354e8eca6ca0e080bc917e5a1f32 (diff)
downloadframeworks_base-1cf70bbf96930662cab0e699d70b62865766ff52.zip
frameworks_base-1cf70bbf96930662cab0e699d70b62865766ff52.tar.gz
frameworks_base-1cf70bbf96930662cab0e699d70b62865766ff52.tar.bz2
Screen magnification - feature - framework.
This change is the initial check in of the screen magnification feature. This feature enables magnification of the screen via global gestures (assuming it has been enabled from settings) to allow a low vision user to efficiently use an Android device. Interaction model: 1. Triple tap toggles permanent screen magnification which is magnifying the area around the location of the triple tap. One can think of the location of the triple tap as the center of the magnified viewport. For example, a triple tap when not magnified would magnify the screen and leave it in a magnified state. A triple tapping when magnified would clear magnification and leave the screen in a not magnified state. 2. Triple tap and hold would magnify the screen if not magnified and enable viewport dragging mode until the finger goes up. One can think of this mode as a way to move the magnified viewport since the area around the moving finger will be magnified to fit the screen. For example, if the screen was not magnified and the user triple taps and holds the screen would magnify and the viewport will follow the user's finger. When the finger goes up the screen will clear zoom out. If the same user interaction is performed when the screen is magnified, the viewport movement will be the same but when the finger goes up the screen will stay magnified. In other words, the initial magnified state is sticky. 3. Pinching with any number of additional fingers when viewport dragging is enabled, i.e. the user triple tapped and holds, would adjust the magnification scale which will become the current default magnification scale. The next time the user magnifies the same magnification scale would be used. 4. When in a permanent magnified state the user can use two or more fingers to pan the viewport. Note that in this mode the content is panned as opposed to the viewport dragging mode in which the viewport is moved. 5. When in a permanent magnified state the user can use three or more fingers to change the magnification scale which will become the current default magnification scale. The next time the user magnifies the same magnification scale would be used. 6. The magnification scale will be persisted in settings and in the cloud. Note: Since two fingers are used to pan the content in a permanently magnified state no other two finger gestures in touch exploration or applications will work unless the uses zooms out to normal state where all gestures works as expected. This is an intentional tradeoff to allow efficient panning since in a permanently magnified state this would be the dominant action to be performed. Design: 1. The window manager exposes APIs for setting accessibility transformation which is a scale and offsets for X and Y axis. The window manager queries the window policy for which windows will not be magnified. For example, the IME windows and the navigation bar are not magnified including windows that are attached to them. 2. The accessibility features such a screen magnification and touch exploration are now impemented as a sequence of transformations on the event stream. The accessibility manager service may request each of these features or both. The behavior of the features is not changed based on the fact that another one is enabled. 3. The screen magnifier keeps a viewport of the content that is magnified which is surrounded by a glow in a magnified state. Interactions outside of the viewport are delegated directly to the application without interpretation. For example, a triple tap on the letter 'a' of the IME would type three letters instead of toggling magnified state. The viewport is updated on screen rotation and on window transitions. For example, when the IME pops up the viewport shrinks. 4. The glow around the viewport is implemented as a special type of window that does not take input focus, cannot be touched, is laid out in the screen coordiates with width and height matching these of the screen. When the magnified region changes the root view of the window draws the hightlight but the size of the window does not change - unless a rotation happens. All changes in the viewport size or showing or hiding it are animated. 5. The viewport is encapsulated in a class that knows how to show, hide, and resize the viewport - potentially animating that. This class uses the new animation framework for animations. 6. The magnification is handled by a magnification controller that keeps track of the current trnasformation to be applied to the screen content and the desired such. If these two are not the same it is responsibility of the magnification controller to reconcile them by potentially animating the transition from one to the other. 7. A dipslay content observer wathces for winodw transitions, screen rotations, and when a rectange on the screen has been reqeusted. This class is responsible for handling interesting state changes such as changing the viewport bounds on IME pop up or screen rotation, panning the content to make a requested rectangle visible on the screen, etc. 8. To implement viewport updates the window manger was updated with APIs to watch for window transitions and when a rectangle has been requested on the screen. These APIs are protected by a signature level permission. Also a parcelable and poolable window info class has been added with APIs for getting the window info given the window token. This enables getting some useful information about a window. There APIs are also signature protected. bug:6795382 Change-Id: Iec93da8bf6376beebbd4f5167ab7723dc7d9bd00
Diffstat (limited to 'services/java/com/android/server/accessibility/TouchExplorer.java')
-rw-r--r--services/java/com/android/server/accessibility/TouchExplorer.java135
1 files changed, 42 insertions, 93 deletions
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
index ba9f2cd..9e4f33e 100644
--- a/services/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -28,7 +28,6 @@ import android.graphics.Rect;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Slog;
-import android.view.InputFilter;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
@@ -64,7 +63,7 @@ import java.util.Arrays;
*
* @hide
*/
-public class TouchExplorer {
+class TouchExplorer implements EventStreamTransformation {
private static final boolean DEBUG = false;
@@ -120,10 +119,6 @@ public class TouchExplorer {
// Slop between the first and second tap to be a double tap.
private final int mDoubleTapSlop;
- // The InputFilter this tracker is associated with i.e. the filter
- // which delegates event processing to this touch explorer.
- private final InputFilter mInputFilter;
-
// The current state of the touch explorer.
private int mCurrentState = STATE_TOUCH_EXPLORING;
@@ -155,6 +150,9 @@ public class TouchExplorer {
// The scaled velocity above which we detect gestures.
private final int mScaledGestureDetectionVelocity;
+ // The handler to which to delegate events.
+ private EventStreamTransformation mNext;
+
// Helper to track gesture velocity.
private VelocityTracker mVelocityTracker;
@@ -206,12 +204,10 @@ public class TouchExplorer {
* @param inputFilter The input filter associated with this explorer.
* @param context A context handle for accessing resources.
*/
- public TouchExplorer(InputFilter inputFilter, Context context,
- AccessibilityManagerService service) {
+ public TouchExplorer(Context context, AccessibilityManagerService service) {
mAms = service;
mReceivedPointerTracker = new ReceivedPointerTracker(context);
mInjectedPointerTracker = new InjectedPointerTracker();
- mInputFilter = inputFilter;
mTapTimeout = ViewConfiguration.getTapTimeout();
mDetermineUserIntentTimeout = (int) (mTapTimeout * 1.5f);
mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
@@ -242,7 +238,11 @@ public class TouchExplorer {
}
}
- public void clear(MotionEvent event, int policyFlags) {
+ public void onDestroy() {
+ // TODO: Implement
+ }
+
+ private void clear(MotionEvent event, int policyFlags) {
switch (mCurrentState) {
case STATE_TOUCH_EXPLORING: {
// If a touch exploration gesture is in progress send events for its end.
@@ -278,8 +278,17 @@ public class TouchExplorer {
mLongPressingPointerDeltaX = 0;
mLongPressingPointerDeltaY = 0;
mCurrentState = STATE_TOUCH_EXPLORING;
+ if (mNext != null) {
+ mNext.clear();
+ }
}
+ @Override
+ public void setNext(EventStreamTransformation next) {
+ mNext = next;
+ }
+
+ @Override
public void onMotionEvent(MotionEvent event, int policyFlags) {
if (DEBUG) {
Slog.d(LOG_TAG, "Received event: " + event + ", policyFlags=0x"
@@ -325,6 +334,9 @@ public class TouchExplorer {
mLastTouchedWindowId = event.getWindowId();
} break;
}
+ if (mNext != null) {
+ mNext.onAccessibilityEvent(event);
+ }
}
/**
@@ -958,7 +970,9 @@ public class TouchExplorer {
// Make sure that the user will see the event.
policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
- mInputFilter.sendInputEvent(event, policyFlags);
+ if (mNext != null) {
+ mNext.onMotionEvent(event, policyFlags);
+ }
mInjectedPointerTracker.onMotionEvent(event);
@@ -1008,11 +1022,13 @@ public class TouchExplorer {
private MotionEvent mFirstTapEvent;
public void onMotionEvent(MotionEvent event, int policyFlags) {
+ final int actionIndex = event.getActionIndex();
final int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: {
- if (mFirstTapEvent != null && !isSamePointerContext(mFirstTapEvent, event)) {
+ if (mFirstTapEvent != null
+ && !GestureUtils.isSamePointerContext(mFirstTapEvent, event)) {
clear();
}
mDownEvent = MotionEvent.obtain(event);
@@ -1022,19 +1038,21 @@ public class TouchExplorer {
if (mDownEvent == null) {
return;
}
- if (!isSamePointerContext(mDownEvent, event)) {
+ if (!GestureUtils.isSamePointerContext(mDownEvent, event)) {
clear();
return;
}
- if (isTap(mDownEvent, event)) {
- if (mFirstTapEvent == null || isTimedOut(mFirstTapEvent, event,
- mDoubleTapTimeout)) {
+ if (GestureUtils.isTap(mDownEvent, event, mTapTimeout, mTouchSlop,
+ actionIndex)) {
+ if (mFirstTapEvent == null || GestureUtils.isTimedOut(mFirstTapEvent,
+ event, mDoubleTapTimeout)) {
mFirstTapEvent = MotionEvent.obtain(event);
mDownEvent.recycle();
mDownEvent = null;
return;
}
- if (isDoubleTap(mFirstTapEvent, event)) {
+ if (GestureUtils.isMultiTap(mFirstTapEvent, event, mDoubleTapTimeout,
+ mDoubleTapSlop, actionIndex)) {
onDoubleTap(event, policyFlags);
mFirstTapEvent.recycle();
mFirstTapEvent = null;
@@ -1140,42 +1158,6 @@ public class TouchExplorer {
}
}
- public boolean isTap(MotionEvent down, MotionEvent up) {
- return eventsWithinTimeoutAndDistance(down, up, mTapTimeout, mTouchSlop);
- }
-
- private boolean isDoubleTap(MotionEvent firstUp, MotionEvent secondUp) {
- return eventsWithinTimeoutAndDistance(firstUp, secondUp, mDoubleTapTimeout,
- mDoubleTapSlop);
- }
-
- private boolean eventsWithinTimeoutAndDistance(MotionEvent first, MotionEvent second,
- int timeout, int distance) {
- if (isTimedOut(first, second, timeout)) {
- return false;
- }
- final int downPtrIndex = first.getActionIndex();
- final int upPtrIndex = second.getActionIndex();
- final float deltaX = second.getX(upPtrIndex) - first.getX(downPtrIndex);
- final float deltaY = second.getY(upPtrIndex) - first.getY(downPtrIndex);
- final double deltaMove = Math.hypot(deltaX, deltaY);
- if (deltaMove >= distance) {
- return false;
- }
- return true;
- }
-
- private boolean isTimedOut(MotionEvent firstUp, MotionEvent secondUp, int timeout) {
- final long deltaTime = secondUp.getEventTime() - firstUp.getEventTime();
- return (deltaTime >= timeout);
- }
-
- private boolean isSamePointerContext(MotionEvent first, MotionEvent second) {
- return (first.getPointerIdBits() == second.getPointerIdBits()
- && first.getPointerId(first.getActionIndex())
- == second.getPointerId(second.getActionIndex()));
- }
-
public boolean firstTapDetected() {
return mFirstTapEvent != null
&& SystemClock.uptimeMillis() - mFirstTapEvent.getEventTime() < mDoubleTapTimeout;
@@ -1201,47 +1183,14 @@ public class TouchExplorer {
final float secondPtrX = event.getX(secondPtrIndex);
final float secondPtrY = event.getY(secondPtrIndex);
- // Check if the pointers are moving in the same direction.
- final float firstDeltaX =
- firstPtrX - receivedTracker.getReceivedPointerDownX(firstPtrIndex);
- final float firstDeltaY =
- firstPtrY - receivedTracker.getReceivedPointerDownY(firstPtrIndex);
-
- if (firstDeltaX == 0 && firstDeltaY == 0) {
- return true;
- }
-
- final float firstMagnitude =
- (float) Math.sqrt(firstDeltaX * firstDeltaX + firstDeltaY * firstDeltaY);
- final float firstXNormalized =
- (firstMagnitude > 0) ? firstDeltaX / firstMagnitude : firstDeltaX;
- final float firstYNormalized =
- (firstMagnitude > 0) ? firstDeltaY / firstMagnitude : firstDeltaY;
-
- final float secondDeltaX =
- secondPtrX - receivedTracker.getReceivedPointerDownX(secondPtrIndex);
- final float secondDeltaY =
- secondPtrY - receivedTracker.getReceivedPointerDownY(secondPtrIndex);
-
- if (secondDeltaX == 0 && secondDeltaY == 0) {
- return true;
- }
-
- final float secondMagnitude =
- (float) Math.sqrt(secondDeltaX * secondDeltaX + secondDeltaY * secondDeltaY);
- final float secondXNormalized =
- (secondMagnitude > 0) ? secondDeltaX / secondMagnitude : secondDeltaX;
- final float secondYNormalized =
- (secondMagnitude > 0) ? secondDeltaY / secondMagnitude : secondDeltaY;
-
- final float angleCos =
- firstXNormalized * secondXNormalized + firstYNormalized * secondYNormalized;
-
- if (angleCos < MAX_DRAGGING_ANGLE_COS) {
- return false;
- }
+ final float firstPtrDownX = receivedTracker.getReceivedPointerDownX(firstPtrIndex);
+ final float firstPtrDownY = receivedTracker.getReceivedPointerDownY(firstPtrIndex);
+ final float secondPtrDownX = receivedTracker.getReceivedPointerDownX(secondPtrIndex);
+ final float secondPtrDownY = receivedTracker.getReceivedPointerDownY(secondPtrIndex);
- return true;
+ return GestureUtils.isDraggingGesture(firstPtrDownX, firstPtrDownY, secondPtrDownX,
+ secondPtrDownY, firstPtrX, firstPtrY, secondPtrX, secondPtrY,
+ MAX_DRAGGING_ANGLE_COS);
}
/**