blob: 2645461b5912c8703a689f2067366c9001470d00 [file] [log] [blame]
Dieter Hsu01f426f2018-08-09 01:45:39 +08001/*
2 * Copyright (C) 2018 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
17package com.android.server.accessibility;
18
Dieter Hsu01f426f2018-08-09 01:45:39 +080019import static org.junit.Assert.assertEquals;
Dieter Hsua7fa8142018-08-29 19:48:42 +080020import static org.junit.Assert.assertTrue;
21import static org.mockito.Mockito.mock;
Dieter Hsu01f426f2018-08-09 01:45:39 +080022
23import android.content.Context;
24import android.graphics.PointF;
25import android.os.SystemClock;
Dieter Hsua7fa8142018-08-29 19:48:42 +080026import android.testing.DexmakerShareClassLoaderRule;
Dieter Hsu01f426f2018-08-09 01:45:39 +080027import android.util.DebugUtils;
28import android.view.InputDevice;
29import android.view.MotionEvent;
30
31import androidx.test.InstrumentationRegistry;
32import androidx.test.runner.AndroidJUnit4;
33
34import org.junit.Before;
Dieter Hsua7fa8142018-08-29 19:48:42 +080035import org.junit.Rule;
Dieter Hsu01f426f2018-08-09 01:45:39 +080036import org.junit.Test;
37import org.junit.runner.RunWith;
38import org.mockito.ArgumentCaptor;
39
40import java.util.ArrayList;
41import java.util.List;
42
43@RunWith(AndroidJUnit4.class)
44public class TouchExplorerTest {
45
46 public static final int STATE_TOUCH_EXPLORING = 0x00000001;
47 public static final int STATE_DRAGGING = 0x00000002;
48 public static final int STATE_DELEGATING = 0x00000004;
49
50 private static final int FLAG_1FINGER = 0x8000;
51 private static final int FLAG_2FINGERS = 0x0100;
52 private static final int FLAG_3FINGERS = 0x0200;
53 private static final int FLAG_MOVING = 0x00010000;
54 private static final int FLAG_MOVING_DIFF_DIRECTION = 0x00020000;
55
56 private static final int STATE_TOUCH_EXPLORING_1FINGER = STATE_TOUCH_EXPLORING | FLAG_1FINGER;
57 private static final int STATE_TOUCH_EXPLORING_2FINGER = STATE_TOUCH_EXPLORING | FLAG_2FINGERS;
58 private static final int STATE_TOUCH_EXPLORING_3FINGER = STATE_TOUCH_EXPLORING | FLAG_3FINGERS;
59 private static final int STATE_MOVING_2FINGERS = STATE_TOUCH_EXPLORING_2FINGER | FLAG_MOVING;
60 private static final int STATE_MOVING_3FINGERS = STATE_TOUCH_EXPLORING_3FINGER | FLAG_MOVING;
61 private static final int STATE_DRAGGING_2FINGERS = STATE_DRAGGING | FLAG_2FINGERS;
62 private static final int STATE_PINCH_2FINGERS =
63 STATE_TOUCH_EXPLORING_2FINGER | FLAG_MOVING_DIFF_DIRECTION;
64 private static final float DEFAULT_X = 301f;
65 private static final float DEFAULT_Y = 299f;
66
67 private EventStreamTransformation mCaptor;
68 private MotionEvent mLastEvent;
69 private TouchExplorer mTouchExplorer;
70 private long mLastDownTime = Integer.MIN_VALUE;
71
Dieter Hsua7fa8142018-08-29 19:48:42 +080072 // mock package-private AccessibilityGestureDetector class
73 @Rule
74 public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
75 new DexmakerShareClassLoaderRule();
76
Dieter Hsu01f426f2018-08-09 01:45:39 +080077 /**
78 * {@link TouchExplorer#sendDownForAllNotInjectedPointers} injecting events with the same object
79 * is resulting {@link ArgumentCaptor} to capture events with last state. Before implementation
80 * change, this helper class will save copies to verify the result.
81 */
82 private class EventCaptor implements EventStreamTransformation {
83 List<MotionEvent> mEvents = new ArrayList<>();
84
85 @Override
86 public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
87 mEvents.add(0, event.copy());
88 }
89
90 @Override
91 public void setNext(EventStreamTransformation next) {
92 }
93
94 @Override
95 public EventStreamTransformation getNext() {
96 return null;
97 }
98 }
99
100 @Before
101 public void setUp() {
102 Context context = InstrumentationRegistry.getContext();
103 AccessibilityManagerService ams = new AccessibilityManagerService(context);
Dieter Hsua7fa8142018-08-29 19:48:42 +0800104 AccessibilityGestureDetector detector = mock(AccessibilityGestureDetector.class);
Dieter Hsu01f426f2018-08-09 01:45:39 +0800105 mCaptor = new EventCaptor();
Dieter Hsua7fa8142018-08-29 19:48:42 +0800106 mTouchExplorer = new TouchExplorer(context, ams, detector);
Dieter Hsu01f426f2018-08-09 01:45:39 +0800107 mTouchExplorer.setNext(mCaptor);
108 }
109
110 @Test
111 public void testTwoFingersMove_shouldDelegatingAndInjectActionDownPointerDown() {
112 goFromStateIdleTo(STATE_MOVING_2FINGERS);
113
114 assertState(STATE_DELEGATING);
115 assertCapturedEvents(
116 MotionEvent.ACTION_DOWN,
117 MotionEvent.ACTION_POINTER_DOWN);
118 assertCapturedEventsNoHistory();
119 }
120
121 @Test
122 public void testTwoFingersDrag_shouldDraggingAndActionDown() {
123 goFromStateIdleTo(STATE_DRAGGING_2FINGERS);
124
125 assertState(STATE_DRAGGING);
126 assertCapturedEvents(MotionEvent.ACTION_DOWN);
127 assertCapturedEventsNoHistory();
128 }
129
130 @Test
131 public void testTwoFingersNotDrag_shouldDelegatingAndActionUpDownPointerDown() {
132 // only from dragging state, and withMoveHistory no dragging
133 goFromStateIdleTo(STATE_PINCH_2FINGERS);
134
135 assertState(STATE_DELEGATING);
136 assertCapturedEvents(
137 /* goto dragging state */ MotionEvent.ACTION_DOWN,
138 /* leave dragging state */ MotionEvent.ACTION_UP,
139 MotionEvent.ACTION_DOWN,
140 MotionEvent.ACTION_POINTER_DOWN);
141 assertCapturedEventsNoHistory();
142 }
143
144 @Test
145 public void testThreeFingersMove_shouldDelegatingAnd3ActionPointerDown() {
146 goFromStateIdleTo(STATE_MOVING_3FINGERS);
147
148 assertState(STATE_DELEGATING);
149 assertCapturedEvents(
150 MotionEvent.ACTION_DOWN,
151 MotionEvent.ACTION_POINTER_DOWN,
152 MotionEvent.ACTION_POINTER_DOWN);
153 assertCapturedEventsNoHistory();
154 }
155
156 private static MotionEvent fromTouchscreen(MotionEvent ev) {
157 ev.setSource(InputDevice.SOURCE_TOUCHSCREEN);
158 return ev;
159 }
160
161 private static PointF p(int x, int y) {
162 return new PointF(x, y);
163 }
164
165 private static String stateToString(int state) {
166 return DebugUtils.valueToString(TouchExplorerTest.class, "STATE_", state);
167 }
168
169 private void goFromStateIdleTo(int state) {
170 try {
171 switch (state) {
172 case STATE_TOUCH_EXPLORING: {
173 mTouchExplorer.onDestroy();
174 }
175 break;
176 case STATE_TOUCH_EXPLORING_1FINGER: {
177 goFromStateIdleTo(STATE_TOUCH_EXPLORING);
178 send(downEvent());
179 }
180 break;
181 case STATE_TOUCH_EXPLORING_2FINGER: {
182 goFromStateIdleTo(STATE_TOUCH_EXPLORING_1FINGER);
183 send(pointerDownEvent());
184 }
185 break;
186 case STATE_TOUCH_EXPLORING_3FINGER: {
187 goFromStateIdleTo(STATE_TOUCH_EXPLORING_2FINGER);
188 send(thirdPointerDownEvent());
189 }
190 break;
191 case STATE_MOVING_2FINGERS: {
192 goFromStateIdleTo(STATE_TOUCH_EXPLORING_2FINGER);
193 moveEachPointers(mLastEvent, p(10, 0), p(5, 10));
194 send(mLastEvent);
195 }
196 break;
197 case STATE_DRAGGING_2FINGERS: {
198 goFromStateIdleTo(STATE_TOUCH_EXPLORING_2FINGER);
199 moveEachPointers(mLastEvent, p(10, 0), p(10, 0));
200 send(mLastEvent);
201 }
202 break;
203 case STATE_PINCH_2FINGERS: {
204 goFromStateIdleTo(STATE_DRAGGING_2FINGERS);
205 moveEachPointers(mLastEvent, p(10, 0), p(-10, 1));
206 send(mLastEvent);
207 }
208 break;
209 case STATE_MOVING_3FINGERS: {
210 goFromStateIdleTo(STATE_TOUCH_EXPLORING_3FINGER);
211 moveEachPointers(mLastEvent, p(1, 0), p(1, 0), p(1, 0));
212 send(mLastEvent);
213 }
214 break;
215 default:
216 throw new IllegalArgumentException("Illegal state: " + state);
217 }
218 } catch (Throwable t) {
219 throw new RuntimeException("Failed to go to state " + stateToString(state), t);
220 }
221 }
222
223 private void send(MotionEvent event) {
224 final MotionEvent sendEvent = fromTouchscreen(event);
225 mLastEvent = sendEvent;
226 try {
227 mTouchExplorer.onMotionEvent(sendEvent, sendEvent, /* policyFlags */ 0);
228 } catch (Throwable t) {
229 throw new RuntimeException("Exception while handling " + sendEvent, t);
230 }
231 }
232
233 private void assertState(int expect) {
234 final String expectState = "STATE_" + stateToString(expect);
235 assertTrue(String.format("Expect state: %s, but: %s", expectState, mTouchExplorer),
236 mTouchExplorer.toString().contains(expectState));
237 }
238
239 private void assertCapturedEvents(int... actionsInOrder) {
240 final int eventCount = actionsInOrder.length;
241 assertEquals(eventCount, getCapturedEvents().size());
242 for (int i = 0; i < eventCount; i++) {
243 assertEquals(actionsInOrder[eventCount - i - 1], getCapturedEvent(i).getActionMasked());
244 }
245 }
246
247 private void assertCapturedEventsNoHistory() {
248 for (MotionEvent e : getCapturedEvents()) {
249 assertEquals(0, e.getHistorySize());
250 }
251 }
252
253 private MotionEvent getCapturedEvent(int index) {
254 return getCapturedEvents().get(index);
255 }
256
257 private List<MotionEvent> getCapturedEvents() {
258 return ((EventCaptor) mCaptor).mEvents;
259 }
260
261 private MotionEvent downEvent() {
262 mLastDownTime = SystemClock.uptimeMillis();
263 return fromTouchscreen(
264 MotionEvent.obtain(mLastDownTime, mLastDownTime, MotionEvent.ACTION_DOWN, DEFAULT_X,
265 DEFAULT_Y, 0));
266 }
267
268 private MotionEvent pointerDownEvent() {
269 final int secondPointerId = 0x0100;
270 final int action = MotionEvent.ACTION_POINTER_DOWN | secondPointerId;
271 final float[] x = new float[]{DEFAULT_X, DEFAULT_X + 29};
272 final float[] y = new float[]{DEFAULT_Y, DEFAULT_Y + 28};
273 return manyPointerEvent(action, x, y);
274 }
275
276 private MotionEvent thirdPointerDownEvent() {
277 final int thirdPointerId = 0x0200;
278 final int action = MotionEvent.ACTION_POINTER_DOWN | thirdPointerId;
279 final float[] x = new float[]{DEFAULT_X, DEFAULT_X + 29, DEFAULT_X + 59};
280 final float[] y = new float[]{DEFAULT_Y, DEFAULT_Y + 28, DEFAULT_Y + 58};
281 return manyPointerEvent(action, x, y);
282 }
283
284 private void moveEachPointers(MotionEvent event, PointF... points) {
285 final float[] x = new float[points.length];
286 final float[] y = new float[points.length];
287 for (int i = 0; i < points.length; i++) {
288 x[i] = event.getX(i) + points[i].x;
289 y[i] = event.getY(i) + points[i].y;
290 }
291 MotionEvent newEvent = manyPointerEvent(MotionEvent.ACTION_MOVE, x, y);
292 event.setAction(MotionEvent.ACTION_MOVE);
293 // add history count
294 event.addBatch(newEvent);
295 }
296
297 private MotionEvent manyPointerEvent(int action, float[] x, float[] y) {
298 return manyPointerEvent(action, x, y, mLastDownTime);
299 }
300
301 private MotionEvent manyPointerEvent(int action, float[] x, float[] y, long downTime) {
302 final int len = x.length;
303
304 final MotionEvent.PointerProperties[] pp = new MotionEvent.PointerProperties[len];
305 for (int i = 0; i < len; i++) {
306 MotionEvent.PointerProperties pointerProperty = new MotionEvent.PointerProperties();
307 pointerProperty.id = i;
308 pointerProperty.toolType = MotionEvent.TOOL_TYPE_FINGER;
309 pp[i] = pointerProperty;
310 }
311
312 final MotionEvent.PointerCoords[] pc = new MotionEvent.PointerCoords[len];
313 for (int i = 0; i < len; i++) {
314 MotionEvent.PointerCoords pointerCoord = new MotionEvent.PointerCoords();
315 pointerCoord.x = x[i];
316 pointerCoord.y = y[i];
317 pc[i] = pointerCoord;
318 }
319
320 return MotionEvent.obtain(
321 /* downTime */ SystemClock.uptimeMillis(),
322 /* eventTime */ downTime,
323 /* action */ action,
324 /* pointerCount */ pc.length,
325 /* pointerProperties */ pp,
326 /* pointerCoords */ pc,
327 /* metaState */ 0,
328 /* buttonState */ 0,
329 /* xPrecision */ 1.0f,
330 /* yPrecision */ 1.0f,
331 /* deviceId */ 0,
332 /* edgeFlags */ 0,
333 /* source */ InputDevice.SOURCE_TOUCHSCREEN,
334 /* flags */ 0);
335 }
336}