• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.server.accessibility;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertTrue;
21 import static org.mockito.Mockito.mock;
22 
23 import android.content.Context;
24 import android.graphics.PointF;
25 import android.os.SystemClock;
26 import android.testing.DexmakerShareClassLoaderRule;
27 import android.util.DebugUtils;
28 import android.view.InputDevice;
29 import android.view.MotionEvent;
30 
31 import androidx.test.InstrumentationRegistry;
32 import androidx.test.runner.AndroidJUnit4;
33 
34 import org.junit.Before;
35 import org.junit.Rule;
36 import org.junit.Test;
37 import org.junit.runner.RunWith;
38 import org.mockito.ArgumentCaptor;
39 
40 import java.util.ArrayList;
41 import java.util.List;
42 
43 @RunWith(AndroidJUnit4.class)
44 public 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 
72     // mock package-private AccessibilityGestureDetector class
73     @Rule
74     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
75             new DexmakerShareClassLoaderRule();
76 
77     /**
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
onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)86         public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
87             mEvents.add(0, event.copy());
88         }
89 
90         @Override
setNext(EventStreamTransformation next)91         public void setNext(EventStreamTransformation next) {
92         }
93 
94         @Override
getNext()95         public EventStreamTransformation getNext() {
96             return null;
97         }
98     }
99 
100     @Before
setUp()101     public void setUp() {
102         Context context = InstrumentationRegistry.getContext();
103         AccessibilityManagerService ams = new AccessibilityManagerService(context);
104         AccessibilityGestureDetector detector = mock(AccessibilityGestureDetector.class);
105         mCaptor = new EventCaptor();
106         mTouchExplorer = new TouchExplorer(context, ams, detector);
107         mTouchExplorer.setNext(mCaptor);
108     }
109 
110     @Test
testTwoFingersMove_shouldDelegatingAndInjectActionDownPointerDown()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
testTwoFingersDrag_shouldDraggingAndActionDown()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
testTwoFingersNotDrag_shouldDelegatingAndActionUpDownPointerDown()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
testThreeFingersMove_shouldDelegatingAnd3ActionPointerDown()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 
fromTouchscreen(MotionEvent ev)156     private static MotionEvent fromTouchscreen(MotionEvent ev) {
157         ev.setSource(InputDevice.SOURCE_TOUCHSCREEN);
158         return ev;
159     }
160 
p(int x, int y)161     private static PointF p(int x, int y) {
162         return new PointF(x, y);
163     }
164 
stateToString(int state)165     private static String stateToString(int state) {
166         return DebugUtils.valueToString(TouchExplorerTest.class, "STATE_", state);
167     }
168 
goFromStateIdleTo(int state)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 
send(MotionEvent event)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 
assertState(int expect)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 
assertCapturedEvents(int... actionsInOrder)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 
assertCapturedEventsNoHistory()247     private void assertCapturedEventsNoHistory() {
248         for (MotionEvent e : getCapturedEvents()) {
249             assertEquals(0, e.getHistorySize());
250         }
251     }
252 
getCapturedEvent(int index)253     private MotionEvent getCapturedEvent(int index) {
254         return getCapturedEvents().get(index);
255     }
256 
getCapturedEvents()257     private List<MotionEvent> getCapturedEvents() {
258         return ((EventCaptor) mCaptor).mEvents;
259     }
260 
downEvent()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 
pointerDownEvent()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 
thirdPointerDownEvent()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 
moveEachPointers(MotionEvent event, PointF... points)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 
manyPointerEvent(int action, float[] x, float[] y)297     private MotionEvent manyPointerEvent(int action, float[] x, float[] y) {
298         return manyPointerEvent(action, x, y, mLastDownTime);
299     }
300 
manyPointerEvent(int action, float[] x, float[] y, long downTime)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 }
337