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