xref: /aosp_15_r20/frameworks/native/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp (revision 38e8c45f13ce32b0dcecb25141ffecaf386fa17f)
1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "RotaryEncoderInputMapper.h"
18 
19 #include <list>
20 #include <string>
21 #include <tuple>
22 #include <variant>
23 
24 #include <android-base/logging.h>
25 #include <android_companion_virtualdevice_flags.h>
26 #include <com_android_input_flags.h>
27 #include <flag_macros.h>
28 #include <gtest/gtest.h>
29 #include <input/DisplayViewport.h>
30 #include <linux/input-event-codes.h>
31 #include <linux/input.h>
32 #include <utils/Timers.h>
33 
34 #include "InputMapperTest.h"
35 #include "InputReaderBase.h"
36 #include "InterfaceMocks.h"
37 #include "NotifyArgs.h"
38 #include "TestEventMatchers.h"
39 #include "ui/Rotation.h"
40 
41 #define TAG "RotaryEncoderInputMapper_test"
42 
43 namespace android {
44 
45 using testing::AllOf;
46 using testing::Return;
47 using testing::VariantWith;
48 constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
49 constexpr ui::LogicalDisplayId SECONDARY_DISPLAY_ID = ui::LogicalDisplayId{DISPLAY_ID.val() + 1};
50 constexpr int32_t DISPLAY_WIDTH = 480;
51 constexpr int32_t DISPLAY_HEIGHT = 800;
52 
53 namespace {
54 
createViewport()55 DisplayViewport createViewport() {
56     DisplayViewport v;
57     v.orientation = ui::Rotation::Rotation0;
58     v.logicalRight = DISPLAY_HEIGHT;
59     v.logicalBottom = DISPLAY_WIDTH;
60     v.physicalRight = DISPLAY_HEIGHT;
61     v.physicalBottom = DISPLAY_WIDTH;
62     v.deviceWidth = DISPLAY_HEIGHT;
63     v.deviceHeight = DISPLAY_WIDTH;
64     v.isActive = true;
65     return v;
66 }
67 
createPrimaryViewport()68 DisplayViewport createPrimaryViewport() {
69     DisplayViewport v = createViewport();
70     v.displayId = DISPLAY_ID;
71     v.uniqueId = "local:1";
72     return v;
73 }
74 
createSecondaryViewport()75 DisplayViewport createSecondaryViewport() {
76     DisplayViewport v = createViewport();
77     v.displayId = SECONDARY_DISPLAY_ID;
78     v.uniqueId = "local:2";
79     v.type = ViewportType::EXTERNAL;
80     return v;
81 }
82 
83 } // namespace
84 
85 namespace vd_flags = android::companion::virtualdevice::flags;
86 
87 /**
88  * Unit tests for RotaryEncoderInputMapper.
89  */
90 class RotaryEncoderInputMapperTest : public InputMapperUnitTest {
91 protected:
SetUp()92     void SetUp() override { SetUpWithBus(BUS_USB); }
SetUpWithBus(int bus)93     void SetUpWithBus(int bus) override {
94         InputMapperUnitTest::SetUpWithBus(bus);
95 
96         EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL))
97                 .WillRepeatedly(Return(true));
98         EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL))
99                 .WillRepeatedly(Return(false));
100         EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
101                 .WillRepeatedly(Return(false));
102         EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
103                 .WillRepeatedly(Return(false));
104     }
105 
106     std::map<std::string, int64_t> mTelemetryLogCounts;
107 
108     /**
109      * A fake function for telemetry logging.
110      * Records the log counts in the `mTelemetryLogCounts` map.
111      */
112     std::function<void(const char*, int64_t)> mTelemetryLogCounter =
__anon99f72efc0202(const char* key, int64_t value) 113             [this](const char* key, int64_t value) { mTelemetryLogCounts[key] += value; };
114 };
115 
TEST_F(RotaryEncoderInputMapperTest,ConfigureDisplayIdWithAssociatedViewport)116 TEST_F(RotaryEncoderInputMapperTest, ConfigureDisplayIdWithAssociatedViewport) {
117     DisplayViewport primaryViewport = createPrimaryViewport();
118     DisplayViewport secondaryViewport = createSecondaryViewport();
119     mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
120 
121     // Set up the secondary display as the associated viewport of the mapper.
122     EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(secondaryViewport));
123     mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration);
124 
125     std::list<NotifyArgs> args;
126     // Ensure input events are generated for the secondary display.
127     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
128     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
129     EXPECT_THAT(args,
130                 ElementsAre(VariantWith<NotifyMotionArgs>(
131                         AllOf(WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
132                               WithSource(AINPUT_SOURCE_ROTARY_ENCODER),
133                               WithDisplayId(SECONDARY_DISPLAY_ID)))));
134 }
135 
TEST_F(RotaryEncoderInputMapperTest,ConfigureDisplayIdNoAssociatedViewport)136 TEST_F(RotaryEncoderInputMapperTest, ConfigureDisplayIdNoAssociatedViewport) {
137     // Set up the default display.
138     mFakePolicy->clearViewports();
139     mFakePolicy->addDisplayViewport(createPrimaryViewport());
140 
141     // Set up the mapper with no associated viewport.
142     mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration);
143 
144     // Ensure input events are generated without display ID
145     std::list<NotifyArgs> args;
146     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
147     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
148     EXPECT_THAT(args,
149                 ElementsAre(VariantWith<NotifyMotionArgs>(
150                         AllOf(WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
151                               WithSource(AINPUT_SOURCE_ROTARY_ENCODER),
152                               WithDisplayId(ui::LogicalDisplayId::INVALID)))));
153 }
154 
TEST_F(RotaryEncoderInputMapperTest,ProcessRegularScroll)155 TEST_F(RotaryEncoderInputMapperTest, ProcessRegularScroll) {
156     mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration);
157 
158     std::list<NotifyArgs> args;
159     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
160     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
161 
162     EXPECT_THAT(args,
163                 ElementsAre(VariantWith<NotifyMotionArgs>(
164                         AllOf(WithSource(AINPUT_SOURCE_ROTARY_ENCODER),
165                               WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), WithScroll(1.0f)))));
166 }
167 
TEST_F(RotaryEncoderInputMapperTest,ProcessHighResScroll)168 TEST_F(RotaryEncoderInputMapperTest, ProcessHighResScroll) {
169     vd_flags::high_resolution_scroll(true);
170     EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
171             .WillRepeatedly(Return(true));
172     mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration);
173 
174     std::list<NotifyArgs> args;
175     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60);
176     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
177 
178     EXPECT_THAT(args,
179                 ElementsAre(VariantWith<NotifyMotionArgs>(
180                         AllOf(WithSource(AINPUT_SOURCE_ROTARY_ENCODER),
181                               WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), WithScroll(0.5f)))));
182 }
183 
TEST_F(RotaryEncoderInputMapperTest,HighResScrollIgnoresRegularScroll)184 TEST_F(RotaryEncoderInputMapperTest, HighResScrollIgnoresRegularScroll) {
185     vd_flags::high_resolution_scroll(true);
186     EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
187             .WillRepeatedly(Return(true));
188     mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration);
189 
190     std::list<NotifyArgs> args;
191     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60);
192     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
193     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
194 
195     EXPECT_THAT(args,
196                 ElementsAre(VariantWith<NotifyMotionArgs>(
197                         AllOf(WithSource(AINPUT_SOURCE_ROTARY_ENCODER),
198                               WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), WithScroll(0.5f)))));
199 }
200 
TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest,RotaryInputTelemetryFlagOff_NoRotationLogging,REQUIRES_FLAGS_DISABLED (ACONFIG_FLAG (com::android::input::flags,rotary_input_telemetry)))201 TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, RotaryInputTelemetryFlagOff_NoRotationLogging,
202                   REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(com::android::input::flags,
203                                                        rotary_input_telemetry))) {
204     mPropertyMap.addProperty("device.res", "3");
205     mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
206                                                           mTelemetryLogCounter);
207     InputDeviceInfo info;
208     mMapper->populateDeviceInfo(info);
209 
210     std::list<NotifyArgs> args;
211     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 70);
212     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
213 
214     ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
215               mTelemetryLogCounts.end());
216 }
217 
TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest,ZeroResolution_NoRotationLogging,REQUIRES_FLAGS_ENABLED (ACONFIG_FLAG (com::android::input::flags,rotary_input_telemetry)))218 TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, ZeroResolution_NoRotationLogging,
219                   REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
220                                                       rotary_input_telemetry))) {
221     mPropertyMap.addProperty("device.res", "-3");
222     mPropertyMap.addProperty("rotary_encoder.min_rotations_to_log", "2");
223     mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
224                                                           mTelemetryLogCounter);
225     InputDeviceInfo info;
226     mMapper->populateDeviceInfo(info);
227 
228     std::list<NotifyArgs> args;
229     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 700);
230     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
231 
232     ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
233               mTelemetryLogCounts.end());
234 }
235 
TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest,NegativeMinLogRotation_NoRotationLogging,REQUIRES_FLAGS_ENABLED (ACONFIG_FLAG (com::android::input::flags,rotary_input_telemetry)))236 TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, NegativeMinLogRotation_NoRotationLogging,
237                   REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
238                                                       rotary_input_telemetry))) {
239     mPropertyMap.addProperty("device.res", "3");
240     mPropertyMap.addProperty("rotary_encoder.min_rotations_to_log", "-2");
241     mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
242                                                           mTelemetryLogCounter);
243     InputDeviceInfo info;
244     mMapper->populateDeviceInfo(info);
245 
246     std::list<NotifyArgs> args;
247     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 700);
248     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
249 
250     ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
251               mTelemetryLogCounts.end());
252 }
253 
TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest,ZeroMinLogRotation_NoRotationLogging,REQUIRES_FLAGS_ENABLED (ACONFIG_FLAG (com::android::input::flags,rotary_input_telemetry)))254 TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, ZeroMinLogRotation_NoRotationLogging,
255                   REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
256                                                       rotary_input_telemetry))) {
257     mPropertyMap.addProperty("device.res", "3");
258     mPropertyMap.addProperty("rotary_encoder.min_rotations_to_log", "0");
259     mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
260                                                           mTelemetryLogCounter);
261     InputDeviceInfo info;
262     mMapper->populateDeviceInfo(info);
263 
264     std::list<NotifyArgs> args;
265     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 700);
266     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
267 
268     ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
269               mTelemetryLogCounts.end());
270 }
271 
TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest,NoMinLogRotation_NoRotationLogging,REQUIRES_FLAGS_ENABLED (ACONFIG_FLAG (com::android::input::flags,rotary_input_telemetry)))272 TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, NoMinLogRotation_NoRotationLogging,
273                   REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
274                                                       rotary_input_telemetry))) {
275     // 3 units per radian, 2 * M_PI * 3 = ~18.85 units per rotation.
276     mPropertyMap.addProperty("device.res", "3");
277     mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
278                                                           mTelemetryLogCounter);
279     InputDeviceInfo info;
280     mMapper->populateDeviceInfo(info);
281 
282     std::list<NotifyArgs> args;
283     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 700);
284     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
285 
286     ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
287               mTelemetryLogCounts.end());
288 }
289 
TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest,RotationLogging,REQUIRES_FLAGS_ENABLED (ACONFIG_FLAG (com::android::input::flags,rotary_input_telemetry)))290 TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, RotationLogging,
291                   REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
292                                                       rotary_input_telemetry))) {
293     // 3 units per radian, 2 * M_PI * 3 = ~18.85 units per rotation.
294     // Multiples of `unitsPerRoation`, to easily follow the assertions below.
295     // [18.85, 37.7, 56.55, 75.4, 94.25, 113.1, 131.95, 150.8]
296     mPropertyMap.addProperty("device.res", "3");
297     mPropertyMap.addProperty("rotary_encoder.min_rotations_to_log", "2");
298 
299     mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
300                                                           mTelemetryLogCounter);
301     InputDeviceInfo info;
302     mMapper->populateDeviceInfo(info);
303 
304     std::list<NotifyArgs> args;
305     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 15); // total scroll = 15
306     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
307     ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
308               mTelemetryLogCounts.end());
309 
310     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 13); // total scroll = 28
311     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
312     // Expect 0 since `min_rotations_to_log` = 2, and total scroll 28 only has 1 rotation.
313     ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
314               mTelemetryLogCounts.end());
315 
316     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 10); // total scroll = 38
317     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
318     // Total scroll includes >= `min_rotations_to_log` (2), expect log.
319     ASSERT_EQ(mTelemetryLogCounts["input.value_rotary_input_device_full_rotation_count"], 2);
320 
321     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, -22); // total scroll = 60
322     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
323     // Expect no additional telemetry. Total rotation is 3, and total unlogged rotation is 1, which
324     // is less than `min_rotations_to_log`.
325     ASSERT_EQ(mTelemetryLogCounts["input.value_rotary_input_device_full_rotation_count"], 2);
326 
327     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, -16); // total scroll = 76
328     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
329     // Total unlogged rotation >= `min_rotations_to_log` (2), so expect 2 more logged rotation.
330     ASSERT_EQ(mTelemetryLogCounts["input.value_rotary_input_device_full_rotation_count"], 4);
331 
332     args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, -76); // total scroll = 152
333     args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
334     // Total unlogged scroll >= 4*`min_rotations_to_log`. Expect *all* unlogged rotations to be
335     // logged, even if that's more than multiple of `min_rotations_to_log`.
336     ASSERT_EQ(mTelemetryLogCounts["input.value_rotary_input_device_full_rotation_count"], 8);
337 }
338 
339 } // namespace android