InputDispatcher: Implement stylus interceptor
A stylus interceptor is a window that receives all stylus events within
its touchable bounds, while letting all other events be dispatched to
windows behind it. This makes it possible for the framework to create a
stylus interceptor on top of another app to implement handwriting
recognition.
The feature has no effect when the window flag FLAG_NOT_TOUCHABLE is
not set.
The feature has no effect when the window is not a trusted overlay.
Test: atest inputflinger_tests
Change-Id: Id4833840aa3088d21333d3ea08beffbded4debbc
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index ba70929..b2d0fb8 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -1003,6 +1003,7 @@
mInfo.ownerPid = INJECTOR_PID;
mInfo.ownerUid = INJECTOR_UID;
mInfo.displayId = displayId;
+ mInfo.trustedOverlay = false;
}
sp<FakeWindowHandle> clone(
@@ -1054,7 +1055,9 @@
void setFlags(Flags<WindowInfo::Flag> flags) { mInfo.flags = flags; }
- void setInputFeatures(WindowInfo::Feature features) { mInfo.inputFeatures = features; }
+ void setInputFeatures(Flags<WindowInfo::Feature> features) { mInfo.inputFeatures = features; }
+
+ void setTrustedOverlay(bool trustedOverlay) { mInfo.trustedOverlay = trustedOverlay; }
void setWindowTransform(float dsdx, float dtdx, float dtdy, float dsdy) {
mInfo.transform.set(dsdx, dtdx, dtdy, dsdy);
@@ -6600,4 +6603,97 @@
spyRight->consumeMotionDown();
}
+class InputDispatcherStylusInterceptorTest : public InputDispatcherTest {
+public:
+ std::pair<sp<FakeWindowHandle>, sp<FakeWindowHandle>> setupStylusOverlayScenario() {
+ std::shared_ptr<FakeApplicationHandle> overlayApplication =
+ std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> overlay =
+ new FakeWindowHandle(overlayApplication, mDispatcher, "Stylus interceptor window",
+ ADISPLAY_ID_DEFAULT);
+ overlay->setFocusable(false);
+ overlay->setOwnerInfo(111, 111);
+ overlay->setFlags(WindowInfo::Flag::NOT_TOUCHABLE | WindowInfo::Flag::SPLIT_TOUCH);
+ overlay->setInputFeatures(WindowInfo::Feature::INTERCEPTS_STYLUS);
+ overlay->setTrustedOverlay(true);
+
+ std::shared_ptr<FakeApplicationHandle> application =
+ std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ new FakeWindowHandle(application, mDispatcher, "Application window",
+ ADISPLAY_ID_DEFAULT);
+ window->setFocusable(true);
+ window->setOwnerInfo(222, 222);
+ window->setFlags(WindowInfo::Flag::SPLIT_TOUCH);
+
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+ setFocusedWindow(window);
+ window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+ return {std::move(overlay), std::move(window)};
+ }
+
+ void sendFingerEvent(int32_t action) {
+ NotifyMotionArgs motionArgs =
+ generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
+ ADISPLAY_ID_DEFAULT, {PointF{20, 20}});
+ mDispatcher->notifyMotion(&motionArgs);
+ }
+
+ void sendStylusEvent(int32_t action) {
+ NotifyMotionArgs motionArgs =
+ generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
+ ADISPLAY_ID_DEFAULT, {PointF{30, 40}});
+ motionArgs.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+ mDispatcher->notifyMotion(&motionArgs);
+ }
+};
+
+TEST_F(InputDispatcherStylusInterceptorTest, UntrustedOverlay_AbortsDispatcher) {
+ auto [overlay, window] = setupStylusOverlayScenario();
+ overlay->setTrustedOverlay(false);
+ // Configuring an untrusted overlay as a stylus interceptor should cause Dispatcher to abort.
+ ASSERT_DEATH(mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}}),
+ ".* not a trusted overlay");
+}
+
+TEST_F(InputDispatcherStylusInterceptorTest, ConsmesOnlyStylusEvents) {
+ auto [overlay, window] = setupStylusOverlayScenario();
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+
+ sendStylusEvent(AMOTION_EVENT_ACTION_DOWN);
+ overlay->consumeMotionDown();
+ sendStylusEvent(AMOTION_EVENT_ACTION_UP);
+ overlay->consumeMotionUp();
+
+ sendFingerEvent(AMOTION_EVENT_ACTION_DOWN);
+ window->consumeMotionDown();
+ sendFingerEvent(AMOTION_EVENT_ACTION_UP);
+ window->consumeMotionUp();
+
+ overlay->assertNoEvents();
+ window->assertNoEvents();
+}
+
+TEST_F(InputDispatcherStylusInterceptorTest, SpyWindowStylusInterceptor) {
+ auto [overlay, window] = setupStylusOverlayScenario();
+ overlay->setInputFeatures(overlay->getInfo()->inputFeatures | WindowInfo::Feature::SPY);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+
+ sendStylusEvent(AMOTION_EVENT_ACTION_DOWN);
+ overlay->consumeMotionDown();
+ window->consumeMotionDown();
+ sendStylusEvent(AMOTION_EVENT_ACTION_UP);
+ overlay->consumeMotionUp();
+ window->consumeMotionUp();
+
+ sendFingerEvent(AMOTION_EVENT_ACTION_DOWN);
+ window->consumeMotionDown();
+ sendFingerEvent(AMOTION_EVENT_ACTION_UP);
+ window->consumeMotionUp();
+
+ overlay->assertNoEvents();
+ window->assertNoEvents();
+}
+
} // namespace android::inputdispatcher