Merge ab/7061308 into stage.
Bug: 180401296 Merged-In: I4bf82035631ccff6d5a6144d6d9b1d203b076851 Change-Id: I1b5f3a672a55eaabba0f5389bab110b395553559
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user