blob: 2007d4fff8c1752cbde7f00a4f4fdb06578b9efc [file] [log] [blame]
Eugene Susla4f8680b2017-08-07 17:25:30 -07001/*
2 * Copyright (C) 2017 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
Eugene Susla4f8680b2017-08-07 17:25:30 -070019import static android.view.MotionEvent.ACTION_DOWN;
20import static android.view.MotionEvent.ACTION_MOVE;
21import static android.view.MotionEvent.ACTION_POINTER_DOWN;
22import static android.view.MotionEvent.ACTION_POINTER_UP;
23
24import static com.android.server.testutils.TestUtils.strictMock;
25
Eugene Susla8f07ee12017-11-14 17:41:03 -080026import static org.junit.Assert.assertFalse;
27import static org.junit.Assert.assertTrue;
Eugene Susla4f8680b2017-08-07 17:25:30 -070028import static org.junit.Assert.fail;
Rhed Jao02655dc2018-10-30 20:44:52 +080029import static org.mockito.ArgumentMatchers.eq;
Eugene Susla4f8680b2017-08-07 17:25:30 -070030import static org.mockito.Matchers.any;
31import static org.mockito.Matchers.anyInt;
32import static org.mockito.Mockito.doNothing;
sanryhuang95d6d822018-10-17 10:00:53 +080033import static org.mockito.Mockito.mock;
Eugene Susla4f8680b2017-08-07 17:25:30 -070034import static org.mockito.Mockito.times;
35import static org.mockito.Mockito.verify;
Rhed Jao02655dc2018-10-30 20:44:52 +080036import static org.mockito.Mockito.when;
Eugene Susla4f8680b2017-08-07 17:25:30 -070037
sanryhuang95d6d822018-10-17 10:00:53 +080038import android.animation.ValueAnimator;
Eugene Susla4f8680b2017-08-07 17:25:30 -070039import android.annotation.NonNull;
40import android.content.Context;
Rhed Jao02655dc2018-10-30 20:44:52 +080041import android.os.Handler;
Eugene Suslafe87bce2018-01-02 17:50:14 -080042import android.os.Message;
Eugene Susla4f8680b2017-08-07 17:25:30 -070043import android.util.DebugUtils;
44import android.view.InputDevice;
45import android.view.MotionEvent;
46
Brett Chabota26eda92018-07-23 13:08:30 -070047import androidx.test.InstrumentationRegistry;
48import androidx.test.runner.AndroidJUnit4;
49
Eugene Susla4f8680b2017-08-07 17:25:30 -070050import com.android.server.testutils.OffsettableClock;
51import com.android.server.testutils.TestHandler;
sanryhuang95d6d822018-10-17 10:00:53 +080052import com.android.server.wm.WindowManagerInternal;
Eugene Susla4f8680b2017-08-07 17:25:30 -070053
sanryhuang95d6d822018-10-17 10:00:53 +080054import org.junit.After;
Eugene Susla4f8680b2017-08-07 17:25:30 -070055import org.junit.Before;
56import org.junit.Test;
57import org.junit.runner.RunWith;
58
59import java.util.function.IntConsumer;
60
Eugene Suslafcf5acd2018-01-03 14:02:57 -080061/**
ryanlwlin62592982019-12-24 11:19:20 +080062 * Tests the state transitions of {@link FullScreenMagnificationGestureHandler}
Eugene Suslafcf5acd2018-01-03 14:02:57 -080063 *
64 * Here's a dot graph describing the transitions being tested:
65 * {@code
66 * digraph {
67 * IDLE -> SHORTCUT_TRIGGERED [label="a11y\nbtn"]
68 * SHORTCUT_TRIGGERED -> IDLE [label="a11y\nbtn"]
69 * IDLE -> DOUBLE_TAP [label="2tap"]
70 * DOUBLE_TAP -> IDLE [label="timeout"]
71 * DOUBLE_TAP -> TRIPLE_TAP_AND_HOLD [label="down"]
72 * SHORTCUT_TRIGGERED -> TRIPLE_TAP_AND_HOLD [label="down"]
73 * TRIPLE_TAP_AND_HOLD -> ZOOMED [label="up"]
74 * TRIPLE_TAP_AND_HOLD -> DRAGGING_TMP [label="hold/\nswipe"]
75 * DRAGGING_TMP -> IDLE [label="release"]
76 * ZOOMED -> ZOOMED_DOUBLE_TAP [label="2tap"]
77 * ZOOMED_DOUBLE_TAP -> ZOOMED [label="timeout"]
78 * ZOOMED_DOUBLE_TAP -> DRAGGING [label="hold"]
79 * ZOOMED_DOUBLE_TAP -> IDLE [label="tap"]
80 * DRAGGING -> ZOOMED [label="release"]
81 * ZOOMED -> IDLE [label="a11y\nbtn"]
82 * ZOOMED -> PANNING [label="2hold"]
83 * PANNING -> PANNING_SCALING [label="pinch"]
84 * PANNING_SCALING -> ZOOMED [label="release"]
85 * PANNING -> ZOOMED [label="release"]
86 * }
87 * }
88 */
Eugene Susla4f8680b2017-08-07 17:25:30 -070089@RunWith(AndroidJUnit4.class)
ryanlwlin62592982019-12-24 11:19:20 +080090public class FullScreenMagnificationGestureHandlerTest {
Eugene Susla4f8680b2017-08-07 17:25:30 -070091
92 public static final int STATE_IDLE = 1;
93 public static final int STATE_ZOOMED = 2;
94 public static final int STATE_2TAPS = 3;
95 public static final int STATE_ZOOMED_2TAPS = 4;
96 public static final int STATE_SHORTCUT_TRIGGERED = 5;
97 public static final int STATE_DRAGGING_TMP = 6;
98 public static final int STATE_DRAGGING = 7;
99 public static final int STATE_PANNING = 8;
100 public static final int STATE_SCALING_AND_PANNING = 9;
101
102
103 public static final int FIRST_STATE = STATE_IDLE;
104 public static final int LAST_STATE = STATE_SCALING_AND_PANNING;
105
106 // Co-prime x and y, to potentially catch x-y-swapped errors
107 public static final float DEFAULT_X = 301;
108 public static final float DEFAULT_Y = 299;
109
Rhed Jao02655dc2018-10-30 20:44:52 +0800110 private static final int DISPLAY_0 = 0;
111
Eugene Susla4f8680b2017-08-07 17:25:30 -0700112 private Context mContext;
sanryhuang95d6d822018-10-17 10:00:53 +0800113 MagnificationController mMagnificationController;
114
Eugene Susla4f8680b2017-08-07 17:25:30 -0700115 private OffsettableClock mClock;
ryanlwlin62592982019-12-24 11:19:20 +0800116 private FullScreenMagnificationGestureHandler mMgh;
Eugene Susla4f8680b2017-08-07 17:25:30 -0700117 private TestHandler mHandler;
118
Eugene Suslafe87bce2018-01-02 17:50:14 -0800119 private long mLastDownTime = Integer.MIN_VALUE;
120
Eugene Susla4f8680b2017-08-07 17:25:30 -0700121 @Before
122 public void setUp() {
123 mContext = InstrumentationRegistry.getContext();
Rhed Jao02655dc2018-10-30 20:44:52 +0800124 final MagnificationController.ControllerContext mockController =
125 mock(MagnificationController.ControllerContext.class);
126 final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
127 when(mockController.getContext()).thenReturn(mContext);
128 when(mockController.getAms()).thenReturn(mock(AccessibilityManagerService.class));
129 when(mockController.getWindowManager()).thenReturn(mockWindowManager);
130 when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper()));
131 when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
132 when(mockController.getAnimationDuration()).thenReturn(1000L);
133 when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true);
134 mMagnificationController = new MagnificationController(mockController, new Object()) {
Eugene Susla4f8680b2017-08-07 17:25:30 -0700135 @Override
Rhed Jao02655dc2018-10-30 20:44:52 +0800136 public boolean magnificationRegionContains(int displayId, float x, float y) {
Eugene Susla4f8680b2017-08-07 17:25:30 -0700137 return true;
138 }
139
140 @Override
Rhed Jao02655dc2018-10-30 20:44:52 +0800141 void setForceShowMagnifiableBounds(int displayId, boolean show) {}
Eugene Susla4f8680b2017-08-07 17:25:30 -0700142 };
Rhed Jao02655dc2018-10-30 20:44:52 +0800143 mMagnificationController.register(DISPLAY_0);
Eugene Susla4f8680b2017-08-07 17:25:30 -0700144 mClock = new OffsettableClock.Stopped();
145
146 boolean detectTripleTap = true;
147 boolean detectShortcutTrigger = true;
148 mMgh = newInstance(detectTripleTap, detectShortcutTrigger);
149 }
150
sanryhuang95d6d822018-10-17 10:00:53 +0800151 @After
152 public void tearDown() {
Rhed Jao02655dc2018-10-30 20:44:52 +0800153 mMagnificationController.unregister(DISPLAY_0);
sanryhuang95d6d822018-10-17 10:00:53 +0800154 }
155
Eugene Susla4f8680b2017-08-07 17:25:30 -0700156 @NonNull
ryanlwlin62592982019-12-24 11:19:20 +0800157 private FullScreenMagnificationGestureHandler newInstance(boolean detectTripleTap,
Eugene Susla4f8680b2017-08-07 17:25:30 -0700158 boolean detectShortcutTrigger) {
ryanlwlin62592982019-12-24 11:19:20 +0800159 FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler(
Eugene Susla4f8680b2017-08-07 17:25:30 -0700160 mContext, mMagnificationController,
Jackal Guod6e8dba2019-01-24 14:37:31 +0800161 detectTripleTap, detectShortcutTrigger, DISPLAY_0);
Eugene Suslafe87bce2018-01-02 17:50:14 -0800162 mHandler = new TestHandler(h.mDetectingState, mClock) {
163 @Override
164 protected String messageToString(Message m) {
165 return DebugUtils.valueToString(
ryanlwlin62592982019-12-24 11:19:20 +0800166 FullScreenMagnificationGestureHandler.DetectingState.class, "MESSAGE_",
167 m.what);
Eugene Suslafe87bce2018-01-02 17:50:14 -0800168 }
169 };
Eugene Susla728354b2017-09-25 17:09:52 -0700170 h.mDetectingState.mHandler = mHandler;
Eugene Susla4f8680b2017-08-07 17:25:30 -0700171 h.setNext(strictMock(EventStreamTransformation.class));
172 return h;
173 }
174
175 @Test
176 public void testInitialState_isIdle() {
177 assertIn(STATE_IDLE);
178 }
179
180 /**
181 * Covers paths to get to and back between each state and {@link #STATE_IDLE}
182 * This navigates between states using "canonical" paths, specified in
183 * {@link #goFromStateIdleTo} (for traversing away from {@link #STATE_IDLE}) and
184 * {@link #returnToNormalFrom} (for navigating back to {@link #STATE_IDLE})
185 */
186 @Test
187 public void testEachState_isReachableAndRecoverable() {
188 forEachState(state -> {
189 goFromStateIdleTo(state);
190 assertIn(state);
191
192 returnToNormalFrom(state);
193 try {
194 assertIn(STATE_IDLE);
195 } catch (AssertionError e) {
196 throw new AssertionError("Failed while testing state " + stateToString(state), e);
197 }
198 });
199 }
200
201 @Test
202 public void testStates_areMutuallyExclusive() {
203 forEachState(state1 -> {
204 forEachState(state2 -> {
205 if (state1 < state2) {
206 goFromStateIdleTo(state1);
207 try {
208 assertIn(state2);
209 fail("State " + stateToString(state1) + " also implies state "
210 + stateToString(state2) + stateDump());
211 } catch (AssertionError e) {
212 // expected
213 returnToNormalFrom(state1);
214 }
215 }
216 });
217 });
218 }
219
Eugene Susla8f07ee12017-11-14 17:41:03 -0800220 @Test
221 public void testTransitionToDelegatingStateAndClear_preservesShortcutTriggeredState() {
222 mMgh.mDetectingState.transitionToDelegatingStateAndClear();
223 assertFalse(mMgh.mDetectingState.mShortcutTriggered);
224
225 goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
226 mMgh.mDetectingState.transitionToDelegatingStateAndClear();
227 assertTrue(mMgh.mDetectingState.mShortcutTriggered);
228 }
229
Eugene Susla4f8680b2017-08-07 17:25:30 -0700230 /**
231 * Covers edges of the graph not covered by "canonical" transitions specified in
232 * {@link #goFromStateIdleTo} and {@link #returnToNormalFrom}
233 */
234 @SuppressWarnings("Convert2MethodRef")
235 @Test
236 public void testAlternativeTransitions_areWorking() {
237 // A11y button followed by a tap&hold turns temporary "viewport dragging" zoom on
238 assertTransition(STATE_SHORTCUT_TRIGGERED, () -> {
239 send(downEvent());
240 fastForward1sec();
241 }, STATE_DRAGGING_TMP);
242
243 // A11y button followed by a tap turns zoom on
244 assertTransition(STATE_SHORTCUT_TRIGGERED, () -> tap(), STATE_ZOOMED);
245
246 // A11y button pressed second time negates the 1st press
247 assertTransition(STATE_SHORTCUT_TRIGGERED, () -> triggerShortcut(), STATE_IDLE);
248
249 // A11y button turns zoom off
250 assertTransition(STATE_ZOOMED, () -> triggerShortcut(), STATE_IDLE);
251
252
253 // Double tap times out while zoomed
254 assertTransition(STATE_ZOOMED_2TAPS, () -> {
255 allowEventDelegation();
256 fastForward1sec();
257 }, STATE_ZOOMED);
258
Eugene Suslafe87bce2018-01-02 17:50:14 -0800259 // tap+tap+swipe doesn't get delegated
260 assertTransition(STATE_2TAPS, () -> swipe(), STATE_IDLE);
261
262 // tap+tap+swipe initiates viewport dragging immediately
263 assertTransition(STATE_2TAPS, () -> swipeAndHold(), STATE_DRAGGING_TMP);
Eugene Susla4f8680b2017-08-07 17:25:30 -0700264 }
265
266 @Test
267 public void testNonTransitions_dontChangeState() {
268 // ACTION_POINTER_DOWN triggers event delegation if not magnifying
269 assertStaysIn(STATE_IDLE, () -> {
270 allowEventDelegation();
271 send(downEvent());
272 send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
273 });
274
275 // Long tap breaks the triple-tap detection sequence
276 Runnable tapAndLongTap = () -> {
277 allowEventDelegation();
278 tap();
279 longTap();
280 };
281 assertStaysIn(STATE_IDLE, tapAndLongTap);
282 assertStaysIn(STATE_ZOOMED, tapAndLongTap);
283
284 // Triple tap with delays in between doesn't count
285 Runnable slow3tap = () -> {
286 tap();
287 fastForward1sec();
288 tap();
289 fastForward1sec();
290 tap();
291 };
292 assertStaysIn(STATE_IDLE, slow3tap);
293 assertStaysIn(STATE_ZOOMED, slow3tap);
294 }
295
296 @Test
297 public void testDisablingTripleTap_removesInputLag() {
298 mMgh = newInstance(/* detect3tap */ false, /* detectShortcut */ true);
299 goFromStateIdleTo(STATE_IDLE);
300 allowEventDelegation();
301 tap();
302 // no fast forward
Eugene Susla728354b2017-09-25 17:09:52 -0700303 verify(mMgh.getNext(), times(2)).onMotionEvent(any(), any(), anyInt());
Eugene Susla4f8680b2017-08-07 17:25:30 -0700304 }
305
Eugene Susla5210e942018-02-14 12:59:29 -0800306 @Test
307 public void testTripleTapAndHold_zoomsImmediately() {
308 assertZoomsImmediatelyOnSwipeFrom(STATE_2TAPS);
309 assertZoomsImmediatelyOnSwipeFrom(STATE_SHORTCUT_TRIGGERED);
310 }
311
Rhed Jao0232d292019-01-22 14:40:48 +0800312 @Test
313 public void testMultiTap_outOfDistanceSlop_shouldInIdle() {
314 // All delay motion events should be sent, if multi-tap with out of distance slop.
315 // STATE_IDLE will check if tapCount() < 2.
316 allowEventDelegation();
317 assertStaysIn(STATE_IDLE, () -> {
318 tap();
319 tap(DEFAULT_X * 2, DEFAULT_Y * 2);
320 });
321 assertStaysIn(STATE_IDLE, () -> {
322 tap();
323 tap(DEFAULT_X * 2, DEFAULT_Y * 2);
324 tap();
325 tap(DEFAULT_X * 2, DEFAULT_Y * 2);
326 tap();
327 });
328 }
329
Eugene Susla5210e942018-02-14 12:59:29 -0800330 private void assertZoomsImmediatelyOnSwipeFrom(int state) {
331 goFromStateIdleTo(state);
332 swipeAndHold();
333 assertIn(STATE_DRAGGING_TMP);
334 returnToNormalFrom(STATE_DRAGGING_TMP);
335 }
336
Eugene Susla4f8680b2017-08-07 17:25:30 -0700337 private void assertTransition(int fromState, Runnable transitionAction, int toState) {
338 goFromStateIdleTo(fromState);
339 transitionAction.run();
340 assertIn(toState);
341 returnToNormalFrom(toState);
342 }
343
344 private void assertStaysIn(int state, Runnable action) {
345 assertTransition(state, action, state);
346 }
347
348 private void forEachState(IntConsumer action) {
349 for (int state = FIRST_STATE; state <= LAST_STATE; state++) {
350 action.accept(state);
351 }
352 }
353
354 private void allowEventDelegation() {
Eugene Susla728354b2017-09-25 17:09:52 -0700355 doNothing().when(mMgh.getNext()).onMotionEvent(any(), any(), anyInt());
Eugene Susla4f8680b2017-08-07 17:25:30 -0700356 }
357
358 private void fastForward1sec() {
359 fastForward(1000);
360 }
361
362 private void fastForward(int ms) {
363 mClock.fastForward(ms);
364 mHandler.timeAdvance();
365 }
366
367 /**
368 * Asserts that {@link #mMgh the handler} is in the given {@code state}
369 */
370 private void assertIn(int state) {
371 switch (state) {
372
373 // Asserts on separate lines for accurate stack traces
374
375 case STATE_IDLE: {
376 check(tapCount() < 2, state);
Eugene Susla728354b2017-09-25 17:09:52 -0700377 check(!mMgh.mDetectingState.mShortcutTriggered, state);
Eugene Susla4f8680b2017-08-07 17:25:30 -0700378 check(!isZoomed(), state);
379 } break;
380 case STATE_ZOOMED: {
381 check(isZoomed(), state);
382 check(tapCount() < 2, state);
383 } break;
384 case STATE_2TAPS: {
385 check(!isZoomed(), state);
386 check(tapCount() == 2, state);
387 } break;
388 case STATE_ZOOMED_2TAPS: {
389 check(isZoomed(), state);
390 check(tapCount() == 2, state);
391 } break;
392 case STATE_DRAGGING: {
Eugene Susla5210e942018-02-14 12:59:29 -0800393 check(isZoomed(), state);
Eugene Susla728354b2017-09-25 17:09:52 -0700394 check(mMgh.mCurrentState == mMgh.mViewportDraggingState,
Eugene Susla4f8680b2017-08-07 17:25:30 -0700395 state);
Eugene Susla728354b2017-09-25 17:09:52 -0700396 check(mMgh.mViewportDraggingState.mZoomedInBeforeDrag, state);
Eugene Susla4f8680b2017-08-07 17:25:30 -0700397 } break;
398 case STATE_DRAGGING_TMP: {
Eugene Susla5210e942018-02-14 12:59:29 -0800399 check(isZoomed(), state);
Eugene Susla728354b2017-09-25 17:09:52 -0700400 check(mMgh.mCurrentState == mMgh.mViewportDraggingState,
Eugene Susla4f8680b2017-08-07 17:25:30 -0700401 state);
Eugene Susla728354b2017-09-25 17:09:52 -0700402 check(!mMgh.mViewportDraggingState.mZoomedInBeforeDrag, state);
Eugene Susla4f8680b2017-08-07 17:25:30 -0700403 } break;
404 case STATE_SHORTCUT_TRIGGERED: {
Eugene Susla728354b2017-09-25 17:09:52 -0700405 check(mMgh.mDetectingState.mShortcutTriggered, state);
Eugene Susla4f8680b2017-08-07 17:25:30 -0700406 check(!isZoomed(), state);
407 } break;
408 case STATE_PANNING: {
Eugene Susla5210e942018-02-14 12:59:29 -0800409 check(isZoomed(), state);
Eugene Susla728354b2017-09-25 17:09:52 -0700410 check(mMgh.mCurrentState == mMgh.mPanningScalingState,
Eugene Susla4f8680b2017-08-07 17:25:30 -0700411 state);
Eugene Susla728354b2017-09-25 17:09:52 -0700412 check(!mMgh.mPanningScalingState.mScaling, state);
Eugene Susla4f8680b2017-08-07 17:25:30 -0700413 } break;
414 case STATE_SCALING_AND_PANNING: {
Eugene Susla5210e942018-02-14 12:59:29 -0800415 check(isZoomed(), state);
Eugene Susla728354b2017-09-25 17:09:52 -0700416 check(mMgh.mCurrentState == mMgh.mPanningScalingState,
Eugene Susla4f8680b2017-08-07 17:25:30 -0700417 state);
Eugene Susla728354b2017-09-25 17:09:52 -0700418 check(mMgh.mPanningScalingState.mScaling, state);
Eugene Susla4f8680b2017-08-07 17:25:30 -0700419 } break;
420 default: throw new IllegalArgumentException("Illegal state: " + state);
421 }
422 }
423
424 /**
425 * Defines a "canonical" path from {@link #STATE_IDLE} to {@code state}
426 */
427 private void goFromStateIdleTo(int state) {
428 try {
429 switch (state) {
430 case STATE_IDLE: {
431 mMgh.clearAndTransitionToStateDetecting();
432 } break;
433 case STATE_2TAPS: {
434 goFromStateIdleTo(STATE_IDLE);
435 tap();
436 tap();
437 } break;
438 case STATE_ZOOMED: {
439 if (mMgh.mDetectTripleTap) {
440 goFromStateIdleTo(STATE_2TAPS);
441 tap();
442 } else {
443 goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
444 tap();
445 }
446 } break;
447 case STATE_ZOOMED_2TAPS: {
448 goFromStateIdleTo(STATE_ZOOMED);
449 tap();
450 tap();
451 } break;
452 case STATE_DRAGGING: {
453 goFromStateIdleTo(STATE_ZOOMED_2TAPS);
454 send(downEvent());
455 fastForward1sec();
456 } break;
457 case STATE_DRAGGING_TMP: {
458 goFromStateIdleTo(STATE_2TAPS);
459 send(downEvent());
460 fastForward1sec();
461 } break;
462 case STATE_SHORTCUT_TRIGGERED: {
463 goFromStateIdleTo(STATE_IDLE);
464 triggerShortcut();
465 } break;
466 case STATE_PANNING: {
467 goFromStateIdleTo(STATE_ZOOMED);
468 send(downEvent());
469 send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
470 } break;
471 case STATE_SCALING_AND_PANNING: {
472 goFromStateIdleTo(STATE_PANNING);
473 send(pointerEvent(ACTION_MOVE, DEFAULT_X * 2, DEFAULT_Y * 3));
474 send(pointerEvent(ACTION_MOVE, DEFAULT_X * 2, DEFAULT_Y * 4));
475 } break;
476 default:
477 throw new IllegalArgumentException("Illegal state: " + state);
478 }
479 } catch (Throwable t) {
480 throw new RuntimeException("Failed to go to state " + stateToString(state), t);
481 }
482 }
483
484 /**
485 * Defines a "canonical" path from {@code state} to {@link #STATE_IDLE}
486 */
487 private void returnToNormalFrom(int state) {
488 switch (state) {
489 case STATE_IDLE: {
490 // no op
491 } break;
492 case STATE_2TAPS: {
493 allowEventDelegation();
494 fastForward1sec();
495 } break;
496 case STATE_ZOOMED: {
497 if (mMgh.mDetectTripleTap) {
498 tap();
499 tap();
500 returnToNormalFrom(STATE_ZOOMED_2TAPS);
501 } else {
502 triggerShortcut();
503 }
504 } break;
505 case STATE_ZOOMED_2TAPS: {
506 tap();
507 } break;
508 case STATE_DRAGGING: {
509 send(upEvent());
510 returnToNormalFrom(STATE_ZOOMED);
511 } break;
512 case STATE_DRAGGING_TMP: {
513 send(upEvent());
514 } break;
515 case STATE_SHORTCUT_TRIGGERED: {
516 triggerShortcut();
517 } break;
518 case STATE_PANNING: {
519 send(pointerEvent(ACTION_POINTER_UP, DEFAULT_X * 2, DEFAULT_Y));
520 send(upEvent());
521 returnToNormalFrom(STATE_ZOOMED);
522 } break;
523 case STATE_SCALING_AND_PANNING: {
524 returnToNormalFrom(STATE_PANNING);
525 } break;
526 default: throw new IllegalArgumentException("Illegal state: " + state);
527 }
528 }
529
530 private void check(boolean condition, int expectedState) {
531 if (!condition) {
532 fail("Expected to be in state " + stateToString(expectedState) + stateDump());
533 }
534 }
535
536 private boolean isZoomed() {
Rhed Jao02655dc2018-10-30 20:44:52 +0800537 return mMgh.mMagnificationController.isMagnifying(DISPLAY_0);
Eugene Susla4f8680b2017-08-07 17:25:30 -0700538 }
539
540 private int tapCount() {
Eugene Susla728354b2017-09-25 17:09:52 -0700541 return mMgh.mDetectingState.tapCount();
Eugene Susla4f8680b2017-08-07 17:25:30 -0700542 }
543
544 private static String stateToString(int state) {
ryanlwlin62592982019-12-24 11:19:20 +0800545 return DebugUtils.valueToString(FullScreenMagnificationGestureHandlerTest.class, "STATE_",
546 state);
Eugene Susla4f8680b2017-08-07 17:25:30 -0700547 }
548
549 private void tap() {
Eugene Suslafe87bce2018-01-02 17:50:14 -0800550 send(downEvent());
551 send(upEvent());
Eugene Susla4f8680b2017-08-07 17:25:30 -0700552 }
553
Rhed Jao0232d292019-01-22 14:40:48 +0800554 private void tap(float x, float y) {
555 send(downEvent(x, y));
556 send(upEvent(x, y));
557 }
558
Eugene Susla4f8680b2017-08-07 17:25:30 -0700559 private void swipe() {
Eugene Suslafe87bce2018-01-02 17:50:14 -0800560 swipeAndHold();
561 send(upEvent());
562 }
563
564 private void swipeAndHold() {
565 send(downEvent());
Eugene Susla4f8680b2017-08-07 17:25:30 -0700566 send(moveEvent(DEFAULT_X * 2, DEFAULT_Y * 2));
Eugene Susla4f8680b2017-08-07 17:25:30 -0700567 }
568
569 private void longTap() {
Eugene Suslafe87bce2018-01-02 17:50:14 -0800570 send(downEvent());
Eugene Susla4f8680b2017-08-07 17:25:30 -0700571 fastForward(2000);
Eugene Suslafe87bce2018-01-02 17:50:14 -0800572 send(upEvent());
Eugene Susla4f8680b2017-08-07 17:25:30 -0700573 }
574
575 private void triggerShortcut() {
576 mMgh.notifyShortcutTriggered();
577 }
578
579 private void send(MotionEvent event) {
580 event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
581 try {
582 mMgh.onMotionEvent(event, event, /* policyFlags */ 0);
583 } catch (Throwable t) {
584 throw new RuntimeException("Exception while handling " + event, t);
585 }
586 fastForward(1);
587 }
588
Eugene Susla8f07ee12017-11-14 17:41:03 -0800589 private static MotionEvent fromTouchscreen(MotionEvent ev) {
590 ev.setSource(InputDevice.SOURCE_TOUCHSCREEN);
591 return ev;
592 }
593
Eugene Susla4f8680b2017-08-07 17:25:30 -0700594 private MotionEvent moveEvent(float x, float y) {
Eugene Susla8f07ee12017-11-14 17:41:03 -0800595 return fromTouchscreen(
ryanlwlin62592982019-12-24 11:19:20 +0800596 MotionEvent.obtain(mLastDownTime, mClock.now(), ACTION_MOVE, x, y, 0));
Eugene Susla4f8680b2017-08-07 17:25:30 -0700597 }
598
599 private MotionEvent downEvent() {
Rhed Jao0232d292019-01-22 14:40:48 +0800600 return downEvent(DEFAULT_X, DEFAULT_Y);
601 }
602
603 private MotionEvent downEvent(float x, float y) {
Eugene Suslafe87bce2018-01-02 17:50:14 -0800604 mLastDownTime = mClock.now();
Eugene Susla8f07ee12017-11-14 17:41:03 -0800605 return fromTouchscreen(MotionEvent.obtain(mLastDownTime, mLastDownTime,
Rhed Jao0232d292019-01-22 14:40:48 +0800606 ACTION_DOWN, x, y, 0));
Eugene Susla4f8680b2017-08-07 17:25:30 -0700607 }
608
609 private MotionEvent upEvent() {
Rhed Jao0232d292019-01-22 14:40:48 +0800610 return upEvent(DEFAULT_X, DEFAULT_Y, mLastDownTime);
Eugene Susla4f8680b2017-08-07 17:25:30 -0700611 }
612
Rhed Jao0232d292019-01-22 14:40:48 +0800613 private MotionEvent upEvent(float x, float y) {
614 return upEvent(x, y, mLastDownTime);
615 }
616
617 private MotionEvent upEvent(float x, float y, long downTime) {
Eugene Susla8f07ee12017-11-14 17:41:03 -0800618 return fromTouchscreen(MotionEvent.obtain(downTime, mClock.now(),
Rhed Jao0232d292019-01-22 14:40:48 +0800619 MotionEvent.ACTION_UP, x, y, 0));
Eugene Susla4f8680b2017-08-07 17:25:30 -0700620 }
621
Eugene Susla4f8680b2017-08-07 17:25:30 -0700622 private MotionEvent pointerEvent(int action, float x, float y) {
623 MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties();
624 defPointerProperties.id = 0;
625 defPointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
626 MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
627 pointerProperties.id = 1;
628 pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
629
630 MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords();
631 defPointerCoords.x = DEFAULT_X;
632 defPointerCoords.y = DEFAULT_Y;
633 MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
634 pointerCoords.x = x;
635 pointerCoords.y = y;
636
637 return MotionEvent.obtain(
638 /* downTime */ mClock.now(),
639 /* eventTime */ mClock.now(),
640 /* action */ action,
641 /* pointerCount */ 2,
642 /* pointerProperties */ new MotionEvent.PointerProperties[] {
ryanlwlin62592982019-12-24 11:19:20 +0800643 defPointerProperties, pointerProperties},
Eugene Susla4f8680b2017-08-07 17:25:30 -0700644 /* pointerCoords */ new MotionEvent.PointerCoords[] { defPointerCoords, pointerCoords },
645 /* metaState */ 0,
646 /* buttonState */ 0,
647 /* xPrecision */ 1.0f,
648 /* yPrecision */ 1.0f,
649 /* deviceId */ 0,
650 /* edgeFlags */ 0,
651 /* source */ InputDevice.SOURCE_TOUCHSCREEN,
652 /* flags */ 0);
653 }
654
655 private String stateDump() {
656 return "\nCurrent state dump:\n" + mMgh + "\n" + mHandler.getPendingMessages();
657 }
658}