1 /* 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 17 package com.android.server.accessibility; 18 19 import static android.view.MotionEvent.ACTION_DOWN; 20 import static android.view.MotionEvent.ACTION_MOVE; 21 import static android.view.MotionEvent.ACTION_POINTER_DOWN; 22 import static android.view.MotionEvent.ACTION_POINTER_UP; 23 24 import static com.android.server.testutils.TestUtils.strictMock; 25 26 import static org.junit.Assert.assertFalse; 27 import static org.junit.Assert.assertTrue; 28 import static org.junit.Assert.fail; 29 import static org.mockito.ArgumentMatchers.eq; 30 import static org.mockito.Matchers.any; 31 import static org.mockito.Matchers.anyInt; 32 import static org.mockito.Mockito.doNothing; 33 import static org.mockito.Mockito.mock; 34 import static org.mockito.Mockito.times; 35 import static org.mockito.Mockito.verify; 36 import static org.mockito.Mockito.when; 37 38 import android.animation.ValueAnimator; 39 import android.annotation.NonNull; 40 import android.content.Context; 41 import android.os.Handler; 42 import android.os.Message; 43 import android.util.DebugUtils; 44 import android.view.InputDevice; 45 import android.view.MotionEvent; 46 47 import androidx.test.InstrumentationRegistry; 48 import androidx.test.runner.AndroidJUnit4; 49 50 import com.android.server.testutils.OffsettableClock; 51 import com.android.server.testutils.TestHandler; 52 import com.android.server.wm.WindowManagerInternal; 53 54 import org.junit.After; 55 import org.junit.Before; 56 import org.junit.Test; 57 import org.junit.runner.RunWith; 58 59 import java.util.function.IntConsumer; 60 61 /** 62 * Tests the state transitions of {@link MagnificationGestureHandler} 63 * 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 */ 89 @RunWith(AndroidJUnit4.class) 90 public class MagnificationGestureHandlerTest { 91 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 110 private static final int DISPLAY_0 = 0; 111 112 private Context mContext; 113 MagnificationController mMagnificationController; 114 115 private OffsettableClock mClock; 116 private MagnificationGestureHandler mMgh; 117 private TestHandler mHandler; 118 119 private long mLastDownTime = Integer.MIN_VALUE; 120 121 @Before setUp()122 public void setUp() { 123 mContext = InstrumentationRegistry.getContext(); 124 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()) { 135 @Override 136 public boolean magnificationRegionContains(int displayId, float x, float y) { 137 return true; 138 } 139 140 @Override 141 void setForceShowMagnifiableBounds(int displayId, boolean show) {} 142 }; 143 mMagnificationController.register(DISPLAY_0); 144 mClock = new OffsettableClock.Stopped(); 145 146 boolean detectTripleTap = true; 147 boolean detectShortcutTrigger = true; 148 mMgh = newInstance(detectTripleTap, detectShortcutTrigger); 149 } 150 151 @After tearDown()152 public void tearDown() { 153 mMagnificationController.unregister(DISPLAY_0); 154 } 155 156 @NonNull newInstance(boolean detectTripleTap, boolean detectShortcutTrigger)157 private MagnificationGestureHandler newInstance(boolean detectTripleTap, 158 boolean detectShortcutTrigger) { 159 MagnificationGestureHandler h = new MagnificationGestureHandler( 160 mContext, mMagnificationController, 161 detectTripleTap, detectShortcutTrigger, DISPLAY_0); 162 mHandler = new TestHandler(h.mDetectingState, mClock) { 163 @Override 164 protected String messageToString(Message m) { 165 return DebugUtils.valueToString( 166 MagnificationGestureHandler.DetectingState.class, "MESSAGE_", m.what); 167 } 168 }; 169 h.mDetectingState.mHandler = mHandler; 170 h.setNext(strictMock(EventStreamTransformation.class)); 171 return h; 172 } 173 174 @Test testInitialState_isIdle()175 public void testInitialState_isIdle() { 176 assertIn(STATE_IDLE); 177 } 178 179 /** 180 * Covers paths to get to and back between each state and {@link #STATE_IDLE} 181 * This navigates between states using "canonical" paths, specified in 182 * {@link #goFromStateIdleTo} (for traversing away from {@link #STATE_IDLE}) and 183 * {@link #returnToNormalFrom} (for navigating back to {@link #STATE_IDLE}) 184 */ 185 @Test testEachState_isReachableAndRecoverable()186 public void testEachState_isReachableAndRecoverable() { 187 forEachState(state -> { 188 goFromStateIdleTo(state); 189 assertIn(state); 190 191 returnToNormalFrom(state); 192 try { 193 assertIn(STATE_IDLE); 194 } catch (AssertionError e) { 195 throw new AssertionError("Failed while testing state " + stateToString(state), e); 196 } 197 }); 198 } 199 200 @Test testStates_areMutuallyExclusive()201 public void testStates_areMutuallyExclusive() { 202 forEachState(state1 -> { 203 forEachState(state2 -> { 204 if (state1 < state2) { 205 goFromStateIdleTo(state1); 206 try { 207 assertIn(state2); 208 fail("State " + stateToString(state1) + " also implies state " 209 + stateToString(state2) + stateDump()); 210 } catch (AssertionError e) { 211 // expected 212 returnToNormalFrom(state1); 213 } 214 } 215 }); 216 }); 217 } 218 219 @Test testTransitionToDelegatingStateAndClear_preservesShortcutTriggeredState()220 public void testTransitionToDelegatingStateAndClear_preservesShortcutTriggeredState() { 221 mMgh.mDetectingState.transitionToDelegatingStateAndClear(); 222 assertFalse(mMgh.mDetectingState.mShortcutTriggered); 223 224 goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED); 225 mMgh.mDetectingState.transitionToDelegatingStateAndClear(); 226 assertTrue(mMgh.mDetectingState.mShortcutTriggered); 227 } 228 229 /** 230 * Covers edges of the graph not covered by "canonical" transitions specified in 231 * {@link #goFromStateIdleTo} and {@link #returnToNormalFrom} 232 */ 233 @SuppressWarnings("Convert2MethodRef") 234 @Test testAlternativeTransitions_areWorking()235 public void testAlternativeTransitions_areWorking() { 236 // A11y button followed by a tap&hold turns temporary "viewport dragging" zoom on 237 assertTransition(STATE_SHORTCUT_TRIGGERED, () -> { 238 send(downEvent()); 239 fastForward1sec(); 240 }, STATE_DRAGGING_TMP); 241 242 // A11y button followed by a tap turns zoom on 243 assertTransition(STATE_SHORTCUT_TRIGGERED, () -> tap(), STATE_ZOOMED); 244 245 // A11y button pressed second time negates the 1st press 246 assertTransition(STATE_SHORTCUT_TRIGGERED, () -> triggerShortcut(), STATE_IDLE); 247 248 // A11y button turns zoom off 249 assertTransition(STATE_ZOOMED, () -> triggerShortcut(), STATE_IDLE); 250 251 252 // Double tap times out while zoomed 253 assertTransition(STATE_ZOOMED_2TAPS, () -> { 254 allowEventDelegation(); 255 fastForward1sec(); 256 }, STATE_ZOOMED); 257 258 // tap+tap+swipe doesn't get delegated 259 assertTransition(STATE_2TAPS, () -> swipe(), STATE_IDLE); 260 261 // tap+tap+swipe initiates viewport dragging immediately 262 assertTransition(STATE_2TAPS, () -> swipeAndHold(), STATE_DRAGGING_TMP); 263 } 264 265 @Test testNonTransitions_dontChangeState()266 public void testNonTransitions_dontChangeState() { 267 // ACTION_POINTER_DOWN triggers event delegation if not magnifying 268 assertStaysIn(STATE_IDLE, () -> { 269 allowEventDelegation(); 270 send(downEvent()); 271 send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y)); 272 }); 273 274 // Long tap breaks the triple-tap detection sequence 275 Runnable tapAndLongTap = () -> { 276 allowEventDelegation(); 277 tap(); 278 longTap(); 279 }; 280 assertStaysIn(STATE_IDLE, tapAndLongTap); 281 assertStaysIn(STATE_ZOOMED, tapAndLongTap); 282 283 // Triple tap with delays in between doesn't count 284 Runnable slow3tap = () -> { 285 tap(); 286 fastForward1sec(); 287 tap(); 288 fastForward1sec(); 289 tap(); 290 }; 291 assertStaysIn(STATE_IDLE, slow3tap); 292 assertStaysIn(STATE_ZOOMED, slow3tap); 293 } 294 295 @Test testDisablingTripleTap_removesInputLag()296 public void testDisablingTripleTap_removesInputLag() { 297 mMgh = newInstance(/* detect3tap */ false, /* detectShortcut */ true); 298 goFromStateIdleTo(STATE_IDLE); 299 allowEventDelegation(); 300 tap(); 301 // no fast forward 302 verify(mMgh.getNext(), times(2)).onMotionEvent(any(), any(), anyInt()); 303 } 304 305 @Test testTripleTapAndHold_zoomsImmediately()306 public void testTripleTapAndHold_zoomsImmediately() { 307 assertZoomsImmediatelyOnSwipeFrom(STATE_2TAPS); 308 assertZoomsImmediatelyOnSwipeFrom(STATE_SHORTCUT_TRIGGERED); 309 } 310 311 @Test testMultiTap_outOfDistanceSlop_shouldInIdle()312 public void testMultiTap_outOfDistanceSlop_shouldInIdle() { 313 // All delay motion events should be sent, if multi-tap with out of distance slop. 314 // STATE_IDLE will check if tapCount() < 2. 315 allowEventDelegation(); 316 assertStaysIn(STATE_IDLE, () -> { 317 tap(); 318 tap(DEFAULT_X * 2, DEFAULT_Y * 2); 319 }); 320 assertStaysIn(STATE_IDLE, () -> { 321 tap(); 322 tap(DEFAULT_X * 2, DEFAULT_Y * 2); 323 tap(); 324 tap(DEFAULT_X * 2, DEFAULT_Y * 2); 325 tap(); 326 }); 327 } 328 assertZoomsImmediatelyOnSwipeFrom(int state)329 private void assertZoomsImmediatelyOnSwipeFrom(int state) { 330 goFromStateIdleTo(state); 331 swipeAndHold(); 332 assertIn(STATE_DRAGGING_TMP); 333 returnToNormalFrom(STATE_DRAGGING_TMP); 334 } 335 assertTransition(int fromState, Runnable transitionAction, int toState)336 private void assertTransition(int fromState, Runnable transitionAction, int toState) { 337 goFromStateIdleTo(fromState); 338 transitionAction.run(); 339 assertIn(toState); 340 returnToNormalFrom(toState); 341 } 342 assertStaysIn(int state, Runnable action)343 private void assertStaysIn(int state, Runnable action) { 344 assertTransition(state, action, state); 345 } 346 forEachState(IntConsumer action)347 private void forEachState(IntConsumer action) { 348 for (int state = FIRST_STATE; state <= LAST_STATE; state++) { 349 action.accept(state); 350 } 351 } 352 allowEventDelegation()353 private void allowEventDelegation() { 354 doNothing().when(mMgh.getNext()).onMotionEvent(any(), any(), anyInt()); 355 } 356 fastForward1sec()357 private void fastForward1sec() { 358 fastForward(1000); 359 } 360 fastForward(int ms)361 private void fastForward(int ms) { 362 mClock.fastForward(ms); 363 mHandler.timeAdvance(); 364 } 365 366 /** 367 * Asserts that {@link #mMgh the handler} is in the given {@code state} 368 */ assertIn(int state)369 private void assertIn(int state) { 370 switch (state) { 371 372 // Asserts on separate lines for accurate stack traces 373 374 case STATE_IDLE: { 375 check(tapCount() < 2, state); 376 check(!mMgh.mDetectingState.mShortcutTriggered, state); 377 check(!isZoomed(), state); 378 } break; 379 case STATE_ZOOMED: { 380 check(isZoomed(), state); 381 check(tapCount() < 2, state); 382 } break; 383 case STATE_2TAPS: { 384 check(!isZoomed(), state); 385 check(tapCount() == 2, state); 386 } break; 387 case STATE_ZOOMED_2TAPS: { 388 check(isZoomed(), state); 389 check(tapCount() == 2, state); 390 } break; 391 case STATE_DRAGGING: { 392 check(isZoomed(), state); 393 check(mMgh.mCurrentState == mMgh.mViewportDraggingState, 394 state); 395 check(mMgh.mViewportDraggingState.mZoomedInBeforeDrag, state); 396 } break; 397 case STATE_DRAGGING_TMP: { 398 check(isZoomed(), state); 399 check(mMgh.mCurrentState == mMgh.mViewportDraggingState, 400 state); 401 check(!mMgh.mViewportDraggingState.mZoomedInBeforeDrag, state); 402 } break; 403 case STATE_SHORTCUT_TRIGGERED: { 404 check(mMgh.mDetectingState.mShortcutTriggered, state); 405 check(!isZoomed(), state); 406 } break; 407 case STATE_PANNING: { 408 check(isZoomed(), state); 409 check(mMgh.mCurrentState == mMgh.mPanningScalingState, 410 state); 411 check(!mMgh.mPanningScalingState.mScaling, state); 412 } break; 413 case STATE_SCALING_AND_PANNING: { 414 check(isZoomed(), state); 415 check(mMgh.mCurrentState == mMgh.mPanningScalingState, 416 state); 417 check(mMgh.mPanningScalingState.mScaling, state); 418 } break; 419 default: throw new IllegalArgumentException("Illegal state: " + state); 420 } 421 } 422 423 /** 424 * Defines a "canonical" path from {@link #STATE_IDLE} to {@code state} 425 */ 426 private void goFromStateIdleTo(int state) { 427 try { 428 switch (state) { 429 case STATE_IDLE: { 430 mMgh.clearAndTransitionToStateDetecting(); 431 } break; 432 case STATE_2TAPS: { 433 goFromStateIdleTo(STATE_IDLE); 434 tap(); 435 tap(); 436 } break; 437 case STATE_ZOOMED: { 438 if (mMgh.mDetectTripleTap) { 439 goFromStateIdleTo(STATE_2TAPS); 440 tap(); 441 } else { 442 goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED); 443 tap(); 444 } 445 } break; 446 case STATE_ZOOMED_2TAPS: { 447 goFromStateIdleTo(STATE_ZOOMED); 448 tap(); 449 tap(); 450 } break; 451 case STATE_DRAGGING: { 452 goFromStateIdleTo(STATE_ZOOMED_2TAPS); 453 send(downEvent()); 454 fastForward1sec(); 455 } break; 456 case STATE_DRAGGING_TMP: { 457 goFromStateIdleTo(STATE_2TAPS); 458 send(downEvent()); 459 fastForward1sec(); 460 } break; 461 case STATE_SHORTCUT_TRIGGERED: { 462 goFromStateIdleTo(STATE_IDLE); 463 triggerShortcut(); 464 } break; 465 case STATE_PANNING: { 466 goFromStateIdleTo(STATE_ZOOMED); 467 send(downEvent()); 468 send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y)); 469 } break; 470 case STATE_SCALING_AND_PANNING: { 471 goFromStateIdleTo(STATE_PANNING); 472 send(pointerEvent(ACTION_MOVE, DEFAULT_X * 2, DEFAULT_Y * 3)); 473 send(pointerEvent(ACTION_MOVE, DEFAULT_X * 2, DEFAULT_Y * 4)); 474 } break; 475 default: 476 throw new IllegalArgumentException("Illegal state: " + state); 477 } 478 } catch (Throwable t) { 479 throw new RuntimeException("Failed to go to state " + stateToString(state), t); 480 } 481 } 482 483 /** 484 * Defines a "canonical" path from {@code state} to {@link #STATE_IDLE} 485 */ 486 private void returnToNormalFrom(int state) { 487 switch (state) { 488 case STATE_IDLE: { 489 // no op 490 } break; 491 case STATE_2TAPS: { 492 allowEventDelegation(); 493 fastForward1sec(); 494 } break; 495 case STATE_ZOOMED: { 496 if (mMgh.mDetectTripleTap) { 497 tap(); 498 tap(); 499 returnToNormalFrom(STATE_ZOOMED_2TAPS); 500 } else { 501 triggerShortcut(); 502 } 503 } break; 504 case STATE_ZOOMED_2TAPS: { 505 tap(); 506 } break; 507 case STATE_DRAGGING: { 508 send(upEvent()); 509 returnToNormalFrom(STATE_ZOOMED); 510 } break; 511 case STATE_DRAGGING_TMP: { 512 send(upEvent()); 513 } break; 514 case STATE_SHORTCUT_TRIGGERED: { 515 triggerShortcut(); 516 } break; 517 case STATE_PANNING: { 518 send(pointerEvent(ACTION_POINTER_UP, DEFAULT_X * 2, DEFAULT_Y)); 519 send(upEvent()); 520 returnToNormalFrom(STATE_ZOOMED); 521 } break; 522 case STATE_SCALING_AND_PANNING: { 523 returnToNormalFrom(STATE_PANNING); 524 } break; 525 default: throw new IllegalArgumentException("Illegal state: " + state); 526 } 527 } 528 529 private void check(boolean condition, int expectedState) { 530 if (!condition) { 531 fail("Expected to be in state " + stateToString(expectedState) + stateDump()); 532 } 533 } 534 535 private boolean isZoomed() { 536 return mMgh.mMagnificationController.isMagnifying(DISPLAY_0); 537 } 538 539 private int tapCount() { 540 return mMgh.mDetectingState.tapCount(); 541 } 542 543 private static String stateToString(int state) { 544 return DebugUtils.valueToString(MagnificationGestureHandlerTest.class, "STATE_", state); 545 } 546 547 private void tap() { 548 send(downEvent()); 549 send(upEvent()); 550 } 551 552 private void tap(float x, float y) { 553 send(downEvent(x, y)); 554 send(upEvent(x, y)); 555 } 556 557 private void swipe() { 558 swipeAndHold(); 559 send(upEvent()); 560 } 561 562 private void swipeAndHold() { 563 send(downEvent()); 564 send(moveEvent(DEFAULT_X * 2, DEFAULT_Y * 2)); 565 } 566 567 private void longTap() { 568 send(downEvent()); 569 fastForward(2000); 570 send(upEvent()); 571 } 572 573 private void triggerShortcut() { 574 mMgh.notifyShortcutTriggered(); 575 } 576 577 private void send(MotionEvent event) { 578 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 579 try { 580 mMgh.onMotionEvent(event, event, /* policyFlags */ 0); 581 } catch (Throwable t) { 582 throw new RuntimeException("Exception while handling " + event, t); 583 } 584 fastForward(1); 585 } 586 587 private static MotionEvent fromTouchscreen(MotionEvent ev) { 588 ev.setSource(InputDevice.SOURCE_TOUCHSCREEN); 589 return ev; 590 } 591 592 private MotionEvent moveEvent(float x, float y) { 593 return fromTouchscreen( 594 MotionEvent.obtain(mLastDownTime, mClock.now(), ACTION_MOVE, x, y, 0)); 595 } 596 597 private MotionEvent downEvent() { 598 return downEvent(DEFAULT_X, DEFAULT_Y); 599 } 600 601 private MotionEvent downEvent(float x, float y) { 602 mLastDownTime = mClock.now(); 603 return fromTouchscreen(MotionEvent.obtain(mLastDownTime, mLastDownTime, 604 ACTION_DOWN, x, y, 0)); 605 } 606 607 private MotionEvent upEvent() { 608 return upEvent(DEFAULT_X, DEFAULT_Y, mLastDownTime); 609 } 610 611 private MotionEvent upEvent(float x, float y) { 612 return upEvent(x, y, mLastDownTime); 613 } 614 615 private MotionEvent upEvent(float x, float y, long downTime) { 616 return fromTouchscreen(MotionEvent.obtain(downTime, mClock.now(), 617 MotionEvent.ACTION_UP, x, y, 0)); 618 } 619 620 private MotionEvent pointerEvent(int action, float x, float y) { 621 MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties(); 622 defPointerProperties.id = 0; 623 defPointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER; 624 MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties(); 625 pointerProperties.id = 1; 626 pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER; 627 628 MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords(); 629 defPointerCoords.x = DEFAULT_X; 630 defPointerCoords.y = DEFAULT_Y; 631 MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords(); 632 pointerCoords.x = x; 633 pointerCoords.y = y; 634 635 return MotionEvent.obtain( 636 /* downTime */ mClock.now(), 637 /* eventTime */ mClock.now(), 638 /* action */ action, 639 /* pointerCount */ 2, 640 /* pointerProperties */ new MotionEvent.PointerProperties[] { 641 defPointerProperties, pointerProperties }, 642 /* pointerCoords */ new MotionEvent.PointerCoords[] { defPointerCoords, pointerCoords }, 643 /* metaState */ 0, 644 /* buttonState */ 0, 645 /* xPrecision */ 1.0f, 646 /* yPrecision */ 1.0f, 647 /* deviceId */ 0, 648 /* edgeFlags */ 0, 649 /* source */ InputDevice.SOURCE_TOUCHSCREEN, 650 /* flags */ 0); 651 } 652 653 private String stateDump() { 654 return "\nCurrent state dump:\n" + mMgh + "\n" + mHandler.getPendingMessages(); 655 } 656 } 657