From b42785efdf3ad0a73db9bd780581dd0ea70a9f54 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 17 Jun 2021 23:27:48 +0000 Subject: [PATCH] Add FLAG_IS_ACCESSIBILITY_EVENT to KeyEvent and MotionEvent This flag indicates that the event was modified or generated by an accessibility service. It allows apps to tell apart real hardware events, events that are injected (device id == -1), and events coming from accessibility (has flag is_accessibility_event). Events that have gone into accessibility, and got reinjected without being modified will not be distinguishable from real hardware events. In the next release, we will make FLAG_IS_ACCESSIBILITY_EVENT public api. Until then, applications will have to hard-code its value (0x800) to use it. The value is the same for both KeyEvents and MotionEvents for convenience. Bug: 175069843 Bug: 152399927 Test: atest VerifiedMotionEventTest VerifiedKeyEventTest Test: atest AccessibilityGestureDispatchTest Test: atest inputflinger_tests libinput_tests GamepadWithAccessibilityTest Change-Id: I38ac2ab8e19e32cad927742c623f14f43ea0c588 --- core/api/test-current.txt | 3 +- core/java/android/view/InputDevice.java | 7 ----- core/java/android/view/KeyEvent.java | 9 ++++++ core/java/android/view/MotionEvent.java | 9 ++++++ .../java/android/view/VerifiedInputEvent.java | 25 ++++++++++++++++ core/java/android/view/VerifiedKeyEvent.java | 3 ++ .../android/view/VerifiedMotionEvent.java | 5 ++++ .../view/WindowManagerPolicyConstants.java | 2 -- .../android/view/VerifiedMotionEventTest.kt | 8 +++-- .../accessibility/MotionEventInjector.java | 9 ++---- .../MotionEventInjectorTest.java | 30 ++++++++++++++----- 11 files changed, 84 insertions(+), 26 deletions(-) diff --git a/core/api/test-current.txt b/core/api/test-current.txt index a4ac61b1f086e..cb60b011d00e2 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2724,12 +2724,12 @@ package android.view { public final class InputDevice implements android.os.Parcelable { method @RequiresPermission("android.permission.DISABLE_INPUT_DEVICE") public void disable(); method @RequiresPermission("android.permission.DISABLE_INPUT_DEVICE") public void enable(); - field public static final int ACCESSIBILITY_DEVICE_ID = -2; // 0xfffffffe } public class KeyEvent extends android.view.InputEvent implements android.os.Parcelable { method public static String actionToString(int); method public final void setDisplayId(int); + field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800 field public static final int LAST_KEYCODE = 288; // 0x120 } @@ -2747,6 +2747,7 @@ package android.view { method public void setActionButton(int); method public void setButtonState(int); method public void setDisplayId(int); + field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800 } @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface RemotableViewMethod { diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index 782a992d28e52..4f1354d7eee6a 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -444,13 +444,6 @@ public final class InputDevice implements Parcelable { private static final int VIBRATOR_ID_ALL = -1; - /** - * The device id of input events generated inside accessibility service. - * @hide - */ - @TestApi - public static final int ACCESSIBILITY_DEVICE_ID = -2; - public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { public InputDevice createFromParcel(Parcel in) { diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index 37f0a64df6132..cda9b233576c0 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -16,6 +16,7 @@ package android.view; +import static android.os.IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT; import static android.view.Display.INVALID_DISPLAY; import android.annotation.NonNull; @@ -1221,6 +1222,14 @@ public class KeyEvent extends InputEvent implements Parcelable { */ public static final int FLAG_FALLBACK = 0x400; + /** + * This flag indicates that this event was modified by or generated from an accessibility + * service. Value = 0x800 + * @hide + */ + @TestApi + public static final int FLAG_IS_ACCESSIBILITY_EVENT = INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT; + /** * Signifies that the key is being predispatched. * @hide diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 0483d0ba76152..69ff64f3d6a58 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -16,6 +16,7 @@ package android.view; +import static android.os.IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT; import static android.view.Display.DEFAULT_DISPLAY; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -493,6 +494,14 @@ public final class MotionEvent extends InputEvent implements Parcelable { */ public static final int FLAG_NO_FOCUS_CHANGE = 0x40; + /** + * This flag indicates that this event was modified by or generated from an accessibility + * service. Value = 0x800 + * @hide + */ + @TestApi + public static final int FLAG_IS_ACCESSIBILITY_EVENT = INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT; + /** * Private flag that indicates when the system has detected that this motion event * may be inconsistent with respect to the sequence of previously delivered motion events, diff --git a/core/java/android/view/VerifiedInputEvent.java b/core/java/android/view/VerifiedInputEvent.java index e2db50165f77a..cc6fbe7f51713 100644 --- a/core/java/android/view/VerifiedInputEvent.java +++ b/core/java/android/view/VerifiedInputEvent.java @@ -20,6 +20,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SuppressLint; import android.os.Parcel; import android.os.Parcelable; @@ -157,4 +158,28 @@ public abstract class VerifiedInputEvent implements Parcelable { throw new IllegalArgumentException("Unexpected input event type in parcel."); } }; + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + VerifiedInputEvent that = (VerifiedInputEvent) o; + return mType == that.mType + && getDeviceId() == that.getDeviceId() + && getEventTimeNanos() == that.getEventTimeNanos() + && getSource() == that.getSource() + && getDisplayId() == that.getDisplayId(); + } + + @Override + public int hashCode() { + int _hash = 1; + _hash = 31 * _hash + mType; + _hash = 31 * _hash + getDeviceId(); + _hash = 31 * _hash + Long.hashCode(getEventTimeNanos()); + _hash = 31 * _hash + getSource(); + _hash = 31 * _hash + getDisplayId(); + return _hash; + } } diff --git a/core/java/android/view/VerifiedKeyEvent.java b/core/java/android/view/VerifiedKeyEvent.java index 77a7d09445212..fc357cc9d2786 100644 --- a/core/java/android/view/VerifiedKeyEvent.java +++ b/core/java/android/view/VerifiedKeyEvent.java @@ -17,6 +17,7 @@ package android.view; import static android.view.KeyEvent.FLAG_CANCELED; +import static android.view.KeyEvent.FLAG_IS_ACCESSIBILITY_EVENT; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -72,6 +73,7 @@ public final class VerifiedKeyEvent extends VerifiedInputEvent implements Parcel * * @see KeyEvent#getFlags() * @see KeyEvent#FLAG_CANCELED + * @see KeyEvent#FLAG_IS_ACCESSIBILITY_EVENT * * @hide */ @@ -125,6 +127,7 @@ public final class VerifiedKeyEvent extends VerifiedInputEvent implements Parcel // InputDispatcher only verifies a subset of the KeyEvent flags. // These values must be kept in sync with Input.cpp case FLAG_CANCELED: + case FLAG_IS_ACCESSIBILITY_EVENT: return (mFlags & flag) != 0; } return null; diff --git a/core/java/android/view/VerifiedMotionEvent.java b/core/java/android/view/VerifiedMotionEvent.java index 7d8345928bf08..948575cf31046 100644 --- a/core/java/android/view/VerifiedMotionEvent.java +++ b/core/java/android/view/VerifiedMotionEvent.java @@ -16,6 +16,7 @@ package android.view; +import static android.view.MotionEvent.FLAG_IS_ACCESSIBILITY_EVENT; import static android.view.MotionEvent.FLAG_WINDOW_IS_OBSCURED; import static android.view.MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED; @@ -83,6 +84,9 @@ public final class VerifiedMotionEvent extends VerifiedInputEvent implements Par * Returns the flags for this motion event. * * @see MotionEvent#getFlags() + * @see MotionEvent#FLAG_IS_ACCESSIBILITY_EVENT + * @see MotionEvent#FLAG_WINDOW_IS_OBSCURED + * @see MotionEvent#FLAG_WINDOW_IS_PARTIALLY_OBSCURED * @hide */ private int mFlags; @@ -117,6 +121,7 @@ public final class VerifiedMotionEvent extends VerifiedInputEvent implements Par switch(flag) { // InputDispatcher only verifies a subset of the MotionEvent flags. // These values must be kept in sync with Input.cpp + case FLAG_IS_ACCESSIBILITY_EVENT: case FLAG_WINDOW_IS_OBSCURED: case FLAG_WINDOW_IS_PARTIALLY_OBSCURED: return (mFlags & flag) != 0; diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index 81c934dffa477..bbef3e6aeefa3 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -17,7 +17,6 @@ package android.view; import static android.os.IInputConstants.POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY; -import static android.os.IInputConstants.POLICY_FLAG_INPUTFILTER_TRUSTED; import android.annotation.IntDef; import android.os.PowerManager; @@ -35,7 +34,6 @@ public interface WindowManagerPolicyConstants { int FLAG_WAKE = 0x00000001; int FLAG_VIRTUAL = 0x00000002; - int FLAG_INPUTFILTER_TRUSTED = POLICY_FLAG_INPUTFILTER_TRUSTED; int FLAG_INJECTED_FROM_ACCESSIBILITY = POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY; int FLAG_INJECTED = 0x01000000; int FLAG_TRUSTED = 0x02000000; diff --git a/core/tests/coretests/src/android/view/VerifiedMotionEventTest.kt b/core/tests/coretests/src/android/view/VerifiedMotionEventTest.kt index 2c4851f0ab4e7..29ad0aa43672b 100644 --- a/core/tests/coretests/src/android/view/VerifiedMotionEventTest.kt +++ b/core/tests/coretests/src/android/view/VerifiedMotionEventTest.kt @@ -18,6 +18,7 @@ package android.view import android.view.InputDevice.SOURCE_TOUCHSCREEN import android.view.MotionEvent.ACTION_MOVE +import android.view.MotionEvent.FLAG_IS_ACCESSIBILITY_EVENT import android.view.MotionEvent.FLAG_WINDOW_IS_OBSCURED import android.view.MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED import android.view.MotionEvent.FLAG_TAINTED @@ -49,6 +50,7 @@ class VerifiedMotionEventTest { assertEquals(RAW_Y, event.rawY, 0f) assertEquals(ACTION_MASKED, event.actionMasked) assertEquals(DOWN_TIME_NANOS, event.downTimeNanos) + assertEquals(FLAGS, event.flags) assertEquals(META_STATE, event.metaState) assertEquals(BUTTON_STATE, event.buttonState) } @@ -128,8 +130,9 @@ class VerifiedMotionEventTest { assertNull(motionEvent.getFlag(0)) // Flag that was not set assertEquals(false, motionEvent.getFlag(FLAG_WINDOW_IS_PARTIALLY_OBSCURED)) - // Flag that was set + // Flags that were set assertEquals(true, motionEvent.getFlag(FLAG_WINDOW_IS_OBSCURED)) + assertEquals(true, motionEvent.getFlag(FLAG_IS_ACCESSIBILITY_EVENT)) // Only 1 flag at a time is accepted assertNull(motionEvent.getFlag( FLAG_WINDOW_IS_PARTIALLY_OBSCURED or FLAG_WINDOW_IS_OBSCURED)) @@ -153,7 +156,7 @@ class VerifiedMotionEventTest { private const val RAW_Y = 200f private const val ACTION_MASKED = ACTION_MOVE private const val DOWN_TIME_NANOS: Long = 1000 - private const val FLAGS = FLAG_WINDOW_IS_OBSCURED + private const val FLAGS = FLAG_WINDOW_IS_OBSCURED or FLAG_IS_ACCESSIBILITY_EVENT private const val META_STATE = 11 private const val BUTTON_STATE = 22 @@ -178,6 +181,7 @@ class VerifiedMotionEventTest { assertEquals(event1.rawY, event2.rawY, 0f) assertEquals(event1.actionMasked, event2.actionMasked) assertEquals(event1.downTimeNanos, event2.downTimeNanos) + assertEquals(event1.flags, event2.flags) assertEquals(event1.metaState, event2.metaState) assertEquals(event1.buttonState, event2.buttonState) } diff --git a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java index ea2c7d2a41e95..2673cd1ac3b8b 100644 --- a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java +++ b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java @@ -30,6 +30,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.view.InputDevice; +import android.view.KeyCharacterMap; import android.view.MotionEvent; import android.view.WindowManagerPolicyConstants; @@ -55,7 +56,6 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement */ private static final int EVENT_META_STATE = 0; private static final int EVENT_BUTTON_STATE = 0; - private static final int EVENT_DEVICE_ID = 0; private static final int EVENT_EDGE_FLAGS = 0; private static final int EVENT_SOURCE = InputDevice.SOURCE_TOUCHSCREEN; private static final int EVENT_FLAGS = 0; @@ -122,9 +122,6 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement return; } cancelAnyPendingInjectedEvents(); - // The events injected from outside of system_server are not trusted. Remove the flag to - // prevent accessibility service from impersonating a real input device. - policyFlags &= ~WindowManagerPolicyConstants.FLAG_INPUTFILTER_TRUSTED; // Indicate that the input event is injected from accessibility, to let applications // distinguish it from events injected by other means. policyFlags |= WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY; @@ -483,8 +480,8 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement } return MotionEvent.obtain(downTime, eventTime, action, touchPointsSize, sPointerProps, sPointerCoords, EVENT_META_STATE, EVENT_BUTTON_STATE, - EVENT_X_PRECISION, EVENT_Y_PRECISION, EVENT_DEVICE_ID, EVENT_EDGE_FLAGS, - EVENT_SOURCE, EVENT_FLAGS); + EVENT_X_PRECISION, EVENT_Y_PRECISION, KeyCharacterMap.VIRTUAL_KEYBOARD, + EVENT_EDGE_FLAGS, EVENT_SOURCE, EVENT_FLAGS); } private static int findPointByStrokeId(TouchPoint[] touchPoints, int touchPointsSize, diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java index a71b481372d84..d4908ee78a1d7 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java @@ -16,6 +16,7 @@ package com.android.server.accessibility; +import static android.view.KeyCharacterMap.VIRTUAL_KEYBOARD; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_HOVER_MOVE; import static android.view.MotionEvent.ACTION_UP; @@ -112,6 +113,13 @@ public class MotionEventInjectorTest { private static final int CONTINUED_LINE_SEQUENCE_1 = 52; private static final int CONTINUED_LINE_SEQUENCE_2 = 53; + private static final float PRESSURE = 1; + private static final float X_PRECISION = 1; + private static final float Y_PRECISION = 1; + private static final int EDGEFLAGS = 0; + private static final float POINTER_SIZE = 1; + private static final int METASTATE = 0; + MotionEventInjector mMotionEventInjector; IAccessibilityServiceClient mServiceInterface; List mLineList = new ArrayList<>(); @@ -152,14 +160,18 @@ public class MotionEventInjectorTest { CONTINUED_LINE_STROKE_ID_1, false, CONTINUED_LINE_INTERVAL, CONTINUED_LINE_MID1, CONTINUED_LINE_MID2, CONTINUED_LINE_END); - mClickDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, CLICK_POINT.x, CLICK_POINT.y, 0); + mClickDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, CLICK_POINT.x, CLICK_POINT.y, + PRESSURE, POINTER_SIZE, METASTATE, X_PRECISION, Y_PRECISION, VIRTUAL_KEYBOARD, + EDGEFLAGS); mClickDownEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); mClickUpEvent = MotionEvent.obtain(0, CLICK_DURATION, ACTION_UP, CLICK_POINT.x, - CLICK_POINT.y, 0); + CLICK_POINT.y, PRESSURE, POINTER_SIZE, METASTATE, X_PRECISION, Y_PRECISION, + VIRTUAL_KEYBOARD, EDGEFLAGS); mClickUpEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); mHoverMoveEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, CLICK_POINT.x, CLICK_POINT.y, - 0); + PRESSURE, POINTER_SIZE, METASTATE, X_PRECISION, Y_PRECISION, VIRTUAL_KEYBOARD, + EDGEFLAGS); mHoverMoveEvent.setSource(InputDevice.SOURCE_MOUSE); mIsLineStart = allOf(IS_ACTION_DOWN, isAtPoint(LINE_START), hasStandardInitialization(), @@ -874,12 +886,14 @@ public class MotionEventInjectorTest { return new TypeSafeMatcher() { @Override protected boolean matchesSafely(MotionEvent event) { - return (0 == event.getActionIndex()) && (0 == event.getDeviceId()) - && (0 == event.getEdgeFlags()) && (0 == event.getFlags()) - && (0 == event.getMetaState()) && (0F == event.getOrientation()) + return (0 == event.getActionIndex()) && (VIRTUAL_KEYBOARD == event.getDeviceId()) + && (EDGEFLAGS == event.getEdgeFlags()) && (0 == event.getFlags()) + && (METASTATE == event.getMetaState()) && (0F == event.getOrientation()) && (0F == event.getTouchMajor()) && (0F == event.getTouchMinor()) - && (1F == event.getXPrecision()) && (1F == event.getYPrecision()) - && (1 == event.getPointerCount()) && (1F == event.getPressure()) + && (X_PRECISION == event.getXPrecision()) + && (Y_PRECISION == event.getYPrecision()) + && (POINTER_SIZE == event.getSize()) + && (1 == event.getPointerCount()) && (PRESSURE == event.getPressure()) && (InputDevice.SOURCE_TOUCHSCREEN == event.getSource()); }