Reset the features in A11yInputFilter per display

The A11yInputFilter is an input filter for implementing many
accessibility features on multi-display. When any display is
added or removed, all features of all displays would be reset
now.

To avoid server side bad user experience, we should only reset
the features and the event stream state based on the changed
display which is added or removed.

Bug: 187996964
Test: a11y CTS & unit tests
Change-Id: I39ae59e12690240adbb0e81338dcb4c736a92197
This commit is contained in:
Jacky Kao
2021-06-10 09:23:20 +08:00
parent 1c61b6dcfc
commit e6014fc060
3 changed files with 315 additions and 129 deletions

View File

@@ -21,6 +21,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY
import android.accessibilityservice.AccessibilityTrace;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Region;
import android.os.PowerManager;
@@ -44,7 +45,10 @@ import com.android.server.accessibility.magnification.WindowMagnificationGesture
import com.android.server.accessibility.magnification.WindowMagnificationPromptController;
import com.android.server.policy.WindowManagerPolicy;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.StringJoiner;
/**
* This class is an input filter for implementing accessibility features such
@@ -172,9 +176,9 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
private int mEnabledFeatures;
private EventStreamState mMouseStreamState;
private final SparseArray<EventStreamState> mMouseStreamStates = new SparseArray<>(0);
private EventStreamState mTouchScreenStreamState;
private final SparseArray<EventStreamState> mTouchScreenStreamStates = new SparseArray<>(0);
private EventStreamState mKeyboardStreamState;
@@ -212,10 +216,17 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
super.onUninstalled();
}
void onDisplayChanged() {
void onDisplayAdded(@NonNull Display display) {
if (mInstalled) {
disableFeatures();
enableFeatures();
resetStreamStateForDisplay(display.getDisplayId());
enableFeaturesForDisplay(display);
}
}
void onDisplayRemoved(int displayId) {
if (mInstalled) {
disableFeaturesForDisplay(displayId);
resetStreamStateForDisplay(displayId);
}
}
@@ -243,16 +254,17 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
return;
}
int eventSource = event.getSource();
final int eventSource = event.getSource();
final int displayId = event.getDisplayId();
if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
state.reset();
clearEventsForAllEventHandlers(eventSource);
clearEventStreamHandler(displayId, eventSource);
super.onInputEvent(event, policyFlags);
return;
}
if (state.updateInputSource(event.getSource())) {
clearEventsForAllEventHandlers(eventSource);
clearEventStreamHandler(displayId, eventSource);
}
if (!state.inputSourceValid()) {
@@ -281,35 +293,39 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
*/
private EventStreamState getEventStreamState(InputEvent event) {
if (event instanceof MotionEvent) {
if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
if (mTouchScreenStreamState == null) {
mTouchScreenStreamState = new TouchScreenEventStreamState();
}
return mTouchScreenStreamState;
}
if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
if (mMouseStreamState == null) {
mMouseStreamState = new MouseEventStreamState();
}
return mMouseStreamState;
}
final int displayId = event.getDisplayId();
if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
EventStreamState touchScreenStreamState = mTouchScreenStreamStates.get(displayId);
if (touchScreenStreamState == null) {
touchScreenStreamState = new TouchScreenEventStreamState();
mTouchScreenStreamStates.put(displayId, touchScreenStreamState);
}
return touchScreenStreamState;
}
if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
EventStreamState mouseStreamState = mMouseStreamStates.get(displayId);
if (mouseStreamState == null) {
mouseStreamState = new MouseEventStreamState();
mMouseStreamStates.put(displayId, mouseStreamState);
}
return mouseStreamState;
}
} else if (event instanceof KeyEvent) {
if (event.isFromSource(InputDevice.SOURCE_KEYBOARD)) {
if (mKeyboardStreamState == null) {
mKeyboardStreamState = new KeyboardEventStreamState();
}
return mKeyboardStreamState;
}
if (event.isFromSource(InputDevice.SOURCE_KEYBOARD)) {
if (mKeyboardStreamState == null) {
mKeyboardStreamState = new KeyboardEventStreamState();
}
return mKeyboardStreamState;
}
}
return null;
}
private void clearEventsForAllEventHandlers(int eventSource) {
for (int i = 0; i < mEventHandler.size(); i++) {
final EventStreamTransformation eventHandler = mEventHandler.valueAt(i);
if (eventHandler != null) {
eventHandler.clearEvents(eventSource);
}
private void clearEventStreamHandler(int displayId, int eventSource) {
final EventStreamTransformation eventHandler = mEventHandler.get(displayId);
if (eventHandler != null) {
eventHandler.clearEvents(eventSource);
}
}
@@ -425,56 +441,69 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
private void enableFeatures() {
if (DEBUG) Slog.i(TAG, "enableFeatures()");
resetStreamState();
resetAllStreamState();
final ArrayList<Display> displaysList = mAms.getValidDisplayList();
if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
mAutoclickController = new AutoclickController(
mContext, mUserId, mAms.getTraceManager());
addFirstEventHandlerForAllDisplays(displaysList, mAutoclickController);
}
for (int i = displaysList.size() - 1; i >= 0; i--) {
final int displayId = displaysList.get(i).getDisplayId();
final Context displayContext = mContext.createDisplayContext(displaysList.get(i));
enableFeaturesForDisplay(displaysList.get(i));
}
enableDisplayIndependentFeatures();
}
if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
TouchExplorer explorer = new TouchExplorer(displayContext, mAms);
if ((mEnabledFeatures & FLAG_SERVICE_HANDLES_DOUBLE_TAP) != 0) {
explorer.setServiceHandlesDoubleTap(true);
}
if ((mEnabledFeatures & FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0) {
explorer.setMultiFingerGesturesEnabled(true);
}
if ((mEnabledFeatures & FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0) {
explorer.setTwoFingerPassthroughEnabled(true);
}
if ((mEnabledFeatures & FLAG_SEND_MOTION_EVENTS) != 0) {
explorer.setSendMotionEventsEnabled(true);
}
addFirstEventHandler(displayId, explorer);
mTouchExplorer.put(displayId, explorer);
}
if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
|| ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
|| ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
final MagnificationGestureHandler magnificationGestureHandler =
createMagnificationGestureHandler(displayId,
displayContext);
addFirstEventHandler(displayId, magnificationGestureHandler);
mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
}
if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
MotionEventInjector injector = new MotionEventInjector(
mContext.getMainLooper(), mAms.getTraceManager());
addFirstEventHandler(displayId, injector);
mMotionEventInjectors.put(displayId, injector);
}
private void enableFeaturesForDisplay(Display display) {
if (DEBUG) {
Slog.i(TAG, "enableFeaturesForDisplay() : display Id = " + display.getDisplayId());
}
final Context displayContext = mContext.createDisplayContext(display);
final int displayId = display.getDisplayId();
if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
if (mAutoclickController == null) {
mAutoclickController = new AutoclickController(
mContext, mUserId, mAms.getTraceManager());
}
addFirstEventHandler(displayId, mAutoclickController);
}
if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
TouchExplorer explorer = new TouchExplorer(displayContext, mAms);
if ((mEnabledFeatures & FLAG_SERVICE_HANDLES_DOUBLE_TAP) != 0) {
explorer.setServiceHandlesDoubleTap(true);
}
if ((mEnabledFeatures & FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0) {
explorer.setMultiFingerGesturesEnabled(true);
}
if ((mEnabledFeatures & FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0) {
explorer.setTwoFingerPassthroughEnabled(true);
}
if ((mEnabledFeatures & FLAG_SEND_MOTION_EVENTS) != 0) {
explorer.setSendMotionEventsEnabled(true);
}
addFirstEventHandler(displayId, explorer);
mTouchExplorer.put(displayId, explorer);
}
if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
|| ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
|| ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
final MagnificationGestureHandler magnificationGestureHandler =
createMagnificationGestureHandler(displayId,
displayContext);
addFirstEventHandler(displayId, magnificationGestureHandler);
mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
}
if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
MotionEventInjector injector = new MotionEventInjector(
mContext.getMainLooper(), mAms.getTraceManager());
addFirstEventHandler(displayId, injector);
mMotionEventInjectors.put(displayId, injector);
}
}
private void enableDisplayIndependentFeatures() {
if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
mAms.setMotionEventInjectors(mMotionEventInjectors);
}
@@ -507,55 +536,57 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mEventHandler.put(displayId, eventHandler);
}
/**
* Adds an event handler to the event handler chain for all displays. The handler is added at
* the beginning of the chain.
*
* @param displayList The list of displays
* @param handler The handler to be added to the event handlers list.
*/
private void addFirstEventHandlerForAllDisplays(ArrayList<Display> displayList,
EventStreamTransformation handler) {
for (int i = 0; i < displayList.size(); i++) {
final int displayId = displayList.get(i).getDisplayId();
addFirstEventHandler(displayId, handler);
private void disableFeatures() {
final ArrayList<Display> displaysList = mAms.getValidDisplayList();
for (int i = displaysList.size() - 1; i >= 0; i--) {
disableFeaturesForDisplay(displaysList.get(i).getDisplayId());
}
mAms.setMotionEventInjectors(null);
disableDisplayIndependentFeatures();
resetAllStreamState();
}
private void disableFeaturesForDisplay(int displayId) {
if (DEBUG) {
Slog.i(TAG, "disableFeaturesForDisplay() : display Id = " + displayId);
}
final MotionEventInjector injector = mMotionEventInjectors.get(displayId);
if (injector != null) {
injector.onDestroy();
mMotionEventInjectors.remove(displayId);
}
final TouchExplorer explorer = mTouchExplorer.get(displayId);
if (explorer != null) {
explorer.onDestroy();
mTouchExplorer.remove(displayId);
}
final MagnificationGestureHandler handler = mMagnificationGestureHandler.get(displayId);
if (handler != null) {
handler.onDestroy();
mMagnificationGestureHandler.remove(displayId);
}
final EventStreamTransformation eventStreamTransformation = mEventHandler.get(displayId);
if (eventStreamTransformation != null) {
mEventHandler.remove(displayId);
}
}
private void disableFeatures() {
for (int i = mMotionEventInjectors.size() - 1; i >= 0; i--) {
final MotionEventInjector injector = mMotionEventInjectors.valueAt(i);
if (injector != null) {
injector.onDestroy();
}
}
mAms.setMotionEventInjectors(null);
mMotionEventInjectors.clear();
private void disableDisplayIndependentFeatures() {
if (mAutoclickController != null) {
mAutoclickController.onDestroy();
mAutoclickController = null;
}
for (int i = mTouchExplorer.size() - 1; i >= 0; i--) {
final TouchExplorer explorer = mTouchExplorer.valueAt(i);
if (explorer != null) {
explorer.onDestroy();
}
}
mTouchExplorer.clear();
for (int i = mMagnificationGestureHandler.size() - 1; i >= 0; i--) {
final MagnificationGestureHandler handler = mMagnificationGestureHandler.valueAt(i);
if (handler != null) {
handler.onDestroy();
}
}
mMagnificationGestureHandler.clear();
if (mKeyboardInterceptor != null) {
mKeyboardInterceptor.onDestroy();
mKeyboardInterceptor = null;
}
mEventHandler.clear();
resetStreamState();
}
private MagnificationGestureHandler createMagnificationGestureHandler(
@@ -584,18 +615,32 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
return magnificationGestureHandler;
}
void resetStreamState() {
if (mTouchScreenStreamState != null) {
mTouchScreenStreamState.reset();
}
if (mMouseStreamState != null) {
mMouseStreamState.reset();
void resetAllStreamState() {
final ArrayList<Display> displaysList = mAms.getValidDisplayList();
for (int i = displaysList.size() - 1; i >= 0; i--) {
resetStreamStateForDisplay(displaysList.get(i).getDisplayId());
}
if (mKeyboardStreamState != null) {
mKeyboardStreamState.reset();
}
}
void resetStreamStateForDisplay(int displayId) {
final EventStreamState touchScreenStreamState = mTouchScreenStreamStates.get(displayId);
if (touchScreenStreamState != null) {
touchScreenStreamState.reset();
mTouchScreenStreamStates.remove(displayId);
}
final EventStreamState mouseStreamState = mMouseStreamStates.get(displayId);
if (mouseStreamState != null) {
mouseStreamState.reset();
mMouseStreamStates.remove(displayId);
}
}
@Override
public void onDestroy() {
/* ignore */
@@ -846,4 +891,45 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mTouchExplorer.get(displayId).setTouchExplorationPassthroughRegion(region);
}
}
/**
* Dumps all {@link AccessibilityInputFilter}s here.
*/
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (mEventHandler == null) {
return;
}
pw.append("A11yInputFilter Info : ");
pw.println();
final ArrayList<Display> displaysList = mAms.getValidDisplayList();
for (int i = 0; i < displaysList.size(); i++) {
final int displayId = displaysList.get(i).getDisplayId();
EventStreamTransformation next = mEventHandler.get(displayId);
if (next != null) {
pw.append("Enabled features of Display [");
pw.append(Integer.toString(displayId));
pw.append("] = ");
final StringJoiner joiner = new StringJoiner(",", "[", "]");
while (next != null) {
if (next instanceof MagnificationGestureHandler) {
joiner.add("MagnificationGesture");
} else if (next instanceof KeyboardInterceptor) {
joiner.add("KeyboardInterceptor");
} else if (next instanceof TouchExplorer) {
joiner.add("TouchExplorer");
} else if (next instanceof AutoclickController) {
joiner.add("AutoclickController");
} else if (next instanceof MotionEventInjector) {
joiner.add("MotionEventInjector");
}
next = next.getNext();
}
pw.append(joiner.toString());
}
pw.println();
}
}
}

View File

@@ -3340,6 +3340,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
pw.println();
}
mA11yWindowManager.dump(fd, pw, args);
if (mHasInputFilter && mInputFilter != null) {
mInputFilter.dump(fd, pw, args);
}
pw.println("Global client list info:{");
mGlobalClients.dump(pw, " Client list ");
pw.println(" Registered clients:{");
@@ -3587,7 +3590,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
synchronized (mLock) {
mDisplaysList.add(display);
if (mInputFilter != null) {
mInputFilter.onDisplayChanged();
mInputFilter.onDisplayAdded(display);
}
AccessibilityUserState userState = getCurrentUserStateLocked();
if (displayId != Display.DEFAULT_DISPLAY) {
@@ -3609,7 +3612,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return;
}
if (mInputFilter != null) {
mInputFilter.onDisplayChanged();
mInputFilter.onDisplayRemoved(displayId);
}
AccessibilityUserState userState = getCurrentUserStateLocked();
if (displayId != Display.DEFAULT_DISPLAY) {

View File

@@ -29,6 +29,7 @@ import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEA
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -176,7 +177,7 @@ public class AccessibilityInputFilterTest {
}
@Test
public void testEventHandler_shouldChangeAfterOnDisplayChanged() {
public void testEventHandler_shouldIncreaseAndHaveCorrectOrderAfterOnDisplayAdded() {
prepareLooper();
// Check if there is only one mEventHandler when there is one default display.
@@ -184,13 +185,51 @@ public class AccessibilityInputFilterTest {
assertEquals(1, mEventHandler.size());
// Check if it has correct numbers of mEventHandler for corresponding displays.
setDisplayCount(4);
mA11yInputFilter.onDisplayChanged();
assertEquals(4, mEventHandler.size());
setDisplayCount(2);
mA11yInputFilter.onDisplayAdded(mDisplayList.get(SECOND_DISPLAY));
assertEquals(2, mEventHandler.size());
EventStreamTransformation next = mEventHandler.get(SECOND_DISPLAY);
assertNotNull(next);
// Start from index 1 because KeyboardInterceptor only exists in EventHandler for
// DEFAULT_DISPLAY.
for (int i = 1; next != null; i++) {
assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]);
next = next.getNext();
}
}
@Test
public void testEventHandler_shouldDecreaseAfterOnDisplayRemoved() {
prepareLooper();
setDisplayCount(2);
mA11yInputFilter.onDisplayChanged();
mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
assertEquals(2, mEventHandler.size());
// Check if it has correct numbers of mEventHandler for corresponding displays.
mA11yInputFilter.onDisplayRemoved(SECOND_DISPLAY);
assertEquals(1, mEventHandler.size());
EventStreamTransformation eventHandler = mEventHandler.get(SECOND_DISPLAY);
assertNull(eventHandler);
}
@Test
public void testEventHandler_shouldNoChangedInOtherDisplayAfterOnDisplayRemoved() {
prepareLooper();
setDisplayCount(2);
mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
EventStreamTransformation eventHandlerBeforeDisplayRemoved =
mEventHandler.get(DEFAULT_DISPLAY);
mA11yInputFilter.onDisplayRemoved(SECOND_DISPLAY);
EventStreamTransformation eventHandlerAfterDisplayRemoved =
mEventHandler.get(DEFAULT_DISPLAY);
assertEquals(eventHandlerBeforeDisplayRemoved, eventHandlerAfterDisplayRemoved);
}
@Test
@@ -240,7 +279,7 @@ public class AccessibilityInputFilterTest {
}
@Test
public void testInputEvent_shouldClearEventsForAllEventHandlers() {
public void testInputEvent_shouldClearEventsForDisplayEventHandlers() {
prepareLooper();
mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
@@ -253,12 +292,70 @@ public class AccessibilityInputFilterTest {
send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
assertEquals(2, mCaptor1.mEvents.size());
// InputEvent with different input source should trigger clearEvents() for each
// EventStreamTransformation in EventHandler.
// InputEvent with different input source to the same display should trigger
// clearEvents() for the EventHandler in this display.
send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_MOUSE));
assertEquals(1, mCaptor1.mEvents.size());
}
@Test
public void testInputEvent_shouldNotClearEventsForOtherDisplayEventHandlers() {
prepareLooper();
setDisplayCount(2);
mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
assertEquals(2, mEventHandler.size());
mCaptor1 = new EventCaptor();
mCaptor2 = new EventCaptor();
mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
mEventHandler.put(SECOND_DISPLAY, mCaptor2);
// InputEvent with different displayId should be dispatched to corresponding EventHandler.
send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
send(downEvent(SECOND_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
// InputEvent with different input source should not trigger clearEvents() for
// the EventHandler in the other display.
send(downEvent(SECOND_DISPLAY, InputDevice.SOURCE_MOUSE));
assertEquals(2, mCaptor1.mEvents.size());
}
@Test
public void testInputEvent_shouldNotClearEventsForOtherDisplayAfterOnDisplayAdded() {
prepareLooper();
mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
mCaptor1 = new EventCaptor();
mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
assertEquals(2, mCaptor1.mEvents.size());
setDisplayCount(2);
mA11yInputFilter.onDisplayAdded(mDisplayList.get(SECOND_DISPLAY));
assertEquals(2, mCaptor1.mEvents.size());
}
@Test
public void testInputEvent_shouldNotClearEventsForOtherDisplayAfterOnDisplayRemoved() {
prepareLooper();
setDisplayCount(2);
mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
mCaptor1 = new EventCaptor();
mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
assertEquals(2, mCaptor1.mEvents.size());
mA11yInputFilter.onDisplayRemoved(SECOND_DISPLAY);
assertEquals(2, mCaptor1.mEvents.size());
}
@Test
public void testEnabledFeatures_windowMagnificationMode_expectedMagnificationGestureHandler() {
prepareLooper();