Skip to content

Commit 25bf4c4

Browse files
Treehugger RobotAndroid (Google) Code Review
authored andcommitted
Merge "Introduce AccessibilityPointerMotionFilter" into main
2 parents 1042eb2 + a86a633 commit 25bf4c4

5 files changed

Lines changed: 135 additions & 2 deletions

File tree

services/inputflinger/PointerChoreographer.cpp

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <android-base/logging.h>
2020
#include <android/configuration.h>
2121
#include <com_android_input_flags.h>
22+
#include <algorithm>
2223
#if defined(__ANDROID__)
2324
#include <gui/SurfaceComposerClient.h>
2425
#endif
@@ -165,6 +166,7 @@ PointerChoreographer::PointerChoreographer(
165166
mNotifiedPointerDisplayId(ui::LogicalDisplayId::INVALID),
166167
mShowTouchesEnabled(false),
167168
mStylusPointerIconEnabled(false),
169+
mPointerMotionFilterEnabled(false),
168170
mCurrentFocusedDisplay(ui::LogicalDisplayId::DEFAULT),
169171
mIsWindowInfoListenerRegistered(false),
170172
mWindowInfoListener(sp<PointerChoreographerDisplayInfoListener>::make(this)),
@@ -322,8 +324,10 @@ void PointerChoreographer::processPointerDeviceMotionEventLocked(NotifyMotionArg
322324
PointerControllerInterface& pc) {
323325
const float deltaX = newArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
324326
const float deltaY = newArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
325-
326-
vec2 unconsumedDelta = pc.move(deltaX, deltaY);
327+
vec2 filteredDelta =
328+
filterPointerMotionForAccessibilityLocked(pc.getPosition(), vec2{deltaX, deltaY},
329+
newArgs.displayId);
330+
vec2 unconsumedDelta = pc.move(filteredDelta.x, filteredDelta.y);
327331
if (com::android::input::flags::connected_displays_cursor() &&
328332
(std::abs(unconsumedDelta.x) > 0 || std::abs(unconsumedDelta.y) > 0)) {
329333
handleUnconsumedDeltaLocked(pc, unconsumedDelta);
@@ -638,6 +642,8 @@ void PointerChoreographer::dump(std::string& dump) {
638642
mShowTouchesEnabled ? "true" : "false");
639643
dump += StringPrintf(INDENT "Stylus PointerIcon Enabled: %s\n",
640644
mStylusPointerIconEnabled ? "true" : "false");
645+
dump += StringPrintf(INDENT "Accessibility Pointer Motion Filter Enabled: %s\n",
646+
mPointerMotionFilterEnabled ? "true" : "false");
641647

642648
dump += INDENT "MousePointerControllers:\n";
643649
for (const auto& [displayId, mousePointerController] : mMousePointersByDisplay) {
@@ -973,6 +979,11 @@ void PointerChoreographer::setFocusedDisplay(ui::LogicalDisplayId displayId) {
973979
mCurrentFocusedDisplay = displayId;
974980
}
975981

982+
void PointerChoreographer::setAccessibilityPointerMotionFilterEnabled(bool enabled) {
983+
std::scoped_lock _l(getLock());
984+
mPointerMotionFilterEnabled = enabled;
985+
}
986+
976987
PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor(
977988
ui::LogicalDisplayId displayId) {
978989
std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
@@ -1046,6 +1057,21 @@ PointerChoreographer::findDestinationDisplayLocked(const ui::LogicalDisplayId so
10461057
return std::nullopt;
10471058
}
10481059

1060+
vec2 PointerChoreographer::filterPointerMotionForAccessibilityLocked(
1061+
const vec2& current, const vec2& delta, const ui::LogicalDisplayId& displayId) {
1062+
if (!mPointerMotionFilterEnabled) {
1063+
return delta;
1064+
}
1065+
std::optional<vec2> filterResult =
1066+
mPolicy.filterPointerMotionForAccessibility(current, delta, displayId);
1067+
if (!filterResult.has_value()) {
1068+
// Disable filter when there's any error.
1069+
mPointerMotionFilterEnabled = false;
1070+
return delta;
1071+
}
1072+
return *filterResult;
1073+
}
1074+
10491075
// --- PointerChoreographer::PointerChoreographerDisplayInfoListener ---
10501076

10511077
void PointerChoreographer::PointerChoreographerDisplayInfoListener::onWindowInfosChanged(

services/inputflinger/PointerChoreographer.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ class PointerChoreographerInterface : public InputListenerInterface {
9090
* This method may be called on any thread (usually by the input manager on a binder thread).
9191
*/
9292
virtual void dump(std::string& dump) = 0;
93+
94+
/**
95+
* Enables motion event filter before pointer coordinates are determined.
96+
*/
97+
virtual void setAccessibilityPointerMotionFilterEnabled(bool enabled) = 0;
9398
};
9499

95100
class PointerChoreographer : public PointerChoreographerInterface {
@@ -110,6 +115,7 @@ class PointerChoreographer : public PointerChoreographerInterface {
110115
void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) override;
111116
void setFocusedDisplay(ui::LogicalDisplayId displayId) override;
112117
void setDisplayTopology(const DisplayTopologyGraph& displayTopologyGraph);
118+
void setAccessibilityPointerMotionFilterEnabled(bool enabled) override;
113119

114120
void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
115121
void notifyKey(const NotifyKeyArgs& args) override;
@@ -168,6 +174,10 @@ class PointerChoreographer : public PointerChoreographerInterface {
168174
const DisplayTopologyPosition sourceBoundary,
169175
int32_t sourceCursorOffsetPx) const REQUIRES(getLock());
170176

177+
vec2 filterPointerMotionForAccessibilityLocked(const vec2& current, const vec2& delta,
178+
const ui::LogicalDisplayId& displayId)
179+
REQUIRES(getLock());
180+
171181
/* Topology is initialized with default-constructed value, which is an empty topology. Till we
172182
* receive setDisplayTopology call.
173183
* Meanwhile Choreographer will treat every display as independent disconnected display.
@@ -228,6 +238,7 @@ class PointerChoreographer : public PointerChoreographerInterface {
228238
std::vector<DisplayViewport> mViewports GUARDED_BY(getLock());
229239
bool mShowTouchesEnabled GUARDED_BY(getLock());
230240
bool mStylusPointerIconEnabled GUARDED_BY(getLock());
241+
bool mPointerMotionFilterEnabled GUARDED_BY(getLock());
231242
std::set<ui::LogicalDisplayId /*displayId*/> mDisplaysWithPointersHidden;
232243
ui::LogicalDisplayId mCurrentFocusedDisplay GUARDED_BY(getLock());
233244

services/inputflinger/include/PointerChoreographerPolicyInterface.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ class PointerChoreographerPolicyInterface {
6161

6262
/* Notifies that mouse cursor faded due to typing. */
6363
virtual void notifyMouseCursorFadedOnTyping() = 0;
64+
65+
/**
66+
* Give accessibility a chance to filter motion event by pointer devices.
67+
* The return values denotes the delta x and y after filtering it.
68+
*
69+
* This call happens on the input hot path and it is extremely performance sensitive.
70+
* This also must not call back into native code.
71+
*/
72+
virtual std::optional<vec2> filterPointerMotionForAccessibility(
73+
const vec2& current, const vec2& delta, const ui::LogicalDisplayId& displayId) = 0;
6474
};
6575

6676
} // namespace android

services/inputflinger/tests/InterfaceMocks.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ class MockPointerChoreographerPolicyInterface : public PointerChoreographerPolic
191191
(ui::LogicalDisplayId displayId, const vec2& position), (override));
192192
MOCK_METHOD(bool, isInputMethodConnectionActive, (), (override));
193193
MOCK_METHOD(void, notifyMouseCursorFadedOnTyping, (), (override));
194+
MOCK_METHOD(std::optional<vec2>, filterPointerMotionForAccessibility,
195+
(const vec2& current, const vec2& delta, const ui::LogicalDisplayId& displayId),
196+
(override));
194197
};
195198

196199
class MockInputDevice : public InputDevice {

services/inputflinger/tests/PointerChoreographer_test.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,6 +1776,89 @@ TEST_F(PointerChoreographerTest, SetsPointerIconForMouseOnTwoDisplays) {
17761776
firstMousePc->assertPointerIconNotSet();
17771777
}
17781778

1779+
TEST_F(PointerChoreographerTest, A11yPointerMotionFilterMouse) {
1780+
mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
1781+
mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
1782+
mChoreographer.notifyInputDevicesChanged(
1783+
{/*id=*/0,
1784+
{generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
1785+
ui::LogicalDisplayId::INVALID)}});
1786+
auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
1787+
ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
1788+
1789+
pc->setPosition(100, 200);
1790+
mChoreographer.setAccessibilityPointerMotionFilterEnabled(true);
1791+
1792+
EXPECT_CALL(mMockPolicy,
1793+
filterPointerMotionForAccessibility(testing::Eq(vec2{100, 200}),
1794+
testing::Eq(vec2{10.f, 20.f}),
1795+
testing::Eq(DISPLAY_ID)))
1796+
.Times(1)
1797+
.WillOnce(testing::Return(vec2{4, 13}));
1798+
1799+
mChoreographer.notifyMotion(
1800+
MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
1801+
.pointer(MOUSE_POINTER)
1802+
.deviceId(DEVICE_ID)
1803+
.displayId(ui::LogicalDisplayId::INVALID)
1804+
.build());
1805+
1806+
// Cursor position is decided by filtered delta, but pointer coord's relative values are kept.
1807+
pc->assertPosition(104, 213);
1808+
mTestListener.assertNotifyMotionWasCalled(AllOf(WithCoords(104, 213), WithDisplayId(DISPLAY_ID),
1809+
WithCursorPosition(104, 213),
1810+
WithRelativeMotion(10, 20)));
1811+
}
1812+
1813+
TEST_F(PointerChoreographerTest, A11yPointerMotionFilterTouchpad) {
1814+
mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
1815+
mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
1816+
mChoreographer.notifyInputDevicesChanged(
1817+
{/*id=*/0,
1818+
{generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
1819+
ui::LogicalDisplayId::INVALID)}});
1820+
auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
1821+
ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
1822+
1823+
pc->setPosition(100, 200);
1824+
mChoreographer.setAccessibilityPointerMotionFilterEnabled(true);
1825+
1826+
EXPECT_CALL(mMockPolicy,
1827+
filterPointerMotionForAccessibility(testing::Eq(vec2{100, 200}),
1828+
testing::Eq(vec2{10.f, 20.f}),
1829+
testing::Eq(DISPLAY_ID)))
1830+
.Times(1)
1831+
.WillOnce(testing::Return(vec2{4, 13}));
1832+
1833+
mChoreographer.notifyMotion(
1834+
MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
1835+
.pointer(TOUCHPAD_POINTER)
1836+
.deviceId(DEVICE_ID)
1837+
.displayId(ui::LogicalDisplayId::INVALID)
1838+
.build());
1839+
1840+
// Cursor position is decided by filtered delta, but pointer coord's relative values are kept.
1841+
pc->assertPosition(104, 213);
1842+
mTestListener.assertNotifyMotionWasCalled(AllOf(WithCoords(104, 213), WithDisplayId(DISPLAY_ID),
1843+
WithCursorPosition(104, 213),
1844+
WithRelativeMotion(10, 20)));
1845+
}
1846+
1847+
TEST_F(PointerChoreographerTest, A11yPointerMotionFilterNotFilterTouch) {
1848+
mChoreographer.notifyInputDevicesChanged(
1849+
{/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
1850+
mChoreographer.setAccessibilityPointerMotionFilterEnabled(true);
1851+
1852+
EXPECT_CALL(mMockPolicy, filterPointerMotionForAccessibility).Times(0);
1853+
1854+
mChoreographer.notifyMotion(
1855+
MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
1856+
.pointer(FIRST_TOUCH_POINTER)
1857+
.deviceId(DEVICE_ID)
1858+
.displayId(DISPLAY_ID)
1859+
.build());
1860+
}
1861+
17791862
using SkipPointerScreenshotForPrivacySensitiveDisplaysFixtureParam =
17801863
std::tuple<std::string_view /*name*/, uint32_t /*source*/, ControllerType, PointerBuilder,
17811864
std::function<void(PointerChoreographer&)>, int32_t /*action*/>;

0 commit comments

Comments
 (0)