summaryrefslogtreecommitdiff
path: root/services/accessibility
diff options
context:
space:
mode:
authorAmeer Armaly <aarmaly@google.com>2020-10-13 20:51:02 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-10-13 20:51:02 +0000
commit5e27076f33dc287e5988cec2f32981365b8888c1 (patch)
tree826875b90143810725a59bc636cfbdf1b815bb39 /services/accessibility
parent365c0eeaf9a5c85d6a189c910f1805975b105e43 (diff)
parent0295f94356b40d4582a12dd061160d97f216b473 (diff)
[DO NOT MERGE ] Stop setting flag to enable two-finger passthrough swipes. am: 0295f94356
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/12792551 Change-Id: I06eb8c5ac72bc34032324c0811ebc90b869bc93e
Diffstat (limited to 'services/accessibility')
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java12
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java13
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java6
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java12
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java74
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java35
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java232
8 files changed, 337 insertions, 49 deletions
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 40a2816ee7de..8822d55cc30f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -148,6 +148,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
private boolean mRequestMultiFingerGestures;
+ private boolean mRequestTwoFingerPassthrough;
+
boolean mRequestFilterKeyEvents;
boolean mRetrieveInteractiveWindows;
@@ -323,8 +325,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
& AccessibilityServiceInfo.FLAG_SERVICE_HANDLES_DOUBLE_TAP) != 0;
mRequestMultiFingerGestures = (info.flags
& AccessibilityServiceInfo.FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0;
- mRequestFilterKeyEvents = (info.flags
- & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
+ mRequestTwoFingerPassthrough =
+ (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0;
+ mRequestFilterKeyEvents =
+ (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
mRetrieveInteractiveWindows = (info.flags
& AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
mCaptureFingerprintGestures = (info.flags
@@ -1773,6 +1777,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
return mRequestMultiFingerGestures;
}
+ public boolean isTwoFingerPassthroughEnabled() {
+ return mRequestTwoFingerPassthrough;
+ }
+
@Override
public void setGestureDetectionPassthroughRegion(int displayId, Region region) {
mSystemSupport.setGestureDetectionPassthroughRegion(displayId, region);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 020f2253743d..a04a7c570b5c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -114,6 +114,13 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
*/
static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x00000100;
+ /**
+ * Flag for enabling multi-finger gestures.
+ *
+ * @see #setUserAndEnabledFeatures(int, int)
+ */
+ static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 0x00000200;
+
static final int FEATURES_AFFECTING_MOTION_EVENTS =
FLAG_FEATURE_INJECT_MOTION_EVENTS
| FLAG_FEATURE_AUTOCLICK
@@ -121,7 +128,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
| FLAG_FEATURE_SCREEN_MAGNIFIER
| FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
| FLAG_SERVICE_HANDLES_DOUBLE_TAP
- | FLAG_REQUEST_MULTI_FINGER_GESTURES;
+ | FLAG_REQUEST_MULTI_FINGER_GESTURES
+ | FLAG_REQUEST_2_FINGER_PASSTHROUGH;
private final Context mContext;
@@ -417,6 +425,9 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
if ((mEnabledFeatures & FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0) {
explorer.setMultiFingerGesturesEnabled(true);
}
+ if ((mEnabledFeatures & FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0) {
+ explorer.setTwoFingerPassthroughEnabled(true);
+ }
addFirstEventHandler(displayId, explorer);
mTouchExplorer.put(displayId, explorer);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index fcf270b4ef35..9c10bad1775a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1742,6 +1742,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (userState.isMultiFingerGesturesEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_REQUEST_MULTI_FINGER_GESTURES;
}
+ if (userState.isTwoFingerPassthroughEnabledLocked()) {
+ flags |= AccessibilityInputFilter.FLAG_REQUEST_2_FINGER_PASSTHROUGH;
+ }
}
if (userState.isFilterKeyEventsEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
@@ -2020,6 +2023,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
boolean touchExplorationEnabled = mUiAutomationManager.isTouchExplorationEnabledLocked();
boolean serviceHandlesDoubleTapEnabled = false;
boolean requestMultiFingerGestures = false;
+ boolean requestTwoFingerPassthrough = false;
final int serviceCount = userState.mBoundServices.size();
for (int i = 0; i < serviceCount; i++) {
AccessibilityServiceConnection service = userState.mBoundServices.get(i);
@@ -2027,6 +2031,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
touchExplorationEnabled = true;
serviceHandlesDoubleTapEnabled = service.isServiceHandlesDoubleTapEnabled();
requestMultiFingerGestures = service.isMultiFingerGesturesEnabled();
+ requestTwoFingerPassthrough = service.isTwoFingerPassthroughEnabled();
break;
}
}
@@ -2043,6 +2048,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
userState.setServiceHandlesDoubleTapLocked(serviceHandlesDoubleTapEnabled);
userState.setMultiFingerGesturesLocked(requestMultiFingerGestures);
+ userState.setTwoFingerPassthroughLocked(requestTwoFingerPassthrough);
}
private boolean readAccessibilityShortcutKeySettingLocked(AccessibilityUserState userState) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 0845d019c060..299a776df1d0 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -109,6 +109,7 @@ class AccessibilityUserState {
private boolean mIsTouchExplorationEnabled;
private boolean mServiceHandlesDoubleTap;
private boolean mRequestMultiFingerGestures;
+ private boolean mRequestTwoFingerPassthrough;
private int mUserInteractiveUiTimeout;
private int mUserNonInteractiveUiTimeout;
private int mNonInteractiveUiTimeout = 0;
@@ -160,6 +161,7 @@ class AccessibilityUserState {
mIsTouchExplorationEnabled = false;
mServiceHandlesDoubleTap = false;
mRequestMultiFingerGestures = false;
+ mRequestTwoFingerPassthrough = false;
mIsDisplayMagnificationEnabled = false;
mIsAutoclickEnabled = false;
mUserNonInteractiveUiTimeout = 0;
@@ -446,6 +448,8 @@ class AccessibilityUserState {
.append(String.valueOf(mServiceHandlesDoubleTap));
pw.append(", requestMultiFingerGestures=")
.append(String.valueOf(mRequestMultiFingerGestures));
+ pw.append(", requestTwoFingerPassthrough=")
+ .append(String.valueOf(mRequestTwoFingerPassthrough));
pw.append(", displayMagnificationEnabled=").append(String.valueOf(
mIsDisplayMagnificationEnabled));
pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
@@ -733,6 +737,14 @@ class AccessibilityUserState {
public void setMultiFingerGesturesLocked(boolean enabled) {
mRequestMultiFingerGestures = enabled;
}
+ public boolean isTwoFingerPassthroughEnabledLocked() {
+ return mRequestTwoFingerPassthrough;
+ }
+
+ public void setTwoFingerPassthroughLocked(boolean enabled) {
+ mRequestTwoFingerPassthrough = enabled;
+ }
+
public int getUserInteractiveUiTimeoutLocked() {
return mUserInteractiveUiTimeout;
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
index 070626be9f80..a4fec82bcf56 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
@@ -256,6 +256,7 @@ class EventDispatcher {
return actionMasked;
}
}
+
/**
* Sends down events to the view hierarchy for all pointers which are not already being
* delivered i.e. pointers that are not yet injected.
@@ -285,6 +286,79 @@ class EventDispatcher {
}
/**
+ * Sends down events to the view hierarchy for all pointers which are not already being
+ * delivered with original down location. i.e. pointers that are not yet injected. The down time
+ * is also replaced by the original one.
+ *
+ *
+ * @param prototype The prototype from which to create the injected events.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ void sendDownForAllNotInjectedPointersWithOriginalDown(MotionEvent prototype, int policyFlags) {
+ // Inject the injected pointers.
+ int pointerIdBits = 0;
+ final int pointerCount = prototype.getPointerCount();
+ final MotionEvent event = computeEventWithOriginalDown(prototype);
+ for (int i = 0; i < pointerCount; i++) {
+ final int pointerId = prototype.getPointerId(i);
+ // Do not send event for already delivered pointers.
+ if (!mState.isInjectedPointerDown(pointerId)) {
+ pointerIdBits |= (1 << pointerId);
+ final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
+ sendMotionEvent(
+ event,
+ action,
+ mState.getLastReceivedEvent(),
+ pointerIdBits,
+ policyFlags);
+ }
+ }
+ }
+
+ private MotionEvent computeEventWithOriginalDown(MotionEvent prototype) {
+ final int pointerCount = prototype.getPointerCount();
+ if (pointerCount != mState.getReceivedPointerTracker().getReceivedPointerDownCount()) {
+ Slog.w(LOG_TAG, "The pointer count doesn't match the received count.");
+ return MotionEvent.obtain(prototype);
+ }
+ MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointerCount];
+ MotionEvent.PointerProperties[] properties =
+ new MotionEvent.PointerProperties[pointerCount];
+ for (int i = 0; i < pointerCount; ++i) {
+ final int pointerId = prototype.getPointerId(i);
+ final float x = mState.getReceivedPointerTracker().getReceivedPointerDownX(pointerId);
+ final float y = mState.getReceivedPointerTracker().getReceivedPointerDownY(pointerId);
+ coords[i] = new MotionEvent.PointerCoords();
+ coords[i].x = x;
+ coords[i].y = y;
+ properties[i] = new MotionEvent.PointerProperties();
+ properties[i].id = pointerId;
+ properties[i].toolType = MotionEvent.TOOL_TYPE_FINGER;
+ }
+ MotionEvent event =
+ MotionEvent.obtain(
+ prototype.getDownTime(),
+ // The event time is used for downTime while sending ACTION_DOWN. We adjust
+ // it to avoid the motion velocity is too fast in the beginning after
+ // Delegating.
+ prototype.getDownTime(),
+ prototype.getAction(),
+ pointerCount,
+ properties,
+ coords,
+ prototype.getMetaState(),
+ prototype.getButtonState(),
+ prototype.getXPrecision(),
+ prototype.getYPrecision(),
+ prototype.getDeviceId(),
+ prototype.getEdgeFlags(),
+ prototype.getSource(),
+ prototype.getFlags());
+ return event;
+ }
+
+ /**
+ *
* Sends up events to the view hierarchy for all pointers which are already being delivered i.e.
* pointers that are injected.
*
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index 5c15214284f0..a860db389d1e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -94,8 +94,13 @@ class GestureManifold implements GestureMatcher.StateChangeListener {
private boolean mServiceHandlesDoubleTap = false;
// Whether multi-finger gestures are enabled.
boolean mMultiFingerGesturesEnabled;
+ // Whether the two-finger passthrough is enabled when multi-finger gestures are enabled.
+ private boolean mTwoFingerPassthroughEnabled;
// A list of all the multi-finger gestures, for easy adding and removal.
private final List<GestureMatcher> mMultiFingerGestures = new ArrayList<>();
+ // A list of two-finger swipes, for easy adding and removal when turning on or off two-finger
+ // passthrough.
+ private final List<GestureMatcher> mTwoFingerSwipes = new ArrayList<>();
// Shared state information.
private TouchState mState;
@@ -105,6 +110,7 @@ class GestureManifold implements GestureMatcher.StateChangeListener {
mListener = listener;
mState = state;
mMultiFingerGesturesEnabled = false;
+ mTwoFingerPassthroughEnabled = false;
// Set up gestures.
// Start with double tap.
mGestures.add(new MultiTap(context, 2, GESTURE_DOUBLE_TAP, this));
@@ -161,14 +167,14 @@ class GestureManifold implements GestureMatcher.StateChangeListener {
mMultiFingerGestures.add(
new MultiFingerMultiTap(mContext, 4, 3, GESTURE_4_FINGER_TRIPLE_TAP, this));
// Two-finger swipes.
- mMultiFingerGestures.add(
+ mTwoFingerSwipes.add(
new MultiFingerSwipe(context, 2, DOWN, GESTURE_2_FINGER_SWIPE_DOWN, this));
- mMultiFingerGestures.add(
+ mTwoFingerSwipes.add(
new MultiFingerSwipe(context, 2, LEFT, GESTURE_2_FINGER_SWIPE_LEFT, this));
- mMultiFingerGestures.add(
+ mTwoFingerSwipes.add(
new MultiFingerSwipe(context, 2, RIGHT, GESTURE_2_FINGER_SWIPE_RIGHT, this));
- mMultiFingerGestures.add(
- new MultiFingerSwipe(context, 2, UP, GESTURE_2_FINGER_SWIPE_UP, this));
+ mTwoFingerSwipes.add(new MultiFingerSwipe(context, 2, UP, GESTURE_2_FINGER_SWIPE_UP, this));
+ mMultiFingerGestures.addAll(mTwoFingerSwipes);
// Three-finger swipes.
mMultiFingerGestures.add(
new MultiFingerSwipe(context, 3, DOWN, GESTURE_3_FINGER_SWIPE_DOWN, this));
@@ -360,6 +366,25 @@ class GestureManifold implements GestureMatcher.StateChangeListener {
}
}
+ public boolean isTwoFingerPassthroughEnabled() {
+ return mTwoFingerPassthroughEnabled;
+ }
+
+ public void setTwoFingerPassthroughEnabled(boolean mode) {
+ if (mTwoFingerPassthroughEnabled != mode) {
+ mTwoFingerPassthroughEnabled = mode;
+ if (!mode) {
+ mMultiFingerGestures.addAll(mTwoFingerSwipes);
+ if (mMultiFingerGesturesEnabled) {
+ mGestures.addAll(mTwoFingerSwipes);
+ }
+ } else {
+ mMultiFingerGestures.removeAll(mTwoFingerSwipes);
+ mGestures.removeAll(mTwoFingerSwipes);
+ }
+ }
+ }
+
public void setServiceHandlesDoubleTap(boolean mode) {
mServiceHandlesDoubleTap = mode;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
index 4b89731b75b6..2a305397924f 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
@@ -294,7 +294,7 @@ class MultiFingerSwipe extends GestureMatcher {
+ Float.toString(mGestureDetectionThresholdPixels));
}
if (getState() == STATE_CLEAR) {
- if (moveDelta < mTouchSlop) {
+ if (moveDelta < (mTargetFingerCount * mTouchSlop)) {
// This still counts as a touch not a swipe.
continue;
} else if (mStrokeBuffers[pointerIndex].size() == 0) {
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 6074ac94faf3..9538110189c8 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -24,6 +24,7 @@ import android.accessibilityservice.AccessibilityGestureEvent;
import android.content.Context;
import android.graphics.Region;
import android.os.Handler;
+import android.util.DisplayMetrics;
import android.util.Slog;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -73,12 +74,21 @@ public class TouchExplorer extends BaseEventStreamTransformation
// The timeout after which we are no longer trying to detect a gesture.
private static final int EXIT_GESTURE_DETECTION_TIMEOUT = 2000;
+ // The height of the top and bottom edges for edge-swipes.
+ // For now this is only used to allow three-finger edge-swipes from the bottom.
+ private static final float EDGE_SWIPE_HEIGHT_CM = 0.25f;
+
+ // The calculated edge height for the top and bottom edges.
+ private final float mEdgeSwipeHeightPixels;
// Timeout before trying to decide what the user is trying to do.
private final int mDetermineUserIntentTimeout;
// Slop between the first and second tap to be a double tap.
private final int mDoubleTapSlop;
+ // Slop to move before being considered a move rather than a tap.
+ private final int mTouchSlop;
+
// The current state of the touch explorer.
private TouchState mState;
@@ -151,6 +161,9 @@ public class TouchExplorer extends BaseEventStreamTransformation
mDispatcher = new EventDispatcher(context, mAms, super.getNext(), mState);
mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout();
mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
+ mEdgeSwipeHeightPixels = metrics.ydpi / GestureUtils.CM_PER_INCH * EDGE_SWIPE_HEIGHT_CM;
mHandler = new Handler(context.getMainLooper());
mExitGestureDetectionModeDelayed = new ExitGestureDetectionModeDelayed();
mSendHoverEnterAndMoveDelayed = new SendHoverEnterAndMoveDelayed();
@@ -196,16 +209,10 @@ public class TouchExplorer extends BaseEventStreamTransformation
if (mState.isTouchExploring()) {
// If a touch exploration gesture is in progress send events for its end.
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
- } else if (mState.isDragging()) {
- mDraggingPointerId = INVALID_POINTER_ID;
- // Send exit to all pointers that we have delivered.
- mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
- } else if (mState.isDelegating()) {
- // Send exit to all pointers that we have delivered.
- mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
- } else if (mState.isGestureDetecting()) {
- // No state specific cleanup required.
}
+ mDraggingPointerId = INVALID_POINTER_ID;
+ // Send exit to any pointers that we have delivered as part of delegating or dragging.
+ mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
// Remove all pending callbacks.
mSendHoverEnterAndMoveDelayed.cancel();
mSendHoverExitDelayed.cancel();
@@ -533,6 +540,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
}
}
+
/**
* Handles ACTION_MOVE while in the touch interacting state. This is where transitions to
* delegating and dragging states are handled.
@@ -541,7 +549,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
MotionEvent event, MotionEvent rawEvent, int policyFlags) {
final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
final int pointerIndex = event.findPointerIndex(pointerId);
- final int pointerIdBits = (1 << pointerId);
+ int pointerIdBits = (1 << pointerId);
switch (event.getPointerCount()) {
case 1:
// We have not started sending events since we try to
@@ -552,12 +560,37 @@ public class TouchExplorer extends BaseEventStreamTransformation
}
break;
case 2:
- if (mGestureDetector.isMultiFingerGesturesEnabled()) {
+ if (mGestureDetector.isMultiFingerGesturesEnabled()
+ && !mGestureDetector.isTwoFingerPassthroughEnabled()) {
return;
}
// Make sure we don't have any pending transitions to touch exploration
mSendHoverEnterAndMoveDelayed.cancel();
mSendHoverExitDelayed.cancel();
+ if (mGestureDetector.isMultiFingerGesturesEnabled()
+ && mGestureDetector.isTwoFingerPassthroughEnabled()) {
+ if (pointerIndex < 0) {
+ return;
+ }
+ // Require both fingers to have moved a certain amount before starting a drag.
+ for (int index = 0; index < event.getPointerCount(); ++index) {
+ int id = event.getPointerId(index);
+ if (!mReceivedPointerTracker.isReceivedPointerDown(id)) {
+ // Something is wrong with the event stream.
+ Slog.e(LOG_TAG, "Invalid pointer id: " + id);
+ }
+ final float deltaX =
+ mReceivedPointerTracker.getReceivedPointerDownX(id)
+ - rawEvent.getX(index);
+ final float deltaY =
+ mReceivedPointerTracker.getReceivedPointerDownY(id)
+ - rawEvent.getY(index);
+ final double moveDelta = Math.hypot(deltaX, deltaY);
+ if (moveDelta < (2 * mTouchSlop)) {
+ return;
+ }
+ }
+ }
// More than one pointer so the user is not touch exploring
// and now we have to decide whether to delegate or drag.
// Remove move history before send injected non-move events
@@ -565,12 +598,20 @@ public class TouchExplorer extends BaseEventStreamTransformation
if (isDraggingGesture(event)) {
// Two pointers moving in the same direction within
// a given distance perform a drag.
- mState.startDragging();
- mDraggingPointerId = pointerId;
- adjustEventLocationForDrag(event);
+ computeDraggingPointerIdIfNeeded(event);
+ pointerIdBits = 1 << mDraggingPointerId;
event.setEdgeFlags(mReceivedPointerTracker.getLastReceivedDownEdgeFlags());
- mDispatcher.sendMotionEvent(
- event, MotionEvent.ACTION_DOWN, rawEvent, pointerIdBits, policyFlags);
+ MotionEvent downEvent = computeDownEventForDrag(event);
+ if (downEvent != null) {
+ mDispatcher.sendMotionEvent(downEvent, MotionEvent.ACTION_DOWN, rawEvent,
+ pointerIdBits, policyFlags);
+ mDispatcher.sendMotionEvent(event, MotionEvent.ACTION_MOVE, rawEvent,
+ pointerIdBits, policyFlags);
+ } else {
+ mDispatcher.sendMotionEvent(event, MotionEvent.ACTION_DOWN, rawEvent,
+ pointerIdBits, policyFlags);
+ }
+ mState.startDragging();
} else {
// Two pointers moving arbitrary are delegated to the view hierarchy.
mState.startDelegating();
@@ -579,12 +620,31 @@ public class TouchExplorer extends BaseEventStreamTransformation
break;
default:
if (mGestureDetector.isMultiFingerGesturesEnabled()) {
- return;
+ if (mGestureDetector.isTwoFingerPassthroughEnabled()) {
+ if (event.getPointerCount() == 3) {
+ // If three fingers went down on the bottom edge of the screen, delegate
+ // immediately.
+ if (allPointersDownOnBottomEdge(event)) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Three-finger edge swipe detected.");
+ }
+ mState.startDelegating();
+ if (mState.isTouchExploring()) {
+ mDispatcher.sendDownForAllNotInjectedPointers(event,
+ policyFlags);
+ } else {
+ mDispatcher.sendDownForAllNotInjectedPointersWithOriginalDown(
+ event, policyFlags);
+ }
+ }
+ }
+ }
+ } else {
+ // More than two pointers are delegated to the view hierarchy.
+ mState.startDelegating();
+ event = MotionEvent.obtainNoHistory(event);
+ mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
}
- // More than two pointers are delegated to the view hierarchy.
- mState.startDelegating();
- event = MotionEvent.obtainNoHistory(event);
- mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
break;
}
}
@@ -626,7 +686,8 @@ public class TouchExplorer extends BaseEventStreamTransformation
event, MotionEvent.ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
break;
case 2:
- if (mGestureDetector.isMultiFingerGesturesEnabled()) {
+ if (mGestureDetector.isMultiFingerGesturesEnabled()
+ && !mGestureDetector.isTwoFingerPassthroughEnabled()) {
return;
}
if (mSendHoverEnterAndMoveDelayed.isPending()) {
@@ -681,7 +742,8 @@ public class TouchExplorer extends BaseEventStreamTransformation
*/
private void handleMotionEventStateDragging(
MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (mGestureDetector.isMultiFingerGesturesEnabled()) {
+ if (mGestureDetector.isMultiFingerGesturesEnabled()
+ && !mGestureDetector.isTwoFingerPassthroughEnabled()) {
// Multi-finger gestures conflict with this functionality.
return;
}
@@ -723,7 +785,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
case 2: {
if (isDraggingGesture(event)) {
// If still dragging send a drag event.
- adjustEventLocationForDrag(event);
+ computeDraggingPointerIdIfNeeded(event);
mDispatcher.sendMotionEvent(
event,
MotionEvent.ACTION_MOVE,
@@ -734,6 +796,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
// The two pointers are moving either in different directions or
// no close enough => delegate the gesture to the view hierarchy.
mState.startDelegating();
+ mDraggingPointerId = INVALID_POINTER_ID;
// Remove move history before send injected non-move events
event = MotionEvent.obtainNoHistory(event);
// Send an event to the end of the drag gesture.
@@ -749,6 +812,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
} break;
default: {
mState.startDelegating();
+ mDraggingPointerId = INVALID_POINTER_ID;
event = MotionEvent.obtainNoHistory(event);
// Send an event to the end of the drag gesture.
mDispatcher.sendMotionEvent(
@@ -772,17 +836,15 @@ public class TouchExplorer extends BaseEventStreamTransformation
}
} break;
case MotionEvent.ACTION_UP: {
- mAms.onTouchInteractionEnd();
- // Announce the end of a new touch interaction.
- mDispatcher.sendAccessibilityEvent(
- AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
final int pointerId = event.getPointerId(event.getActionIndex());
if (pointerId == mDraggingPointerId) {
- mDraggingPointerId = INVALID_POINTER_ID;
- // Send an event to the end of the drag gesture.
mDispatcher.sendMotionEvent(
event, MotionEvent.ACTION_UP, rawEvent, pointerIdBits, policyFlags);
}
+ mAms.onTouchInteractionEnd();
+ // Announce the end of a new touch interaction.
+ mDispatcher.sendAccessibilityEvent(
+ AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
} break;
}
}
@@ -901,21 +963,104 @@ public class TouchExplorer extends BaseEventStreamTransformation
}
/**
- * Adjust the location of an injected event when performing a drag The new location will be in
- * between the two fingers touching the screen.
+ * Computes {@link #mDraggingPointerId} if it is invalid. The pointer will be the finger
+ * closet to an edge of the screen.
*/
- private void adjustEventLocationForDrag(MotionEvent event) {
-
+ private void computeDraggingPointerIdIfNeeded(MotionEvent event) {
+ if (mDraggingPointerId != INVALID_POINTER_ID) {
+ // If we have a valid pointer ID, we should be good
+ final int pointerIndex = event.findPointerIndex(mDraggingPointerId);
+ if (event.findPointerIndex(pointerIndex) >= 0) {
+ return;
+ }
+ }
+ // Use the pointer that is closest to its closest edge.
final float firstPtrX = event.getX(0);
final float firstPtrY = event.getY(0);
+ final int firstPtrId = event.getPointerId(0);
final float secondPtrX = event.getX(1);
final float secondPtrY = event.getY(1);
- final int pointerIndex = event.findPointerIndex(mDraggingPointerId);
- final float deltaX =
- (pointerIndex == 0) ? (secondPtrX - firstPtrX) : (firstPtrX - secondPtrX);
- final float deltaY =
- (pointerIndex == 0) ? (secondPtrY - firstPtrY) : (firstPtrY - secondPtrY);
- event.offsetLocation(deltaX / 2, deltaY / 2);
+ final int secondPtrId = event.getPointerId(1);
+ mDraggingPointerId = (getDistanceToClosestEdge(firstPtrX, firstPtrY)
+ < getDistanceToClosestEdge(secondPtrX, secondPtrY))
+ ? firstPtrId : secondPtrId;
+ }
+
+ private float getDistanceToClosestEdge(float x, float y) {
+ final long width = mContext.getResources().getDisplayMetrics().widthPixels;
+ final long height = mContext.getResources().getDisplayMetrics().heightPixels;
+ float distance = Float.MAX_VALUE;
+ if (x < (width - x)) {
+ distance = x;
+ } else {
+ distance = width - x;
+ }
+ if (distance > y) {
+ distance = y;
+ }
+ if (distance > (height - y)) {
+ distance = (height - y);
+ }
+ return distance;
+ }
+
+ /**
+ * Creates a down event using the down coordinates of the dragging pointer and other information
+ * from the supplied event. The supplied event's down time is adjusted to reflect the time when
+ * the dragging pointer initially went down.
+ */
+ private MotionEvent computeDownEventForDrag(MotionEvent event) {
+ // Creating a down event only makes sense if we haven't started touch exploring yet.
+ if (mState.isTouchExploring()
+ || mDraggingPointerId == INVALID_POINTER_ID
+ || event == null) {
+ return null;
+ }
+ final float x = mReceivedPointerTracker.getReceivedPointerDownX(mDraggingPointerId);
+ final float y = mReceivedPointerTracker.getReceivedPointerDownY(mDraggingPointerId);
+ final long time = mReceivedPointerTracker.getReceivedPointerDownTime(mDraggingPointerId);
+ MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[1];
+ coords[0] = new MotionEvent.PointerCoords();
+ coords[0].x = x;
+ coords[0].y = y;
+ MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1];
+ properties[0] = new MotionEvent.PointerProperties();
+ properties[0].id = mDraggingPointerId;
+ properties[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
+ MotionEvent downEvent =
+ MotionEvent.obtain(
+ time,
+ time,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ properties,
+ coords,
+ event.getMetaState(),
+ event.getButtonState(),
+ event.getXPrecision(),
+ event.getYPrecision(),
+ event.getDeviceId(),
+ event.getEdgeFlags(),
+ event.getSource(),
+ event.getFlags());
+ event.setDownTime(time);
+ return downEvent;
+ }
+
+ private boolean allPointersDownOnBottomEdge(MotionEvent event) {
+ final long screenHeight =
+ mContext.getResources().getDisplayMetrics().heightPixels;
+ for (int i = 0; i < event.getPointerCount(); ++i) {
+ final int pointerId = event.getPointerId(i);
+ final float pointerDownY = mReceivedPointerTracker.getReceivedPointerDownY(pointerId);
+ if (pointerDownY < (screenHeight - mEdgeSwipeHeightPixels)) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "The pointer is not on the bottom edge" + pointerDownY);
+ }
+ return false;
+ }
+ }
+ return true;
}
public TouchState getState() {
@@ -944,6 +1089,13 @@ public class TouchExplorer extends BaseEventStreamTransformation
mGestureDetector.setMultiFingerGesturesEnabled(enabled);
}
+ /**
+ * This function turns on and off two-finger passthrough gestures such as drag and pinch when
+ * multi-finger gestures are enabled.
+ */
+ public void setTwoFingerPassthroughEnabled(boolean enabled) {
+ mGestureDetector.setTwoFingerPassthroughEnabled(enabled);
+ }
public void setGestureDetectionPassthroughRegion(Region region) {
mGestureDetectionPassthroughRegion = region;
}