blob: 70f40aa028952bd6cf318e1ea0e3da97dce25868 [file] [log] [blame]
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -08001/*
2 * Copyright (C) 2022 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 <gtest/gtest.h>
18#include "../PreferStylusOverTouchBlocker.h"
19
20namespace android {
21
22constexpr int32_t TOUCH_DEVICE_ID = 3;
23constexpr int32_t STYLUS_DEVICE_ID = 4;
24
25constexpr int DOWN = AMOTION_EVENT_ACTION_DOWN;
26constexpr int MOVE = AMOTION_EVENT_ACTION_MOVE;
27constexpr int UP = AMOTION_EVENT_ACTION_UP;
28constexpr int CANCEL = AMOTION_EVENT_ACTION_CANCEL;
29constexpr int32_t TOUCHSCREEN = AINPUT_SOURCE_TOUCHSCREEN;
30constexpr int32_t STYLUS = AINPUT_SOURCE_STYLUS;
31
32struct PointerData {
33 float x;
34 float y;
35};
36
37static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, int32_t action,
38 const std::vector<PointerData>& points,
39 uint32_t source) {
40 size_t pointerCount = points.size();
41 if (action == DOWN || action == UP) {
42 EXPECT_EQ(1U, pointerCount) << "Actions DOWN and UP can only contain a single pointer";
43 }
44
45 PointerProperties pointerProperties[pointerCount];
46 PointerCoords pointerCoords[pointerCount];
47
48 const int32_t deviceId = isFromSource(source, TOUCHSCREEN) ? TOUCH_DEVICE_ID : STYLUS_DEVICE_ID;
49 const int32_t toolType = isFromSource(source, TOUCHSCREEN) ? AMOTION_EVENT_TOOL_TYPE_FINGER
50 : AMOTION_EVENT_TOOL_TYPE_STYLUS;
51 for (size_t i = 0; i < pointerCount; i++) {
52 pointerProperties[i].clear();
53 pointerProperties[i].id = i;
54 pointerProperties[i].toolType = toolType;
55
56 pointerCoords[i].clear();
57 pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, points[i].x);
58 pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, points[i].y);
59 }
60
61 // Currently, can't have STYLUS source without it also being a TOUCH source. Update the source
62 // accordingly.
63 if (isFromSource(source, STYLUS)) {
64 source |= TOUCHSCREEN;
65 }
66
67 // Define a valid motion event.
68 NotifyMotionArgs args(/* id */ 0, eventTime, 0 /*readTime*/, deviceId, source, 0 /*displayId*/,
69 POLICY_FLAG_PASS_TO_USER, action, /* actionButton */ 0,
70 /* flags */ 0, AMETA_NONE, /* buttonState */ 0,
71 MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
72 pointerProperties, pointerCoords, /* xPrecision */ 0, /* yPrecision */ 0,
73 AMOTION_EVENT_INVALID_CURSOR_POSITION,
74 AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, /* videoFrames */ {});
75
76 return args;
77}
78
79class PreferStylusOverTouchTest : public testing::Test {
80protected:
81 void assertNotBlocked(const NotifyMotionArgs& args) {
82 ftl::StaticVector<NotifyMotionArgs, 2> processedArgs = mBlocker.processMotion(args);
83 ASSERT_EQ(1u, processedArgs.size());
84 ASSERT_EQ(args, processedArgs[0]);
85 }
86
87 void assertDropped(const NotifyMotionArgs& args) {
88 ftl::StaticVector<NotifyMotionArgs, 2> processedArgs = mBlocker.processMotion(args);
89 ASSERT_TRUE(processedArgs.empty());
90 }
91
92 void assertCanceled(const NotifyMotionArgs& args,
93 std::optional<NotifyMotionArgs> canceledArgs) {
94 ftl::StaticVector<NotifyMotionArgs, 2> processedArgs = mBlocker.processMotion(args);
95 ASSERT_EQ(2u, processedArgs.size());
96 NotifyMotionArgs& cancelEvent = processedArgs[0];
97 ASSERT_EQ(CANCEL, cancelEvent.action);
98 ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, cancelEvent.flags & AMOTION_EVENT_FLAG_CANCELED);
99 ASSERT_TRUE(isFromSource(cancelEvent.source, TOUCHSCREEN));
100 ASSERT_FALSE(isFromSource(cancelEvent.source, STYLUS));
101
102 ASSERT_EQ(args, processedArgs[1]);
103 }
104
105private:
106 PreferStylusOverTouchBlocker mBlocker;
107};
108
109TEST_F(PreferStylusOverTouchTest, TouchGestureIsNotBlocked) {
110 NotifyMotionArgs args;
111
112 args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
113 assertNotBlocked(args);
114
115 args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
116 assertNotBlocked(args);
117
118 args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
119 assertNotBlocked(args);
120}
121
122TEST_F(PreferStylusOverTouchTest, StylusGestureIsNotBlocked) {
123 NotifyMotionArgs args;
124
125 args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, STYLUS);
126 assertNotBlocked(args);
127
128 args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, STYLUS);
129 assertNotBlocked(args);
130
131 args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, STYLUS);
132 assertNotBlocked(args);
133}
134
135/**
136 * Existing touch gesture should be canceled when stylus goes down. There should be an ACTION_CANCEL
137 * event generated.
138 */
139TEST_F(PreferStylusOverTouchTest, TouchIsCanceledWhenStylusGoesDown) {
140 NotifyMotionArgs args;
141
142 args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
143 assertNotBlocked(args);
144
145 args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
146 assertNotBlocked(args);
147
148 args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
149 NotifyMotionArgs cancelArgs =
150 generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, CANCEL, {{1, 3}}, TOUCHSCREEN);
151 assertCanceled(args, cancelArgs);
152
153 // Both stylus and touch events continue. Stylus should be not blocked, and touch should be
154 // blocked
155 args = generateMotionArgs(3 /*downTime*/, 4 /*eventTime*/, MOVE, {{10, 31}}, STYLUS);
156 assertNotBlocked(args);
157
158 args = generateMotionArgs(0 /*downTime*/, 5 /*eventTime*/, MOVE, {{1, 4}}, TOUCHSCREEN);
159 assertDropped(args);
160}
161
162/**
163 * New touch events should be simply blocked (dropped) when stylus is down. No CANCEL event should
164 * be generated.
165 */
166TEST_F(PreferStylusOverTouchTest, NewTouchIsBlockedWhenStylusIsDown) {
167 NotifyMotionArgs args;
168 constexpr nsecs_t stylusDownTime = 0;
169 constexpr nsecs_t touchDownTime = 1;
170
171 args = generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
172 assertNotBlocked(args);
173
174 args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
175 assertDropped(args);
176
177 // Stylus should continue to work
178 args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, MOVE, {{10, 31}}, STYLUS);
179 assertNotBlocked(args);
180
181 // Touch should continue to be blocked
182 args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
183 assertDropped(args);
184
185 args = generateMotionArgs(0 /*downTime*/, 5 /*eventTime*/, MOVE, {{1, 4}}, TOUCHSCREEN);
186 assertDropped(args);
187}
188
189/**
190 * New touch events should be simply blocked (dropped) when stylus is down. No CANCEL event should
191 * be generated.
192 */
193TEST_F(PreferStylusOverTouchTest, NewTouchWorksAfterStylusIsLifted) {
194 NotifyMotionArgs args;
195 constexpr nsecs_t stylusDownTime = 0;
196 constexpr nsecs_t touchDownTime = 4;
197
198 // Stylus goes down and up
199 args = generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
200 assertNotBlocked(args);
201
202 args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, MOVE, {{10, 31}}, STYLUS);
203 assertNotBlocked(args);
204
205 args = generateMotionArgs(stylusDownTime, 3 /*eventTime*/, UP, {{10, 31}}, STYLUS);
206 assertNotBlocked(args);
207
208 // New touch goes down. It should not be blocked
209 args = generateMotionArgs(touchDownTime, touchDownTime, DOWN, {{1, 2}}, TOUCHSCREEN);
210 assertNotBlocked(args);
211
212 args = generateMotionArgs(touchDownTime, 5 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
213 assertNotBlocked(args);
214
215 args = generateMotionArgs(touchDownTime, 6 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
216 assertNotBlocked(args);
217}
218
219/**
220 * Once a touch gesture is canceled, it should continue to be canceled, even if the stylus has been
221 * lifted.
222 */
223TEST_F(PreferStylusOverTouchTest, AfterStylusIsLiftedCurrentTouchIsBlocked) {
224 NotifyMotionArgs args;
225 constexpr nsecs_t stylusDownTime = 0;
226 constexpr nsecs_t touchDownTime = 1;
227
228 assertNotBlocked(generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS));
229
230 args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
231 assertDropped(args);
232
233 // Lift the stylus
234 args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, UP, {{10, 30}}, STYLUS);
235 assertNotBlocked(args);
236
237 // Touch should continue to be blocked
238 args = generateMotionArgs(touchDownTime, 3 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
239 assertDropped(args);
240
241 args = generateMotionArgs(touchDownTime, 4 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
242 assertDropped(args);
243
244 // New touch should go through, though.
245 constexpr nsecs_t newTouchDownTime = 5;
246 args = generateMotionArgs(newTouchDownTime, 5 /*eventTime*/, DOWN, {{10, 20}}, TOUCHSCREEN);
247 assertNotBlocked(args);
248}
249
250} // namespace android