1 /* 2 * Copyright (C) 2020 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.magnification; 18 19 import static android.view.InputDevice.SOURCE_TOUCHSCREEN; 20 import static android.view.MotionEvent.ACTION_CANCEL; 21 import static android.view.MotionEvent.ACTION_MOVE; 22 import static android.view.MotionEvent.ACTION_UP; 23 24 import static java.util.Arrays.asList; 25 import static java.util.Arrays.copyOfRange; 26 27 import android.annotation.Nullable; 28 import android.annotation.UiContext; 29 import android.content.Context; 30 import android.graphics.Point; 31 import android.os.SystemClock; 32 import android.provider.Settings; 33 import android.util.MathUtils; 34 import android.util.Slog; 35 import android.view.Display; 36 import android.view.MotionEvent; 37 38 import com.android.internal.accessibility.util.AccessibilityStatsLogUtils; 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.server.accessibility.AccessibilityTraceManager; 41 import com.android.server.accessibility.EventStreamTransformation; 42 import com.android.server.accessibility.Flags; 43 import com.android.server.accessibility.gestures.GestureMatcher; 44 import com.android.server.accessibility.gestures.MultiFingerMultiTap; 45 import com.android.server.accessibility.gestures.MultiFingerMultiTapAndHold; 46 import com.android.server.accessibility.gestures.MultiTap; 47 import com.android.server.accessibility.gestures.MultiTapAndHold; 48 49 import java.util.ArrayList; 50 import java.util.List; 51 52 /** 53 * This class handles window magnification in response to touch events and shortcut. 54 * 55 * The behavior is as follows: 56 * 57 * <ol> 58 * <li> 1. Toggle Window magnification by triple-tap gesture shortcut. It is triggered via 59 * {@link #onTripleTap(MotionEvent)}. 60 * <li> 2. Toggle Window magnification by tapping shortcut. It is triggered via 61 * {@link #notifyShortcutTriggered()}. 62 * <li> When the window magnifier is visible, pinching with any number of additional fingers 63 * would adjust the magnification scale .<strong>Note</strong> that this operation is valid only 64 * when at least one finger is in the window. 65 * <li> When the window magnifier is visible, to do scrolling to move the window magnifier, 66 * the user can use two or more fingers and at least one of them is inside the window. 67 * <br><strong>Note</strong> that the offset of this callback is opposed to moving direction. 68 * The operation becomes invalid after performing scaling operation until all fingers are 69 * lifted. 70 * </ol> 71 */ 72 @SuppressWarnings("WeakerAccess") 73 public class WindowMagnificationGestureHandler extends MagnificationGestureHandler { 74 75 private static final boolean DEBUG_STATE_TRANSITIONS = false | DEBUG_ALL; 76 private static final boolean DEBUG_DETECTING = false | DEBUG_ALL; 77 78 //Ensure the range has consistency with FullScreenMagnificationGestureHandler. 79 private static final float MIN_SCALE = 1.0f; 80 private static final float MAX_SCALE = MagnificationScaleProvider.MAX_SCALE; 81 82 private final MagnificationConnectionManager mMagnificationConnectionManager; 83 @VisibleForTesting 84 final DelegatingState mDelegatingState; 85 @VisibleForTesting 86 final DetectingState mDetectingState; 87 @VisibleForTesting 88 final PanningScalingGestureState mObservePanningScalingState; 89 @VisibleForTesting 90 final ViewportDraggingState mViewportDraggingState; 91 92 @VisibleForTesting 93 State mCurrentState; 94 @VisibleForTesting 95 State mPreviousState; 96 97 private MotionEventDispatcherDelegate mMotionEventDispatcherDelegate; 98 private final Context mContext; 99 private final Point mTempPoint = new Point(); 100 101 private long mTripleTapAndHoldStartedTime = 0; 102 WindowMagnificationGestureHandler(@iContext Context context, MagnificationConnectionManager magnificationConnectionManager, AccessibilityTraceManager trace, Callback callback, boolean detectSingleFingerTripleTap, boolean detectTwoFingerTripleTap, boolean detectShortcutTrigger, int displayId)103 public WindowMagnificationGestureHandler(@UiContext Context context, 104 MagnificationConnectionManager magnificationConnectionManager, 105 AccessibilityTraceManager trace, 106 Callback callback, 107 boolean detectSingleFingerTripleTap, 108 boolean detectTwoFingerTripleTap, 109 boolean detectShortcutTrigger, 110 int displayId) { 111 super(displayId, detectSingleFingerTripleTap, detectTwoFingerTripleTap, 112 detectShortcutTrigger, trace, callback); 113 if (DEBUG_ALL) { 114 Slog.i(mLogTag, 115 "WindowMagnificationGestureHandler() , displayId = " + displayId + ")"); 116 } 117 mContext = context; 118 mMagnificationConnectionManager = magnificationConnectionManager; 119 mMotionEventDispatcherDelegate = new MotionEventDispatcherDelegate(context, 120 (event, rawEvent, policyFlags) -> dispatchTransformedEvent(event, rawEvent, 121 policyFlags)); 122 mDelegatingState = new DelegatingState(mMotionEventDispatcherDelegate); 123 mDetectingState = new DetectingState(context); 124 mViewportDraggingState = new ViewportDraggingState(); 125 mObservePanningScalingState = new PanningScalingGestureState( 126 new PanningScalingHandler(context, MAX_SCALE, MIN_SCALE, true, 127 new PanningScalingHandler.MagnificationDelegate() { 128 @Override 129 public boolean processScroll(int displayId, float distanceX, 130 float distanceY) { 131 return mMagnificationConnectionManager.processScroll( 132 displayId, distanceX, distanceY); 133 } 134 135 @Override 136 public void setScale(int displayId, float scale) { 137 mMagnificationConnectionManager.setScale(displayId, scale); 138 } 139 140 @Override 141 public float getScale(int displayId) { 142 return mMagnificationConnectionManager.getScale(displayId); 143 } 144 })); 145 146 transitionTo(mDetectingState); 147 } 148 149 @Override handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)150 void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { 151 // Window Magnification viewport doesn't move with mouse events (yet). 152 } 153 154 @Override onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags)155 void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) { 156 if (event.getSource() != SOURCE_TOUCHSCREEN) { 157 return; 158 } 159 // To keep InputEventConsistencyVerifiers within GestureDetectors happy. 160 mObservePanningScalingState.mPanningScalingHandler.onTouchEvent(event); 161 mCurrentState.onMotionEvent(event, rawEvent, policyFlags); 162 } 163 164 @Override clearEvents(int inputSource)165 public void clearEvents(int inputSource) { 166 if (inputSource == SOURCE_TOUCHSCREEN) { 167 resetToDetectState(); 168 } 169 super.clearEvents(inputSource); 170 } 171 172 @Override onDestroy()173 public void onDestroy() { 174 if (DEBUG_ALL) { 175 Slog.i(mLogTag, "onDestroy(); delayed = " 176 + mDetectingState.toString()); 177 } 178 mMagnificationConnectionManager.disableWindowMagnification(mDisplayId, true); 179 resetToDetectState(); 180 } 181 182 @Override handleShortcutTriggered()183 public void handleShortcutTriggered() { 184 final Point screenSize = mTempPoint; 185 getScreenSize(mTempPoint); 186 toggleMagnification(screenSize.x / 2.0f, screenSize.y / 2.0f, 187 MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER); 188 } 189 getScreenSize(Point outSize)190 private void getScreenSize(Point outSize) { 191 final Display display = mContext.getDisplay(); 192 display.getRealSize(outSize); 193 } 194 195 @Override getMode()196 public int getMode() { 197 return Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; 198 } 199 enableWindowMagnifier(float centerX, float centerY, @MagnificationConnectionManager.WindowPosition int windowPosition)200 private void enableWindowMagnifier(float centerX, float centerY, 201 @MagnificationConnectionManager.WindowPosition int windowPosition) { 202 if (DEBUG_ALL) { 203 Slog.i(mLogTag, "enableWindowMagnifier :" 204 + centerX + ", " + centerY + ", " + windowPosition); 205 } 206 207 final float scale = MathUtils.constrain( 208 mMagnificationConnectionManager.getPersistedScale(mDisplayId), 209 MIN_SCALE, MAX_SCALE); 210 mMagnificationConnectionManager.enableWindowMagnification( 211 mDisplayId, scale, centerX, centerY, windowPosition); 212 } 213 disableWindowMagnifier()214 private void disableWindowMagnifier() { 215 if (DEBUG_ALL) { 216 Slog.i(mLogTag, "disableWindowMagnifier()"); 217 } 218 mMagnificationConnectionManager.disableWindowMagnification(mDisplayId, false); 219 } 220 toggleMagnification(float centerX, float centerY, @MagnificationConnectionManager.WindowPosition int windowPosition)221 private void toggleMagnification(float centerX, float centerY, 222 @MagnificationConnectionManager.WindowPosition int windowPosition) { 223 if (mMagnificationConnectionManager.isWindowMagnifierEnabled(mDisplayId)) { 224 disableWindowMagnifier(); 225 } else { 226 enableWindowMagnifier(centerX, centerY, windowPosition); 227 } 228 } 229 onTripleTap(MotionEvent up)230 private void onTripleTap(MotionEvent up) { 231 if (DEBUG_DETECTING) { 232 Slog.i(mLogTag, "onTripleTap()"); 233 } 234 toggleMagnification(up.getX(), up.getY(), 235 MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER); 236 } 237 238 @VisibleForTesting onTripleTapAndHold(MotionEvent up)239 void onTripleTapAndHold(MotionEvent up) { 240 if (DEBUG_DETECTING) { 241 Slog.i(mLogTag, "onTripleTapAndHold()"); 242 } 243 mViewportDraggingState.mEnabledBeforeDrag = 244 mMagnificationConnectionManager.isWindowMagnifierEnabled(mDisplayId); 245 enableWindowMagnifier(up.getX(), up.getY(), 246 MagnificationConnectionManager.WINDOW_POSITION_AT_TOP_LEFT); 247 mTripleTapAndHoldStartedTime = SystemClock.uptimeMillis(); 248 transitionTo(mViewportDraggingState); 249 } 250 251 @VisibleForTesting releaseTripleTapAndHold()252 void releaseTripleTapAndHold() { 253 if (!mViewportDraggingState.mEnabledBeforeDrag) { 254 mMagnificationConnectionManager.disableWindowMagnification(mDisplayId, true); 255 } 256 transitionTo(mDetectingState); 257 if (mTripleTapAndHoldStartedTime != 0) { 258 final long duration = SystemClock.uptimeMillis() - mTripleTapAndHoldStartedTime; 259 logMagnificationTripleTapAndHoldSession(duration); 260 mTripleTapAndHoldStartedTime = 0; 261 } 262 } 263 264 /** 265 * Logs the duration for the magnification session which is activated by the triple tap and 266 * hold gesture. 267 * 268 * @param duration The duration of a triple-tap-and-hold activation session. 269 */ 270 @VisibleForTesting logMagnificationTripleTapAndHoldSession(long duration)271 void logMagnificationTripleTapAndHoldSession(long duration) { 272 AccessibilityStatsLogUtils.logMagnificationTripleTapAndHoldSession(duration); 273 } 274 resetToDetectState()275 void resetToDetectState() { 276 transitionTo(mDetectingState); 277 } 278 279 /** 280 * An interface to intercept the {@link MotionEvent} for gesture detection. The intercepted 281 * events should be delivered to next {@link EventStreamTransformation} with { 282 * {@link EventStreamTransformation#onMotionEvent(MotionEvent, MotionEvent, int)}} if there is 283 * no valid gestures. 284 */ 285 interface State { onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)286 void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags); 287 clear()288 default void clear() { 289 } 290 onEnter()291 default void onEnter() { 292 } 293 onExit()294 default void onExit() { 295 } 296 name()297 default String name() { 298 return getClass().getSimpleName(); 299 } 300 nameOf(@ullable State s)301 static String nameOf(@Nullable State s) { 302 return s != null ? s.name() : "null"; 303 } 304 } 305 transitionTo(State state)306 private void transitionTo(State state) { 307 if (DEBUG_STATE_TRANSITIONS) { 308 Slog.i(mLogTag, "state transition: " + (State.nameOf(mCurrentState) + " -> " 309 + State.nameOf(state) + " at " 310 + asList(copyOfRange(new RuntimeException().getStackTrace(), 1, 5))) 311 .replace(getClass().getName(), "")); 312 } 313 mPreviousState = mCurrentState; 314 if (mPreviousState != null) { 315 mPreviousState.onExit(); 316 } 317 mCurrentState = state; 318 if (mCurrentState != null) { 319 mCurrentState.onEnter(); 320 } 321 } 322 323 /** 324 * When entering this state, {@link PanningScalingHandler} will be enabled to address the 325 * gestures until receiving {@link MotionEvent#ACTION_UP} or {@link MotionEvent#ACTION_CANCEL}. 326 * When leaving this state, current scale will be persisted. 327 */ 328 final class PanningScalingGestureState implements State { 329 private final PanningScalingHandler mPanningScalingHandler; 330 PanningScalingGestureState(PanningScalingHandler panningScalingHandler)331 PanningScalingGestureState(PanningScalingHandler panningScalingHandler) { 332 mPanningScalingHandler = panningScalingHandler; 333 } 334 335 @Override onEnter()336 public void onEnter() { 337 mPanningScalingHandler.setEnabled(true); 338 } 339 340 @Override onExit()341 public void onExit() { 342 mPanningScalingHandler.setEnabled(false); 343 mMagnificationConnectionManager.persistScale(mDisplayId); 344 clear(); 345 } 346 347 @Override onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)348 public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { 349 int action = event.getActionMasked(); 350 if (action == ACTION_UP || action == ACTION_CANCEL) { 351 transitionTo(mDetectingState); 352 } 353 } 354 355 @Override clear()356 public void clear() { 357 mPanningScalingHandler.clear(); 358 } 359 360 @Override toString()361 public String toString() { 362 return "PanningScalingState{" 363 + "mPanningScalingHandler=" + mPanningScalingHandler + '}'; 364 } 365 } 366 367 /** 368 * A state not to intercept {@link MotionEvent}. Leaving this state until receiving 369 * {@link MotionEvent#ACTION_UP} or {@link MotionEvent#ACTION_CANCEL}. 370 */ 371 final class DelegatingState implements State { 372 private final MotionEventDispatcherDelegate mMotionEventDispatcherDelegate; 373 DelegatingState(MotionEventDispatcherDelegate motionEventDispatcherDelegate)374 DelegatingState(MotionEventDispatcherDelegate motionEventDispatcherDelegate) { 375 mMotionEventDispatcherDelegate = motionEventDispatcherDelegate; 376 } 377 378 @Override onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)379 public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { 380 mMotionEventDispatcherDelegate.dispatchMotionEvent(event, rawEvent, policyFlags); 381 switch (event.getActionMasked()) { 382 case ACTION_UP: 383 case ACTION_CANCEL: { 384 transitionTo(mDetectingState); 385 } 386 break; 387 } 388 } 389 } 390 391 392 /** 393 * This class handles motion events when the event dispatcher has 394 * determined that the user is performing a single-finger drag of the 395 * magnification viewport. 396 * 397 * Leaving this state until receiving {@link MotionEvent#ACTION_UP} 398 * or {@link MotionEvent#ACTION_CANCEL}. 399 */ 400 final class ViewportDraggingState implements State { 401 402 /** Whether to disable zoom after dragging ends */ 403 boolean mEnabledBeforeDrag; 404 405 private float mLastX = Float.NaN; 406 private float mLastY = Float.NaN; 407 408 @Override onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)409 public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { 410 final int action = event.getActionMasked(); 411 switch (action) { 412 case ACTION_MOVE: { 413 if (!Float.isNaN(mLastX) && !Float.isNaN(mLastY)) { 414 float offsetX = event.getX() - mLastX; 415 float offsetY = event.getY() - mLastY; 416 mMagnificationConnectionManager.moveWindowMagnification(mDisplayId, offsetX, 417 offsetY); 418 } 419 mLastX = event.getX(); 420 mLastY = event.getY(); 421 } 422 break; 423 424 case ACTION_UP: 425 case ACTION_CANCEL: { 426 releaseTripleTapAndHold(); 427 } 428 break; 429 } 430 } 431 432 @Override clear()433 public void clear() { 434 mLastX = Float.NaN; 435 mLastY = Float.NaN; 436 } 437 438 @Override onExit()439 public void onExit() { 440 clear(); 441 } 442 443 @Override toString()444 public String toString() { 445 return "ViewportDraggingState{" 446 + "mLastX=" + mLastX 447 + ",mLastY=" + mLastY 448 + '}'; 449 } 450 } 451 452 /** 453 * This class handles motion events in a duration to determine if the user is going to 454 * manipulate the window magnifier or want to interact with current UI. The rule of leaving 455 * this state is as follows: 456 * <ol> 457 * <li> If {@link MagnificationGestureMatcher#GESTURE_TWO_FINGERS_DOWN_OR_SWIPE} is detected, 458 * {@link State} will be transited to {@link PanningScalingGestureState}.</li> 459 * <li> If other gesture is detected and the last motion event is neither ACTION_UP nor 460 * ACTION_CANCEL. 461 * </ol> 462 * <b>Note</b> The motion events will be cached and dispatched before leaving this state. 463 */ 464 final class DetectingState implements State, 465 MagnificationGesturesObserver.Callback { 466 467 private final MagnificationGesturesObserver mGesturesObserver; 468 DetectingState(@iContext Context context)469 DetectingState(@UiContext Context context) { 470 if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { 471 final List<GestureMatcher> mGestureMatchers = new ArrayList<>(); 472 473 mGestureMatchers.add(new SimpleSwipe(context)); 474 // Observe single tap and single tap and hold to reduce response time when the 475 // user performs these two gestures inside the window magnifier. 476 mGestureMatchers.add(new MultiTap(context, 477 mDetectSingleFingerTripleTap ? 3 : 1, 478 mDetectSingleFingerTripleTap 479 ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP 480 : MagnificationGestureMatcher.GESTURE_SINGLE_TAP, 481 MagnificationGestureMatcher.getMagnificationMultiTapTimeout(mContext), 482 null)); 483 mGestureMatchers.add(new MultiTapAndHold(context, 484 mDetectSingleFingerTripleTap ? 3 : 1, 485 mDetectSingleFingerTripleTap 486 ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD 487 : MagnificationGestureMatcher.GESTURE_SINGLE_TAP_AND_HOLD, 488 MagnificationGestureMatcher.getMagnificationMultiTapTimeout(mContext), 489 null)); 490 mGestureMatchers.add(new TwoFingersDownOrSwipe(context)); 491 492 if (mDetectTwoFingerTripleTap) { 493 mGestureMatchers.add(new MultiFingerMultiTap(context, /* fingers= */ 2, 494 /* taps= */ 2, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP, 495 null)); 496 mGestureMatchers.add(new MultiFingerMultiTapAndHold(context, /* fingers= */ 2, 497 /* taps= */ 2, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD, 498 null)); 499 } 500 501 mGesturesObserver = new MagnificationGesturesObserver(this, 502 mGestureMatchers.toArray(new GestureMatcher[mGestureMatchers.size()])); 503 } else { 504 final MultiTap multiTap = new MultiTap(context, 505 mDetectSingleFingerTripleTap ? 3 : 1, 506 mDetectSingleFingerTripleTap 507 ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP 508 : MagnificationGestureMatcher.GESTURE_SINGLE_TAP, 509 MagnificationGestureMatcher.getMagnificationMultiTapTimeout(mContext), 510 null); 511 final MultiTapAndHold multiTapAndHold = new MultiTapAndHold(context, 512 mDetectSingleFingerTripleTap ? 3 : 1, 513 mDetectSingleFingerTripleTap 514 ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD 515 : MagnificationGestureMatcher.GESTURE_SINGLE_TAP_AND_HOLD, 516 MagnificationGestureMatcher.getMagnificationMultiTapTimeout(mContext), 517 null); 518 mGesturesObserver = new MagnificationGesturesObserver(this, 519 new SimpleSwipe(context), 520 multiTap, 521 multiTapAndHold, 522 new TwoFingersDownOrSwipe(context)); 523 } 524 } 525 526 @Override onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)527 public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { 528 mGesturesObserver.onMotionEvent(event, rawEvent, policyFlags); 529 } 530 531 @Override toString()532 public String toString() { 533 return "DetectingState{" 534 + "mGestureTimeoutObserver=" + mGesturesObserver 535 + '}'; 536 } 537 538 @Override shouldStopDetection(MotionEvent motionEvent)539 public boolean shouldStopDetection(MotionEvent motionEvent) { 540 return !mMagnificationConnectionManager.isWindowMagnifierEnabled(mDisplayId) 541 && !mDetectSingleFingerTripleTap 542 && !(mDetectTwoFingerTripleTap 543 && Flags.enableMagnificationMultipleFingerMultipleTapGesture()); 544 } 545 546 @Override onGestureCompleted(int gestureId, long lastDownEventTime, List<MotionEventInfo> delayedEventQueue, MotionEvent motionEvent)547 public void onGestureCompleted(int gestureId, long lastDownEventTime, 548 List<MotionEventInfo> delayedEventQueue, 549 MotionEvent motionEvent) { 550 if (DEBUG_DETECTING) { 551 Slog.d(mLogTag, "onGestureDetected : gesture = " 552 + MagnificationGestureMatcher.gestureIdToString( 553 gestureId)); 554 Slog.d(mLogTag, 555 "onGestureDetected : delayedEventQueue = " + delayedEventQueue); 556 } 557 if (gestureId == MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE 558 && mMagnificationConnectionManager 559 .pointersInWindow(mDisplayId, motionEvent) > 0) { 560 transitionTo(mObservePanningScalingState); 561 } else if (gestureId == MagnificationGestureMatcher.GESTURE_TRIPLE_TAP) { 562 onTripleTap(motionEvent); 563 } else if (gestureId == MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD) { 564 onTripleTapAndHold(motionEvent); 565 } else { 566 mMotionEventDispatcherDelegate.sendDelayedMotionEvents(delayedEventQueue, 567 lastDownEventTime); 568 changeToDelegateStateIfNeed(motionEvent); 569 } 570 } 571 572 @Override onGestureCancelled(long lastDownEventTime, List<MotionEventInfo> delayedEventQueue, MotionEvent motionEvent)573 public void onGestureCancelled(long lastDownEventTime, 574 List<MotionEventInfo> delayedEventQueue, 575 MotionEvent motionEvent) { 576 if (DEBUG_DETECTING) { 577 Slog.d(mLogTag, 578 "onGestureCancelled : delayedEventQueue = " + delayedEventQueue); 579 } 580 mMotionEventDispatcherDelegate.sendDelayedMotionEvents(delayedEventQueue, 581 lastDownEventTime); 582 changeToDelegateStateIfNeed(motionEvent); 583 } 584 changeToDelegateStateIfNeed(MotionEvent motionEvent)585 private void changeToDelegateStateIfNeed(MotionEvent motionEvent) { 586 if (motionEvent != null && (motionEvent.getActionMasked() == ACTION_UP 587 || motionEvent.getActionMasked() == ACTION_CANCEL)) { 588 return; 589 } 590 transitionTo(mDelegatingState); 591 } 592 } 593 594 @Override toString()595 public String toString() { 596 return "WindowMagnificationGestureHandler{" 597 + "mDetectingState=" + mDetectingState 598 + ", mDelegatingState=" + mDelegatingState 599 + ", mViewportDraggingState=" + mViewportDraggingState 600 + ", mMagnifiedInteractionState=" + mObservePanningScalingState 601 + ", mCurrentState=" + State.nameOf(mCurrentState) 602 + ", mPreviousState=" + State.nameOf(mPreviousState) 603 + ", mMagnificationConnectionManager=" + mMagnificationConnectionManager 604 + ", mDisplayId=" + mDisplayId 605 + '}'; 606 } 607 } 608