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
This commit is contained in:
Siarhei Vishniakou
2021-06-17 23:27:48 +00:00
parent a46f524537
commit b42785efdf
11 changed files with 84 additions and 26 deletions

View File

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

View File

@@ -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<InputDevice> CREATOR =
new Parcelable.Creator<InputDevice>() {
public InputDevice createFromParcel(Parcel in) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<GestureStep> 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<MotionEvent>() {
@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());
}