Merge "[DO NOT MERGE] Bring back touch events for double tap and double tap and hold." into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
e16a6a02a7
@@ -486,6 +486,21 @@ public final class MotionEvent extends InputEvent implements Parcelable {
|
|||||||
*/
|
*/
|
||||||
public static final int FLAG_TAINTED = 0x80000000;
|
public static final int FLAG_TAINTED = 0x80000000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private flag indicating that this event was synthesized by the system and should be delivered
|
||||||
|
* to the accessibility focused view first. When being dispatched such an event is not handled
|
||||||
|
* by predecessors of the accessibility focused view and after the event reaches that view the
|
||||||
|
* flag is cleared and normal event dispatch is performed. This ensures that the platform can
|
||||||
|
* click on any view that has accessibility focus which is semantically equivalent to asking the
|
||||||
|
* view to perform a click accessibility action but more generic as views not implementing click
|
||||||
|
* action correctly can still be activated.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
* @see #isTargetAccessibilityFocus()
|
||||||
|
* @see #setTargetAccessibilityFocus(boolean)
|
||||||
|
*/
|
||||||
|
public static final int FLAG_TARGET_ACCESSIBILITY_FOCUS = 0x40000000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag indicating the motion event intersected the top edge of the screen.
|
* Flag indicating the motion event intersected the top edge of the screen.
|
||||||
*/
|
*/
|
||||||
@@ -2139,6 +2154,20 @@ public final class MotionEvent extends InputEvent implements Parcelable {
|
|||||||
nativeSetFlags(mNativePtr, tainted ? flags | FLAG_TAINTED : flags & ~FLAG_TAINTED);
|
nativeSetFlags(mNativePtr, tainted ? flags | FLAG_TAINTED : flags & ~FLAG_TAINTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public boolean isTargetAccessibilityFocus() {
|
||||||
|
final int flags = getFlags();
|
||||||
|
return (flags & FLAG_TARGET_ACCESSIBILITY_FOCUS) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public void setTargetAccessibilityFocus(boolean targetsFocus) {
|
||||||
|
final int flags = getFlags();
|
||||||
|
nativeSetFlags(mNativePtr, targetsFocus
|
||||||
|
? flags | FLAG_TARGET_ACCESSIBILITY_FOCUS
|
||||||
|
: flags & ~FLAG_TARGET_ACCESSIBILITY_FOCUS);
|
||||||
|
}
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public final boolean isHoverExitPending() {
|
public final boolean isHoverExitPending() {
|
||||||
final int flags = getFlags();
|
final int flags = getFlags();
|
||||||
|
|||||||
@@ -14274,6 +14274,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
|||||||
*/
|
*/
|
||||||
public boolean dispatchTouchEvent(MotionEvent event) {
|
public boolean dispatchTouchEvent(MotionEvent event) {
|
||||||
// If the event should be handled by accessibility focus first.
|
// If the event should be handled by accessibility focus first.
|
||||||
|
if (event.isTargetAccessibilityFocus()) {
|
||||||
|
// We don't have focus or no virtual descendant has it, do not handle the event.
|
||||||
|
if (!isAccessibilityFocusedViewOrHost()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// We have focus and got the event, then use normal event dispatch.
|
||||||
|
event.setTargetAccessibilityFocus(false);
|
||||||
|
}
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
|
|
||||||
if (mInputEventConsistencyVerifier != null) {
|
if (mInputEventConsistencyVerifier != null) {
|
||||||
|
|||||||
@@ -2048,8 +2048,26 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
|||||||
for (int i = childrenCount - 1; i >= 0; i--) {
|
for (int i = childrenCount - 1; i >= 0; i--) {
|
||||||
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
|
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
|
||||||
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
|
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
|
||||||
|
View childWithAccessibilityFocus =
|
||||||
|
event.isTargetAccessibilityFocus()
|
||||||
|
? findChildWithAccessibilityFocus()
|
||||||
|
: null;
|
||||||
|
|
||||||
if (!child.canReceivePointerEvents()
|
if (!child.canReceivePointerEvents()
|
||||||
|| !isTransformedTouchPointInView(x, y, child, null)) {
|
|| !isTransformedTouchPointInView(x, y, child, null)) {
|
||||||
|
|
||||||
|
// If there is a view that has accessibility focus we want it
|
||||||
|
// to get the event first and if not handled we will perform a
|
||||||
|
// normal dispatch. We may do a double iteration but this is
|
||||||
|
// safer given the timeframe.
|
||||||
|
if (childWithAccessibilityFocus != null) {
|
||||||
|
if (childWithAccessibilityFocus != child) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
childWithAccessibilityFocus = null;
|
||||||
|
i = childrenCount - 1;
|
||||||
|
}
|
||||||
|
event.setTargetAccessibilityFocus(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final PointerIcon pointerIcon =
|
final PointerIcon pointerIcon =
|
||||||
@@ -2617,6 +2635,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
|||||||
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
|
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the event targets the accessibility focused view and this is it, start
|
||||||
|
// normal event dispatch. Maybe a descendant is what will handle the click.
|
||||||
|
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
|
||||||
|
ev.setTargetAccessibilityFocus(false);
|
||||||
|
}
|
||||||
|
|
||||||
boolean handled = false;
|
boolean handled = false;
|
||||||
if (onFilterTouchEventForSecurity(ev)) {
|
if (onFilterTouchEventForSecurity(ev)) {
|
||||||
final int action = ev.getAction();
|
final int action = ev.getAction();
|
||||||
@@ -2647,6 +2671,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
|||||||
// so this view group continues to intercept touches.
|
// so this view group continues to intercept touches.
|
||||||
intercepted = true;
|
intercepted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If intercepted, start normal event dispatch. Also if there is already
|
||||||
|
// a view that is handling the gesture, do normal event dispatch.
|
||||||
|
if (intercepted || mFirstTouchTarget != null) {
|
||||||
|
ev.setTargetAccessibilityFocus(false);
|
||||||
|
}
|
||||||
|
|
||||||
// Check for cancelation.
|
// Check for cancelation.
|
||||||
final boolean canceled = resetCancelNextUpFlag(this)
|
final boolean canceled = resetCancelNextUpFlag(this)
|
||||||
|| actionMasked == MotionEvent.ACTION_CANCEL;
|
|| actionMasked == MotionEvent.ACTION_CANCEL;
|
||||||
@@ -2658,6 +2689,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
|||||||
TouchTarget newTouchTarget = null;
|
TouchTarget newTouchTarget = null;
|
||||||
boolean alreadyDispatchedToNewTouchTarget = false;
|
boolean alreadyDispatchedToNewTouchTarget = false;
|
||||||
if (!canceled && !intercepted) {
|
if (!canceled && !intercepted) {
|
||||||
|
// If the event is targeting accessibility focus we give it to the
|
||||||
|
// view that has accessibility focus and if it does not handle it
|
||||||
|
// we clear the flag and dispatch the event to all children as usual.
|
||||||
|
// We are looking up the accessibility focused host to avoid keeping
|
||||||
|
// state since these events are very rare.
|
||||||
|
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
|
||||||
|
? findChildWithAccessibilityFocus() : null;
|
||||||
|
|
||||||
if (actionMasked == MotionEvent.ACTION_DOWN
|
if (actionMasked == MotionEvent.ACTION_DOWN
|
||||||
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|
||||||
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
|
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
|
||||||
@@ -2720,6 +2759,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
|||||||
alreadyDispatchedToNewTouchTarget = true;
|
alreadyDispatchedToNewTouchTarget = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The accessibility focus didn't handle the event, so clear
|
||||||
|
// the flag and do a normal dispatch to all children.
|
||||||
|
ev.setTargetAccessibilityFocus(false);
|
||||||
}
|
}
|
||||||
if (preorderedList != null) preorderedList.clear();
|
if (preorderedList != null) preorderedList.clear();
|
||||||
}
|
}
|
||||||
@@ -2803,6 +2846,34 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
|||||||
return buildOrderedChildList();
|
return buildOrderedChildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the child which has accessibility focus.
|
||||||
|
*
|
||||||
|
* @return The child that has focus.
|
||||||
|
*/
|
||||||
|
private View findChildWithAccessibilityFocus() {
|
||||||
|
ViewRootImpl viewRoot = getViewRootImpl();
|
||||||
|
if (viewRoot == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
View current = viewRoot.getAccessibilityFocusedHost();
|
||||||
|
if (current == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewParent parent = current.getParent();
|
||||||
|
while (parent instanceof View) {
|
||||||
|
if (parent == this) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
current = (View) parent;
|
||||||
|
parent = current.getParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets all touch state in preparation for a new cycle.
|
* Resets all touch state in preparation for a new cycle.
|
||||||
*/
|
*/
|
||||||
@@ -3257,9 +3328,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("descendant focusability must be "
|
throw new IllegalStateException(
|
||||||
+ "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
|
"descendant focusability must be one of FOCUS_BEFORE_DESCENDANTS,"
|
||||||
+ "but is " + descendantFocusability);
|
+ " FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS but is "
|
||||||
|
+ descendantFocusability);
|
||||||
}
|
}
|
||||||
if (result && !isLayoutValid() && ((mPrivateFlags & PFLAG_WANTS_FOCUS) == 0)) {
|
if (result && !isLayoutValid() && ((mPrivateFlags & PFLAG_WANTS_FOCUS) == 0)) {
|
||||||
mPrivateFlags |= PFLAG_WANTS_FOCUS;
|
mPrivateFlags |= PFLAG_WANTS_FOCUS;
|
||||||
@@ -4925,7 +4997,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
|||||||
if (params == null) {
|
if (params == null) {
|
||||||
params = generateDefaultLayoutParams();
|
params = generateDefaultLayoutParams();
|
||||||
if (params == null) {
|
if (params == null) {
|
||||||
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
|
throw new IllegalArgumentException(
|
||||||
|
"generateDefaultLayoutParams() cannot return null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addView(child, index, params);
|
addView(child, index, params);
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ import android.content.pm.PackageManagerInternal;
|
|||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
import android.content.pm.ServiceInfo;
|
import android.content.pm.ServiceInfo;
|
||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.graphics.Region;
|
import android.graphics.Region;
|
||||||
import android.hardware.display.DisplayManager;
|
import android.hardware.display.DisplayManager;
|
||||||
import android.hardware.fingerprint.IFingerprintService;
|
import android.hardware.fingerprint.IFingerprintService;
|
||||||
@@ -190,6 +192,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
|||||||
private final SimpleStringSplitter mStringColonSplitter =
|
private final SimpleStringSplitter mStringColonSplitter =
|
||||||
new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
|
new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
|
||||||
|
|
||||||
|
private final Rect mTempRect = new Rect();
|
||||||
|
private final Rect mTempRect1 = new Rect();
|
||||||
|
|
||||||
private final PackageManager mPackageManager;
|
private final PackageManager mPackageManager;
|
||||||
|
|
||||||
private final PowerManager mPowerManager;
|
private final PowerManager mPowerManager;
|
||||||
@@ -246,6 +251,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
|||||||
//TODO: Remove this hack
|
//TODO: Remove this hack
|
||||||
private boolean mInitialized;
|
private boolean mInitialized;
|
||||||
|
|
||||||
|
private Point mTempPoint;
|
||||||
private boolean mIsAccessibilityButtonShown;
|
private boolean mIsAccessibilityButtonShown;
|
||||||
|
|
||||||
private AccessibilityUserState getCurrentUserStateLocked() {
|
private AccessibilityUserState getCurrentUserStateLocked() {
|
||||||
@@ -1067,6 +1073,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
|||||||
return motionEventInjector;
|
return motionEventInjector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a point within the accessibility focused node where we can send down
|
||||||
|
* and up events to perform a click.
|
||||||
|
*
|
||||||
|
* @param outPoint The click point to populate.
|
||||||
|
* @return Whether accessibility a click point was found and set.
|
||||||
|
*/
|
||||||
|
// TODO: (multi-display) Make sure this works for multiple displays.
|
||||||
|
public boolean getAccessibilityFocusClickPointInScreen(Point outPoint) {
|
||||||
|
return getInteractionBridge().getAccessibilityFocusClickPointInScreenNotLocked(outPoint);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform an accessibility action on the view that currently has accessibility focus.
|
* Perform an accessibility action on the view that currently has accessibility focus.
|
||||||
* Has no effect if no item has accessibility focus, if the item with accessibility
|
* Has no effect if no item has accessibility focus, if the item with accessibility
|
||||||
@@ -1081,6 +1099,32 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
|||||||
return getInteractionBridge().performActionOnAccessibilityFocusedItemNotLocked(action);
|
return getInteractionBridge().performActionOnAccessibilityFocusedItemNotLocked(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if accessibility focus is confined to the active window.
|
||||||
|
*/
|
||||||
|
public boolean accessibilityFocusOnlyInActiveWindow() {
|
||||||
|
synchronized (mLock) {
|
||||||
|
return mA11yWindowManager.isTrackingWindowsLocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the bounds of a window.
|
||||||
|
*
|
||||||
|
* @param outBounds The output to which to write the bounds.
|
||||||
|
*/
|
||||||
|
boolean getWindowBounds(int windowId, Rect outBounds) {
|
||||||
|
IBinder token;
|
||||||
|
synchronized (mLock) {
|
||||||
|
token = getWindowToken(windowId, mCurrentUserId);
|
||||||
|
}
|
||||||
|
mWindowManagerService.getWindowFrame(token, outBounds);
|
||||||
|
if (!outBounds.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public int getActiveWindowId() {
|
public int getActiveWindowId() {
|
||||||
return mA11yWindowManager.getActiveWindowId(mCurrentUserId);
|
return mA11yWindowManager.getActiveWindowId(mCurrentUserId);
|
||||||
}
|
}
|
||||||
@@ -1824,9 +1868,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
|||||||
for (int i = 0; !observingWindows && (i < boundServiceCount); i++) {
|
for (int i = 0; !observingWindows && (i < boundServiceCount); i++) {
|
||||||
AccessibilityServiceConnection boundService = boundServices.get(i);
|
AccessibilityServiceConnection boundService = boundServices.get(i);
|
||||||
if (boundService.canRetrieveInteractiveWindowsLocked()) {
|
if (boundService.canRetrieveInteractiveWindowsLocked()) {
|
||||||
|
userState.setAccessibilityFocusOnlyInActiveWindow(false);
|
||||||
observingWindows = true;
|
observingWindows = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
userState.setAccessibilityFocusOnlyInActiveWindow(true);
|
||||||
|
|
||||||
// Gets all valid displays and start tracking windows of each display if there is at least
|
// Gets all valid displays and start tracking windows of each display if there is at least
|
||||||
// one bound service that can retrieve window content.
|
// one bound service that can retrieve window content.
|
||||||
@@ -2929,6 +2975,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
|||||||
mDefaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
|
mDefaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a point within the accessibility focused node where we can send down and up events
|
||||||
|
* to perform a click.
|
||||||
|
*
|
||||||
|
* @param outPoint The click point to populate.
|
||||||
|
* @return Whether accessibility a click point was found and set.
|
||||||
|
*/
|
||||||
|
// TODO: (multi-display) Make sure this works for multiple displays.
|
||||||
|
boolean getAccessibilityFocusClickPointInScreen(Point outPoint) {
|
||||||
|
return getInteractionBridge()
|
||||||
|
.getAccessibilityFocusClickPointInScreenNotLocked(outPoint);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform an accessibility action on the view that currently has accessibility focus.
|
* Perform an accessibility action on the view that currently has accessibility focus.
|
||||||
* Has no effect if no item has accessibility focus, if the item with accessibility
|
* Has no effect if no item has accessibility focus, if the item with accessibility
|
||||||
@@ -2947,6 +3006,43 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
|||||||
return focus.performAction(action.getId());
|
return focus.performAction(action.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getAccessibilityFocusClickPointInScreenNotLocked(Point outPoint) {
|
||||||
|
AccessibilityNodeInfo focus = getAccessibilityFocusNotLocked();
|
||||||
|
if (focus == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (mLock) {
|
||||||
|
Rect boundsInScreen = mTempRect;
|
||||||
|
focus.getBoundsInScreen(boundsInScreen);
|
||||||
|
|
||||||
|
// Apply magnification if needed.
|
||||||
|
MagnificationSpec spec = getCompatibleMagnificationSpecLocked(focus.getWindowId());
|
||||||
|
if (spec != null && !spec.isNop()) {
|
||||||
|
boundsInScreen.offset((int) -spec.offsetX, (int) -spec.offsetY);
|
||||||
|
boundsInScreen.scale(1 / spec.scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clip to the window bounds.
|
||||||
|
Rect windowBounds = mTempRect1;
|
||||||
|
getWindowBounds(focus.getWindowId(), windowBounds);
|
||||||
|
if (!boundsInScreen.intersect(windowBounds)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clip to the screen bounds.
|
||||||
|
Point screenSize = mTempPoint;
|
||||||
|
mDefaultDisplay.getRealSize(screenSize);
|
||||||
|
if (!boundsInScreen.intersect(0, 0, screenSize.x, screenSize.y)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outPoint.set(boundsInScreen.centerX(), boundsInScreen.centerY());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private AccessibilityNodeInfo getAccessibilityFocusNotLocked() {
|
private AccessibilityNodeInfo getAccessibilityFocusNotLocked() {
|
||||||
final int focusedWindowId;
|
final int focusedWindowId;
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ class AccessibilityUserState {
|
|||||||
private boolean mIsDisplayMagnificationEnabled;
|
private boolean mIsDisplayMagnificationEnabled;
|
||||||
private boolean mIsFilterKeyEventsEnabled;
|
private boolean mIsFilterKeyEventsEnabled;
|
||||||
private boolean mIsPerformGesturesEnabled;
|
private boolean mIsPerformGesturesEnabled;
|
||||||
|
private boolean mAccessibilityFocusOnlyInActiveWindow;
|
||||||
private boolean mIsTextHighContrastEnabled;
|
private boolean mIsTextHighContrastEnabled;
|
||||||
private boolean mIsTouchExplorationEnabled;
|
private boolean mIsTouchExplorationEnabled;
|
||||||
private boolean mServiceHandlesDoubleTap;
|
private boolean mServiceHandlesDoubleTap;
|
||||||
@@ -685,6 +686,13 @@ class AccessibilityUserState {
|
|||||||
mIsPerformGesturesEnabled = enabled;
|
mIsPerformGesturesEnabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAccessibilityFocusOnlyInActiveWindow() {
|
||||||
|
return mAccessibilityFocusOnlyInActiveWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccessibilityFocusOnlyInActiveWindow(boolean enabled) {
|
||||||
|
mAccessibilityFocusOnlyInActiveWindow = enabled;
|
||||||
|
}
|
||||||
public ComponentName getServiceChangingSoftKeyboardModeLocked() {
|
public ComponentName getServiceChangingSoftKeyboardModeLocked() {
|
||||||
return mServiceChangingSoftKeyboardMode;
|
return mServiceChangingSoftKeyboardMode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,11 @@ import static com.android.server.accessibility.gestures.TouchState.ALL_POINTER_I
|
|||||||
import static com.android.server.accessibility.gestures.TouchState.MAX_POINTER_COUNT;
|
import static com.android.server.accessibility.gestures.TouchState.MAX_POINTER_COUNT;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.graphics.Point;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
import android.view.MotionEvent.PointerCoords;
|
||||||
|
import android.view.MotionEvent.PointerProperties;
|
||||||
import android.view.accessibility.AccessibilityEvent;
|
import android.view.accessibility.AccessibilityEvent;
|
||||||
import android.view.accessibility.AccessibilityManager;
|
import android.view.accessibility.AccessibilityManager;
|
||||||
|
|
||||||
@@ -37,19 +40,27 @@ import com.android.server.policy.WindowManagerPolicy;
|
|||||||
*/
|
*/
|
||||||
class EventDispatcher {
|
class EventDispatcher {
|
||||||
private static final String LOG_TAG = "EventDispatcher";
|
private static final String LOG_TAG = "EventDispatcher";
|
||||||
|
private static final int CLICK_LOCATION_NONE = 0;
|
||||||
|
private static final int CLICK_LOCATION_ACCESSIBILITY_FOCUS = 1;
|
||||||
|
private static final int CLICK_LOCATION_LAST_TOUCH_EXPLORED = 2;
|
||||||
|
|
||||||
private final AccessibilityManagerService mAms;
|
private final AccessibilityManagerService mAms;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
// The receiver of motion events.
|
// The receiver of motion events.
|
||||||
private EventStreamTransformation mReceiver;
|
private EventStreamTransformation mReceiver;
|
||||||
// Keep track of which pointers sent to the system are down.
|
|
||||||
private int mInjectedPointersDown;
|
|
||||||
|
|
||||||
// The time of the last injected down.
|
// The long pressing pointer id if coordinate remapping is needed for double tap and hold
|
||||||
private long mLastInjectedDownEventTime;
|
private int mLongPressingPointerId = -1;
|
||||||
|
|
||||||
|
// The long pressing pointer X if coordinate remapping is needed for double tap and hold.
|
||||||
|
private int mLongPressingPointerDeltaX;
|
||||||
|
|
||||||
|
// The long pressing pointer Y if coordinate remapping is needed for double tap and hold.
|
||||||
|
private int mLongPressingPointerDeltaY;
|
||||||
|
|
||||||
|
// Temporary point to avoid instantiation.
|
||||||
|
private final Point mTempPoint = new Point();
|
||||||
|
|
||||||
// The last injected hover event.
|
|
||||||
private MotionEvent mLastInjectedHoverEvent;
|
|
||||||
private TouchState mState;
|
private TouchState mState;
|
||||||
|
|
||||||
EventDispatcher(
|
EventDispatcher(
|
||||||
@@ -98,8 +109,18 @@ class EventDispatcher {
|
|||||||
if (action == MotionEvent.ACTION_DOWN) {
|
if (action == MotionEvent.ACTION_DOWN) {
|
||||||
event.setDownTime(event.getEventTime());
|
event.setDownTime(event.getEventTime());
|
||||||
} else {
|
} else {
|
||||||
event.setDownTime(getLastInjectedDownEventTime());
|
event.setDownTime(mState.getLastInjectedDownEventTime());
|
||||||
}
|
}
|
||||||
|
// If the user is long pressing but the long pressing pointer
|
||||||
|
// was not exactly over the accessibility focused item we need
|
||||||
|
// to remap the location of that pointer so the user does not
|
||||||
|
// have to explicitly touch explore something to be able to
|
||||||
|
// long press it, or even worse to avoid the user long pressing
|
||||||
|
// on the wrong item since click and long press behave differently.
|
||||||
|
if (mLongPressingPointerId >= 0) {
|
||||||
|
event = offsetEvent(event, -mLongPressingPointerDeltaX, -mLongPressingPointerDeltaY);
|
||||||
|
}
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Slog.d(
|
Slog.d(
|
||||||
LOG_TAG,
|
LOG_TAG,
|
||||||
@@ -116,7 +137,7 @@ class EventDispatcher {
|
|||||||
} else {
|
} else {
|
||||||
Slog.e(LOG_TAG, "Error sending event: no receiver specified.");
|
Slog.e(LOG_TAG, "Error sending event: no receiver specified.");
|
||||||
}
|
}
|
||||||
updateState(event);
|
mState.onInjectedMotionEvent(event);
|
||||||
|
|
||||||
if (event != prototype) {
|
if (event != prototype) {
|
||||||
event.recycle();
|
event.recycle();
|
||||||
@@ -145,87 +166,15 @@ class EventDispatcher {
|
|||||||
mState.onInjectedAccessibilityEvent(type);
|
mState.onInjectedAccessibilityEvent(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes an injected {@link MotionEvent} event.
|
|
||||||
*
|
|
||||||
* @param event The event to process.
|
|
||||||
*/
|
|
||||||
void updateState(MotionEvent event) {
|
|
||||||
final int action = event.getActionMasked();
|
|
||||||
final int pointerId = event.getPointerId(event.getActionIndex());
|
|
||||||
final int pointerFlag = (1 << pointerId);
|
|
||||||
switch (action) {
|
|
||||||
case MotionEvent.ACTION_DOWN:
|
|
||||||
case MotionEvent.ACTION_POINTER_DOWN:
|
|
||||||
mInjectedPointersDown |= pointerFlag;
|
|
||||||
mLastInjectedDownEventTime = event.getDownTime();
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_UP:
|
|
||||||
case MotionEvent.ACTION_POINTER_UP:
|
|
||||||
mInjectedPointersDown &= ~pointerFlag;
|
|
||||||
if (mInjectedPointersDown == 0) {
|
|
||||||
mLastInjectedDownEventTime = 0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_HOVER_ENTER:
|
|
||||||
case MotionEvent.ACTION_HOVER_MOVE:
|
|
||||||
case MotionEvent.ACTION_HOVER_EXIT:
|
|
||||||
if (mLastInjectedHoverEvent != null) {
|
|
||||||
mLastInjectedHoverEvent.recycle();
|
|
||||||
}
|
|
||||||
mLastInjectedHoverEvent = MotionEvent.obtain(event);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (DEBUG) {
|
|
||||||
Slog.i(LOG_TAG, "Injected pointer:\n" + toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Clears the internals state. */
|
|
||||||
public void clear() {
|
|
||||||
mInjectedPointersDown = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return The time of the last injected down event. */
|
|
||||||
public long getLastInjectedDownEventTime() {
|
|
||||||
return mLastInjectedDownEventTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return The number of down pointers injected to the view hierarchy. */
|
|
||||||
public int getInjectedPointerDownCount() {
|
|
||||||
return Integer.bitCount(mInjectedPointersDown);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return The bits of the injected pointers that are down. */
|
|
||||||
public int getInjectedPointersDown() {
|
|
||||||
return mInjectedPointersDown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether an injected pointer is down.
|
|
||||||
*
|
|
||||||
* @param pointerId The unique pointer id.
|
|
||||||
* @return True if the pointer is down.
|
|
||||||
*/
|
|
||||||
public boolean isInjectedPointerDown(int pointerId) {
|
|
||||||
final int pointerFlag = (1 << pointerId);
|
|
||||||
return (mInjectedPointersDown & pointerFlag) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return The the last injected hover event. */
|
|
||||||
public MotionEvent getLastInjectedHoverEvent() {
|
|
||||||
return mLastInjectedHoverEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
builder.append("=========================");
|
builder.append("=========================");
|
||||||
builder.append("\nDown pointers #");
|
builder.append("\nDown pointers #");
|
||||||
builder.append(Integer.bitCount(mInjectedPointersDown));
|
builder.append(Integer.bitCount(mState.getInjectedPointersDown()));
|
||||||
builder.append(" [ ");
|
builder.append(" [ ");
|
||||||
for (int i = 0; i < MAX_POINTER_COUNT; i++) {
|
for (int i = 0; i < MAX_POINTER_COUNT; i++) {
|
||||||
if ((mInjectedPointersDown & i) != 0) {
|
if (mState.isInjectedPointerDown(i)) {
|
||||||
builder.append(i);
|
builder.append(i);
|
||||||
builder.append(" ");
|
builder.append(" ");
|
||||||
}
|
}
|
||||||
@@ -235,6 +184,48 @@ class EventDispatcher {
|
|||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /** Offsets all pointers in the given event by adding the specified X and Y offsets.
|
||||||
|
*
|
||||||
|
* @param event The event to offset.
|
||||||
|
* @param offsetX The X offset.
|
||||||
|
* @param offsetY The Y offset.
|
||||||
|
* @return An event with the offset pointers or the original event if both offsets are zero.
|
||||||
|
*/
|
||||||
|
private MotionEvent offsetEvent(MotionEvent event, int offsetX, int offsetY) {
|
||||||
|
if (offsetX == 0 && offsetY == 0) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
final int remappedIndex = event.findPointerIndex(mLongPressingPointerId);
|
||||||
|
final int pointerCount = event.getPointerCount();
|
||||||
|
PointerProperties[] props = PointerProperties.createArray(pointerCount);
|
||||||
|
PointerCoords[] coords = PointerCoords.createArray(pointerCount);
|
||||||
|
for (int i = 0; i < pointerCount; i++) {
|
||||||
|
event.getPointerProperties(i, props[i]);
|
||||||
|
event.getPointerCoords(i, coords[i]);
|
||||||
|
if (i == remappedIndex) {
|
||||||
|
coords[i].x += offsetX;
|
||||||
|
coords[i].y += offsetY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MotionEvent.obtain(
|
||||||
|
event.getDownTime(),
|
||||||
|
event.getEventTime(),
|
||||||
|
event.getAction(),
|
||||||
|
event.getPointerCount(),
|
||||||
|
props,
|
||||||
|
coords,
|
||||||
|
event.getMetaState(),
|
||||||
|
event.getButtonState(),
|
||||||
|
1.0f,
|
||||||
|
1.0f,
|
||||||
|
event.getDeviceId(),
|
||||||
|
event.getEdgeFlags(),
|
||||||
|
event.getSource(),
|
||||||
|
event.getDisplayId(),
|
||||||
|
event.getFlags());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes the action for an injected event based on a masked action and a pointer index.
|
* Computes the action for an injected event based on a masked action and a pointer index.
|
||||||
*
|
*
|
||||||
@@ -247,7 +238,7 @@ class EventDispatcher {
|
|||||||
case MotionEvent.ACTION_DOWN:
|
case MotionEvent.ACTION_DOWN:
|
||||||
case MotionEvent.ACTION_POINTER_DOWN:
|
case MotionEvent.ACTION_POINTER_DOWN:
|
||||||
// Compute the action based on how many down pointers are injected.
|
// Compute the action based on how many down pointers are injected.
|
||||||
if (getInjectedPointerDownCount() == 0) {
|
if (mState.getInjectedPointerDownCount() == 0) {
|
||||||
return MotionEvent.ACTION_DOWN;
|
return MotionEvent.ACTION_DOWN;
|
||||||
} else {
|
} else {
|
||||||
return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
|
return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
|
||||||
@@ -255,7 +246,7 @@ class EventDispatcher {
|
|||||||
}
|
}
|
||||||
case MotionEvent.ACTION_POINTER_UP:
|
case MotionEvent.ACTION_POINTER_UP:
|
||||||
// Compute the action based on how many down pointers are injected.
|
// Compute the action based on how many down pointers are injected.
|
||||||
if (getInjectedPointerDownCount() == 1) {
|
if (mState.getInjectedPointerDownCount() == 1) {
|
||||||
return MotionEvent.ACTION_UP;
|
return MotionEvent.ACTION_UP;
|
||||||
} else {
|
} else {
|
||||||
return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
|
return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
|
||||||
@@ -280,7 +271,7 @@ class EventDispatcher {
|
|||||||
for (int i = 0; i < pointerCount; i++) {
|
for (int i = 0; i < pointerCount; i++) {
|
||||||
final int pointerId = prototype.getPointerId(i);
|
final int pointerId = prototype.getPointerId(i);
|
||||||
// Do not send event for already delivered pointers.
|
// Do not send event for already delivered pointers.
|
||||||
if (!isInjectedPointerDown(pointerId)) {
|
if (!mState.isInjectedPointerDown(pointerId)) {
|
||||||
pointerIdBits |= (1 << pointerId);
|
pointerIdBits |= (1 << pointerId);
|
||||||
final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
|
final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
|
||||||
sendMotionEvent(
|
sendMotionEvent(
|
||||||
@@ -306,7 +297,7 @@ class EventDispatcher {
|
|||||||
for (int i = 0; i < pointerCount; i++) {
|
for (int i = 0; i < pointerCount; i++) {
|
||||||
final int pointerId = prototype.getPointerId(i);
|
final int pointerId = prototype.getPointerId(i);
|
||||||
// Skip non injected down pointers.
|
// Skip non injected down pointers.
|
||||||
if (!isInjectedPointerDown(pointerId)) {
|
if (!mState.isInjectedPointerDown(pointerId)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final int action = computeInjectionAction(MotionEvent.ACTION_POINTER_UP, i);
|
final int action = computeInjectionAction(MotionEvent.ACTION_POINTER_UP, i);
|
||||||
@@ -315,4 +306,97 @@ class EventDispatcher {
|
|||||||
pointerIdBits &= ~(1 << pointerId);
|
pointerIdBits &= ~(1 << pointerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean longPressWithTouchEvents(MotionEvent event, int policyFlags) {
|
||||||
|
final int pointerIndex = event.getActionIndex();
|
||||||
|
final int pointerId = event.getPointerId(pointerIndex);
|
||||||
|
Point clickLocation = mTempPoint;
|
||||||
|
final int result = computeClickLocation(clickLocation);
|
||||||
|
if (result == CLICK_LOCATION_NONE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mLongPressingPointerId = pointerId;
|
||||||
|
mLongPressingPointerDeltaX = (int) event.getX(pointerIndex) - clickLocation.x;
|
||||||
|
mLongPressingPointerDeltaY = (int) event.getY(pointerIndex) - clickLocation.y;
|
||||||
|
sendDownForAllNotInjectedPointers(event, policyFlags);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clickWithTouchEvents(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
|
||||||
|
final int pointerIndex = event.getActionIndex();
|
||||||
|
final int pointerId = event.getPointerId(pointerIndex);
|
||||||
|
Point clickLocation = mTempPoint;
|
||||||
|
final int result = computeClickLocation(clickLocation);
|
||||||
|
if (result == CLICK_LOCATION_NONE) {
|
||||||
|
Slog.e(LOG_TAG, "Unable to compute click location.");
|
||||||
|
// We can't send a click to no location, but the gesture was still
|
||||||
|
// consumed.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Do the click.
|
||||||
|
PointerProperties[] properties = new PointerProperties[1];
|
||||||
|
properties[0] = new PointerProperties();
|
||||||
|
event.getPointerProperties(pointerIndex, properties[0]);
|
||||||
|
PointerCoords[] coords = new PointerCoords[1];
|
||||||
|
coords[0] = new PointerCoords();
|
||||||
|
coords[0].x = clickLocation.x;
|
||||||
|
coords[0].y = clickLocation.y;
|
||||||
|
MotionEvent clickEvent =
|
||||||
|
MotionEvent.obtain(
|
||||||
|
event.getDownTime(),
|
||||||
|
event.getEventTime(),
|
||||||
|
MotionEvent.ACTION_DOWN,
|
||||||
|
1,
|
||||||
|
properties,
|
||||||
|
coords,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1.0f,
|
||||||
|
1.0f,
|
||||||
|
event.getDeviceId(),
|
||||||
|
0,
|
||||||
|
event.getSource(),
|
||||||
|
event.getDisplayId(),
|
||||||
|
event.getFlags());
|
||||||
|
final boolean targetAccessibilityFocus = (result == CLICK_LOCATION_ACCESSIBILITY_FOCUS);
|
||||||
|
sendActionDownAndUp(clickEvent, rawEvent, policyFlags, targetAccessibilityFocus);
|
||||||
|
clickEvent.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int computeClickLocation(Point outLocation) {
|
||||||
|
if (mState.getLastInjectedHoverEventForClick() != null) {
|
||||||
|
final int lastExplorePointerIndex =
|
||||||
|
mState.getLastInjectedHoverEventForClick().getActionIndex();
|
||||||
|
outLocation.x =
|
||||||
|
(int) mState.getLastInjectedHoverEventForClick().getX(lastExplorePointerIndex);
|
||||||
|
outLocation.y =
|
||||||
|
(int) mState.getLastInjectedHoverEventForClick().getY(lastExplorePointerIndex);
|
||||||
|
if (!mAms.accessibilityFocusOnlyInActiveWindow()
|
||||||
|
|| mState.getLastTouchedWindowId() == mAms.getActiveWindowId()) {
|
||||||
|
if (mAms.getAccessibilityFocusClickPointInScreen(outLocation)) {
|
||||||
|
return CLICK_LOCATION_ACCESSIBILITY_FOCUS;
|
||||||
|
} else {
|
||||||
|
return CLICK_LOCATION_LAST_TOUCH_EXPLORED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mAms.getAccessibilityFocusClickPointInScreen(outLocation)) {
|
||||||
|
return CLICK_LOCATION_ACCESSIBILITY_FOCUS;
|
||||||
|
}
|
||||||
|
return CLICK_LOCATION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendActionDownAndUp(
|
||||||
|
MotionEvent prototype,
|
||||||
|
MotionEvent rawEvent,
|
||||||
|
int policyFlags,
|
||||||
|
boolean targetAccessibilityFocus) {
|
||||||
|
// Tap with the pointer that last explored.
|
||||||
|
final int pointerId = prototype.getPointerId(prototype.getActionIndex());
|
||||||
|
final int pointerIdBits = (1 << pointerId);
|
||||||
|
prototype.setTargetAccessibilityFocus(targetAccessibilityFocus);
|
||||||
|
sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, rawEvent, pointerIdBits, policyFlags);
|
||||||
|
prototype.setTargetAccessibilityFocus(targetAccessibilityFocus);
|
||||||
|
sendMotionEvent(prototype, MotionEvent.ACTION_UP, rawEvent, pointerIdBits, policyFlags);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ class GestureManifold implements GestureMatcher.StateChangeListener {
|
|||||||
mHandler = new Handler(context.getMainLooper());
|
mHandler = new Handler(context.getMainLooper());
|
||||||
mListener = listener;
|
mListener = listener;
|
||||||
mState = state;
|
mState = state;
|
||||||
|
mMultiFingerGesturesEnabled = false;
|
||||||
// Set up gestures.
|
// Set up gestures.
|
||||||
// Start with double tap.
|
// Start with double tap.
|
||||||
mGestures.add(new MultiTap(context, 2, GESTURE_DOUBLE_TAP, this));
|
mGestures.add(new MultiTap(context, 2, GESTURE_DOUBLE_TAP, this));
|
||||||
@@ -247,7 +248,7 @@ class GestureManifold implements GestureMatcher.StateChangeListener {
|
|||||||
* and hold is dispatched via onGestureCompleted. Otherwise, this method is called when the
|
* and hold is dispatched via onGestureCompleted. Otherwise, this method is called when the
|
||||||
* user has performed a double tap and then held down the second tap.
|
* user has performed a double tap and then held down the second tap.
|
||||||
*/
|
*/
|
||||||
void onDoubleTapAndHold();
|
void onDoubleTapAndHold(MotionEvent event, MotionEvent rawEvent, int policyFlags);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When FLAG_SERVICE_HANDLES_DOUBLE_TAP is enabled, this method is not called; double-tap is
|
* When FLAG_SERVICE_HANDLES_DOUBLE_TAP is enabled, this method is not called; double-tap is
|
||||||
@@ -256,7 +257,7 @@ class GestureManifold implements GestureMatcher.StateChangeListener {
|
|||||||
*
|
*
|
||||||
* @return true if the event is consumed, else false
|
* @return true if the event is consumed, else false
|
||||||
*/
|
*/
|
||||||
boolean onDoubleTap();
|
boolean onDoubleTap(MotionEvent event, MotionEvent rawEvent, int policyFlags);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the system has decided the event stream is a potential gesture.
|
* Called when the system has decided the event stream is a potential gesture.
|
||||||
@@ -322,7 +323,7 @@ class GestureManifold implements GestureMatcher.StateChangeListener {
|
|||||||
new AccessibilityGestureEvent(gestureId, event.getDisplayId());
|
new AccessibilityGestureEvent(gestureId, event.getDisplayId());
|
||||||
mListener.onGestureCompleted(gestureEvent);
|
mListener.onGestureCompleted(gestureEvent);
|
||||||
} else {
|
} else {
|
||||||
mListener.onDoubleTap();
|
mListener.onDoubleTap(event, rawEvent, policyFlags);
|
||||||
}
|
}
|
||||||
clear();
|
clear();
|
||||||
break;
|
break;
|
||||||
@@ -332,7 +333,7 @@ class GestureManifold implements GestureMatcher.StateChangeListener {
|
|||||||
new AccessibilityGestureEvent(gestureId, event.getDisplayId());
|
new AccessibilityGestureEvent(gestureId, event.getDisplayId());
|
||||||
mListener.onGestureCompleted(gestureEvent);
|
mListener.onGestureCompleted(gestureEvent);
|
||||||
} else {
|
} else {
|
||||||
mListener.onDoubleTapAndHold();
|
mListener.onDoubleTapAndHold(event, rawEvent, policyFlags);
|
||||||
}
|
}
|
||||||
clear();
|
clear();
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import static com.android.server.accessibility.gestures.TouchState.ALL_POINTER_I
|
|||||||
|
|
||||||
import android.accessibilityservice.AccessibilityGestureEvent;
|
import android.accessibilityservice.AccessibilityGestureEvent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Point;
|
|
||||||
import android.graphics.Region;
|
import android.graphics.Region;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
@@ -86,6 +85,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
|
|||||||
// The ID of the pointer used for dragging.
|
// The ID of the pointer used for dragging.
|
||||||
private int mDraggingPointerId;
|
private int mDraggingPointerId;
|
||||||
|
|
||||||
|
|
||||||
// Handler for performing asynchronous operations.
|
// Handler for performing asynchronous operations.
|
||||||
private final Handler mHandler;
|
private final Handler mHandler;
|
||||||
|
|
||||||
@@ -115,8 +115,6 @@ public class TouchExplorer extends BaseEventStreamTransformation
|
|||||||
// Handle to the accessibility manager service.
|
// Handle to the accessibility manager service.
|
||||||
private final AccessibilityManagerService mAms;
|
private final AccessibilityManagerService mAms;
|
||||||
|
|
||||||
// Temporary point to avoid instantiation.
|
|
||||||
private final Point mTempPoint = new Point();
|
|
||||||
|
|
||||||
// Context in which this explorer operates.
|
// Context in which this explorer operates.
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
@@ -277,6 +275,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
|
|||||||
if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
|
if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
|
||||||
sendsPendingA11yEventsIfNeed();
|
sendsPendingA11yEventsIfNeed();
|
||||||
}
|
}
|
||||||
|
mState.onReceivedAccessibilityEvent(event);
|
||||||
super.onAccessibilityEvent(event);
|
super.onAccessibilityEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,16 +308,20 @@ public class TouchExplorer extends BaseEventStreamTransformation
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDoubleTapAndHold() {
|
public void onDoubleTapAndHold(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
|
||||||
// Try to use the standard accessibility API to long click
|
// Try to use the standard accessibility API to long click
|
||||||
if (!mAms.performActionOnAccessibilityFocusedItem(
|
if (!mAms.performActionOnAccessibilityFocusedItem(
|
||||||
AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK)) {
|
AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK)) {
|
||||||
Slog.e(LOG_TAG, "ACTION_LONG_CLICK failed.");
|
Slog.e(LOG_TAG, "ACTION_LONG_CLICK failed.");
|
||||||
|
if (mDispatcher.longPressWithTouchEvents(event, policyFlags)) {
|
||||||
|
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
|
||||||
|
mState.startDelegating();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onDoubleTap() {
|
public boolean onDoubleTap(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
|
||||||
mAms.onTouchInteractionEnd();
|
mAms.onTouchInteractionEnd();
|
||||||
// Remove pending event deliveries.
|
// Remove pending event deliveries.
|
||||||
mSendHoverEnterAndMoveDelayed.cancel();
|
mSendHoverEnterAndMoveDelayed.cancel();
|
||||||
@@ -334,7 +337,10 @@ public class TouchExplorer extends BaseEventStreamTransformation
|
|||||||
// Try to use the standard accessibility API to click
|
// Try to use the standard accessibility API to click
|
||||||
if (!mAms.performActionOnAccessibilityFocusedItem(
|
if (!mAms.performActionOnAccessibilityFocusedItem(
|
||||||
AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)) {
|
AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)) {
|
||||||
Slog.e(LOG_TAG, "ACTION_CLICK failed.");
|
Slog.e(LOG_TAG, "ACTION_CLICK failed. Dispatching motion events to simulate click.");
|
||||||
|
|
||||||
|
mDispatcher.clickWithTouchEvents(event, rawEvent, policyFlags);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -840,7 +846,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
|
|||||||
* @param policyFlags The policy flags associated with the event.
|
* @param policyFlags The policy flags associated with the event.
|
||||||
*/
|
*/
|
||||||
private void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) {
|
private void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) {
|
||||||
MotionEvent event = mDispatcher.getLastInjectedHoverEvent();
|
MotionEvent event = mState.getLastInjectedHoverEvent();
|
||||||
if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
|
if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
|
||||||
final int pointerIdBits = event.getPointerIdBits();
|
final int pointerIdBits = event.getPointerIdBits();
|
||||||
if (!mSendTouchExplorationEndDelayed.isPending()) {
|
if (!mSendTouchExplorationEndDelayed.isPending()) {
|
||||||
@@ -862,7 +868,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
|
|||||||
* @param policyFlags The policy flags associated with the event.
|
* @param policyFlags The policy flags associated with the event.
|
||||||
*/
|
*/
|
||||||
private void sendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags) {
|
private void sendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags) {
|
||||||
MotionEvent event = mDispatcher.getLastInjectedHoverEvent();
|
MotionEvent event = mState.getLastInjectedHoverEvent();
|
||||||
if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
|
if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
|
||||||
final int pointerIdBits = event.getPointerIdBits();
|
final int pointerIdBits = event.getPointerIdBits();
|
||||||
mDispatcher.sendMotionEvent(
|
mDispatcher.sendMotionEvent(
|
||||||
@@ -1188,7 +1194,6 @@ public class TouchExplorer extends BaseEventStreamTransformation
|
|||||||
+ ", mDetermineUserIntentTimeout: " + mDetermineUserIntentTimeout
|
+ ", mDetermineUserIntentTimeout: " + mDetermineUserIntentTimeout
|
||||||
+ ", mDoubleTapSlop: " + mDoubleTapSlop
|
+ ", mDoubleTapSlop: " + mDoubleTapSlop
|
||||||
+ ", mDraggingPointerId: " + mDraggingPointerId
|
+ ", mDraggingPointerId: " + mDraggingPointerId
|
||||||
+ ", mTempPoint: " + mTempPoint
|
|
||||||
+ " }";
|
+ " }";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,16 @@ public class TouchState {
|
|||||||
private MotionEvent mLastReceivedEvent;
|
private MotionEvent mLastReceivedEvent;
|
||||||
// The accompanying raw event without any transformations.
|
// The accompanying raw event without any transformations.
|
||||||
private MotionEvent mLastReceivedRawEvent;
|
private MotionEvent mLastReceivedRawEvent;
|
||||||
|
// The id of the last touch explored window.
|
||||||
|
private int mLastTouchedWindowId;
|
||||||
|
// The last injected hover event.
|
||||||
|
private MotionEvent mLastInjectedHoverEvent;
|
||||||
|
// The last injected hover event used for performing clicks.
|
||||||
|
private MotionEvent mLastInjectedHoverEventForClick;
|
||||||
|
// The time of the last injected down.
|
||||||
|
private long mLastInjectedDownEventTime;
|
||||||
|
// Keep track of which pointers sent to the system are down.
|
||||||
|
private int mInjectedPointersDown;
|
||||||
|
|
||||||
public TouchState() {
|
public TouchState() {
|
||||||
mReceivedPointerTracker = new ReceivedPointerTracker();
|
mReceivedPointerTracker = new ReceivedPointerTracker();
|
||||||
@@ -88,7 +98,9 @@ public class TouchState {
|
|||||||
mLastReceivedEvent.recycle();
|
mLastReceivedEvent.recycle();
|
||||||
mLastReceivedEvent = null;
|
mLastReceivedEvent = null;
|
||||||
}
|
}
|
||||||
|
mLastTouchedWindowId = -1;
|
||||||
mReceivedPointerTracker.clear();
|
mReceivedPointerTracker.clear();
|
||||||
|
mInjectedPointersDown = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -107,6 +119,71 @@ public class TouchState {
|
|||||||
mReceivedPointerTracker.onMotionEvent(rawEvent);
|
mReceivedPointerTracker.onMotionEvent(rawEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes an injected {@link MotionEvent} event.
|
||||||
|
*
|
||||||
|
* @param event The event to process.
|
||||||
|
*/
|
||||||
|
void onInjectedMotionEvent(MotionEvent event) {
|
||||||
|
final int action = event.getActionMasked();
|
||||||
|
final int pointerId = event.getPointerId(event.getActionIndex());
|
||||||
|
final int pointerFlag = (1 << pointerId);
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
case MotionEvent.ACTION_POINTER_DOWN:
|
||||||
|
mInjectedPointersDown |= pointerFlag;
|
||||||
|
mLastInjectedDownEventTime = event.getDownTime();
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_POINTER_UP:
|
||||||
|
mInjectedPointersDown &= ~pointerFlag;
|
||||||
|
if (mInjectedPointersDown == 0) {
|
||||||
|
mLastInjectedDownEventTime = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_HOVER_ENTER:
|
||||||
|
case MotionEvent.ACTION_HOVER_MOVE:
|
||||||
|
if (mLastInjectedHoverEvent != null) {
|
||||||
|
mLastInjectedHoverEvent.recycle();
|
||||||
|
}
|
||||||
|
mLastInjectedHoverEvent = MotionEvent.obtain(event);
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_HOVER_EXIT:
|
||||||
|
if (mLastInjectedHoverEvent != null) {
|
||||||
|
mLastInjectedHoverEvent.recycle();
|
||||||
|
}
|
||||||
|
mLastInjectedHoverEvent = MotionEvent.obtain(event);
|
||||||
|
if (mLastInjectedHoverEventForClick != null) {
|
||||||
|
mLastInjectedHoverEventForClick.recycle();
|
||||||
|
}
|
||||||
|
mLastInjectedHoverEventForClick = MotionEvent.obtain(event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.i(LOG_TAG, "Injected pointer:\n" + toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates state in response to an accessibility event received from the outside. */
|
||||||
|
public void onReceivedAccessibilityEvent(AccessibilityEvent event) {
|
||||||
|
// If a new window opens or the accessibility focus moves we no longer
|
||||||
|
// want to click/long press on the last touch explored location.
|
||||||
|
switch (event.getEventType()) {
|
||||||
|
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
|
||||||
|
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
|
||||||
|
if (mLastInjectedHoverEventForClick != null) {
|
||||||
|
mLastInjectedHoverEventForClick.recycle();
|
||||||
|
mLastInjectedHoverEventForClick = null;
|
||||||
|
}
|
||||||
|
mLastTouchedWindowId = -1;
|
||||||
|
break;
|
||||||
|
case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
|
||||||
|
case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT:
|
||||||
|
mLastTouchedWindowId = event.getWindowId();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void onInjectedAccessibilityEvent(int type) {
|
public void onInjectedAccessibilityEvent(int type) {
|
||||||
// The below state transitions go here because the related events are often sent on a
|
// The below state transitions go here because the related events are often sent on a
|
||||||
// delay.
|
// delay.
|
||||||
@@ -236,6 +313,46 @@ public class TouchState {
|
|||||||
return mLastReceivedEvent;
|
return mLastReceivedEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return The the last injected hover event. */
|
||||||
|
public MotionEvent getLastInjectedHoverEvent() {
|
||||||
|
return mLastInjectedHoverEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return The time of the last injected down event. */
|
||||||
|
public long getLastInjectedDownEventTime() {
|
||||||
|
return mLastInjectedDownEventTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLastTouchedWindowId() {
|
||||||
|
return mLastTouchedWindowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return The number of down pointers injected to the view hierarchy. */
|
||||||
|
public int getInjectedPointerDownCount() {
|
||||||
|
return Integer.bitCount(mInjectedPointersDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return The bits of the injected pointers that are down. */
|
||||||
|
public int getInjectedPointersDown() {
|
||||||
|
return mInjectedPointersDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether an injected pointer is down.
|
||||||
|
*
|
||||||
|
* @param pointerId The unique pointer id.
|
||||||
|
* @return True if the pointer is down.
|
||||||
|
*/
|
||||||
|
public boolean isInjectedPointerDown(int pointerId) {
|
||||||
|
final int pointerFlag = (1 << pointerId);
|
||||||
|
return (mInjectedPointersDown & pointerFlag) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return The the last injected hover event used for a click. */
|
||||||
|
public MotionEvent getLastInjectedHoverEventForClick() {
|
||||||
|
return mLastInjectedHoverEventForClick;
|
||||||
|
}
|
||||||
|
|
||||||
/** This class tracks where and when a pointer went down. It does not track its movement. */
|
/** This class tracks where and when a pointer went down. It does not track its movement. */
|
||||||
class ReceivedPointerTracker {
|
class ReceivedPointerTracker {
|
||||||
private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker";
|
private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker";
|
||||||
|
|||||||
Reference in New Issue
Block a user