blob: 157bee33e1b749241c2f382b1e74489b1955d4e8 [file] [log] [blame]
Vladimir Komsiyskidd438e22024-02-13 11:47:54 +01001/*
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>
Biswarup Palba27d1d2024-07-09 19:57:33 +000025#include <android_companion_virtualdevice_flags.h>
Yeabkal Wubshit58bda652024-09-24 20:22:18 -070026#include <com_android_input_flags.h>
27#include <flag_macros.h>
Vladimir Komsiyskidd438e22024-02-13 11:47:54 +010028#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
43namespace android {
44
45using testing::AllOf;
46using testing::Return;
47using testing::VariantWith;
48constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
49constexpr ui::LogicalDisplayId SECONDARY_DISPLAY_ID = ui::LogicalDisplayId{DISPLAY_ID.val() + 1};
50constexpr int32_t DISPLAY_WIDTH = 480;
51constexpr int32_t DISPLAY_HEIGHT = 800;
52
53namespace {
54
55DisplayViewport 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
68DisplayViewport createPrimaryViewport() {
69 DisplayViewport v = createViewport();
70 v.displayId = DISPLAY_ID;
71 v.uniqueId = "local:1";
72 return v;
73}
74
75DisplayViewport 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
Vladimir Komsiyskidd438e22024-02-13 11:47:54 +010083} // namespace
84
Biswarup Palba27d1d2024-07-09 19:57:33 +000085namespace vd_flags = android::companion::virtualdevice::flags;
86
Vladimir Komsiyskidd438e22024-02-13 11:47:54 +010087/**
88 * Unit tests for RotaryEncoderInputMapper.
89 */
90class RotaryEncoderInputMapperTest : public InputMapperUnitTest {
91protected:
92 void SetUp() override { SetUpWithBus(BUS_USB); }
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));
Biswarup Pal8ff5e5e2024-06-15 12:58:20 +0000100 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));
Vladimir Komsiyskidd438e22024-02-13 11:47:54 +0100104 }
Yeabkal Wubshit58bda652024-09-24 20:22:18 -0700105
Yeabkal Wubshit12c4ce62024-10-02 08:10:19 +0000106 std::map<std::string, int64_t> mTelemetryLogCounts;
Yeabkal Wubshit58bda652024-09-24 20:22:18 -0700107
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 =
113 [this](const char* key, int64_t value) { mTelemetryLogCounts[key] += value; };
Vladimir Komsiyskidd438e22024-02-13 11:47:54 +0100114};
115
116TEST_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.
Harry Cutts0b613dc2024-07-25 19:33:48 +0000122 EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(secondaryViewport));
123 mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration);
Vladimir Komsiyskidd438e22024-02-13 11:47:54 +0100124
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
136TEST_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.
Vladimir Komsiyskidd438e22024-02-13 11:47:54 +0100142 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
Biswarup Palba27d1d2024-07-09 19:57:33 +0000155TEST_F(RotaryEncoderInputMapperTest, ProcessRegularScroll) {
Biswarup Palba27d1d2024-07-09 19:57:33 +0000156 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
168TEST_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));
Biswarup Palba27d1d2024-07-09 19:57:33 +0000172 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
184TEST_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));
Biswarup Palba27d1d2024-07-09 19:57:33 +0000188 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
Yeabkal Wubshit58bda652024-09-24 20:22:18 -0700201TEST_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
218TEST_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
236TEST_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
254TEST_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
272TEST_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
290TEST_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
Vladimir Komsiyskidd438e22024-02-13 11:47:54 +0100339} // namespace android