Merge ab/7061308 into stage.

Bug: 180401296
Merged-In: I4bf82035631ccff6d5a6144d6d9b1d203b076851
Change-Id: I1b5f3a672a55eaabba0f5389bab110b395553559
This commit is contained in:
Xin Li
2021-02-21 09:39:53 -08:00
1252 changed files with 26660 additions and 52648 deletions

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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.
*

View File

@@ -94,17 +94,23 @@ 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;
GestureManifold(Context context, Listener listener, TouchState state) {
GestureManifold(Context context, Listener listener, TouchState state, Handler handler) {
mContext = context;
mHandler = new Handler(context.getMainLooper());
mHandler = handler;
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;
}

View File

@@ -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) {

View File

@@ -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();
@@ -162,7 +175,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
AccessibilityEvent.TYPE_TOUCH_INTERACTION_END,
mDetermineUserIntentTimeout);
if (detector == null) {
mGestureDetector = new GestureManifold(context, this, mState);
mGestureDetector = new GestureManifold(context, this, mState, mHandler);
} else {
mGestureDetector = detector;
}
@@ -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();
@@ -214,6 +221,8 @@ public class TouchExplorer extends BaseEventStreamTransformation
mSendTouchInteractionEndDelayed.cancel();
// Clear the gesture detector
mGestureDetector.clear();
// Clear the offset data by long pressing.
mDispatcher.clear();
// Go to initial state.
mState.clear();
mAms.onTouchInteractionEnd();
@@ -344,7 +353,6 @@ public class TouchExplorer extends BaseEventStreamTransformation
public boolean onGestureStarted() {
// We have to perform gesture detection, so
// clear the current state and try to detect.
mState.startGestureDetecting();
mSendHoverEnterAndMoveDelayed.cancel();
mSendHoverExitDelayed.cancel();
mExitGestureDetectionModeDelayed.post();
@@ -533,6 +541,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 +550,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 +561,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 +599,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 +621,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 +687,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 +743,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 +786,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 +797,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 +813,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 +837,15 @@ public class TouchExplorer extends BaseEventStreamTransformation
}
} break;
case MotionEvent.ACTION_UP: {
final int pointerId = event.getPointerId(event.getActionIndex());
if (pointerId == mDraggingPointerId) {
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);
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);
}
} break;
}
}
@@ -901,21 +964,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 +1090,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;
}
@@ -953,7 +1106,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
}
private boolean shouldPerformGestureDetection(MotionEvent event) {
if (mState.isDelegating()) {
if (mState.isDelegating() || mState.isDragging()) {
return false;
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
@@ -1046,6 +1199,15 @@ public class TouchExplorer extends BaseEventStreamTransformation
}
public void run() {
if (mReceivedPointerTracker.getReceivedPointerDownCount() > 1) {
// Multi-finger touch exploration doesn't make sense.
Slog.e(
LOG_TAG,
"Attempted touch exploration with "
+ mReceivedPointerTracker.getReceivedPointerDownCount()
+ " pointers down.");
return;
}
// Send an accessibility event to announce the touch exploration start.
mDispatcher.sendAccessibilityEvent(
AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);

View File

@@ -208,7 +208,9 @@ public class TouchState {
startGestureDetecting();
break;
case AccessibilityEvent.TYPE_GESTURE_DETECTION_END:
startTouchInteracting();
// Clear to make sure that we don't accidentally execute passthrough, and that we
// are ready for the next interaction.
clear();
break;
default:
break;