1/* 2 * Copyright (C) 2015 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 19import android.content.Context; 20import android.os.Handler; 21import android.os.Message; 22import android.util.MathUtils; 23import android.util.Slog; 24import android.util.TypedValue; 25import android.view.GestureDetector; 26import android.view.GestureDetector.SimpleOnGestureListener; 27import android.view.InputDevice; 28import android.view.KeyEvent; 29import android.view.MotionEvent; 30import android.view.MotionEvent.PointerCoords; 31import android.view.MotionEvent.PointerProperties; 32import android.view.ScaleGestureDetector; 33import android.view.ScaleGestureDetector.OnScaleGestureListener; 34import android.view.ViewConfiguration; 35import android.view.accessibility.AccessibilityEvent; 36 37/** 38 * This class handles magnification in response to touch events. 39 * 40 * The behavior is as follows: 41 * 42 * 1. Triple tap toggles permanent screen magnification which is magnifying 43 * the area around the location of the triple tap. One can think of the 44 * location of the triple tap as the center of the magnified viewport. 45 * For example, a triple tap when not magnified would magnify the screen 46 * and leave it in a magnified state. A triple tapping when magnified would 47 * clear magnification and leave the screen in a not magnified state. 48 * 49 * 2. Triple tap and hold would magnify the screen if not magnified and enable 50 * viewport dragging mode until the finger goes up. One can think of this 51 * mode as a way to move the magnified viewport since the area around the 52 * moving finger will be magnified to fit the screen. For example, if the 53 * screen was not magnified and the user triple taps and holds the screen 54 * would magnify and the viewport will follow the user's finger. When the 55 * finger goes up the screen will zoom out. If the same user interaction 56 * is performed when the screen is magnified, the viewport movement will 57 * be the same but when the finger goes up the screen will stay magnified. 58 * In other words, the initial magnified state is sticky. 59 * 60 * 3. Pinching with any number of additional fingers when viewport dragging 61 * is enabled, i.e. the user triple tapped and holds, would adjust the 62 * magnification scale which will become the current default magnification 63 * scale. The next time the user magnifies the same magnification scale 64 * would be used. 65 * 66 * 4. When in a permanent magnified state the user can use two or more fingers 67 * to pan the viewport. Note that in this mode the content is panned as 68 * opposed to the viewport dragging mode in which the viewport is moved. 69 * 70 * 5. When in a permanent magnified state the user can use two or more 71 * fingers to change the magnification scale which will become the current 72 * default magnification scale. The next time the user magnifies the same 73 * magnification scale would be used. 74 * 75 * 6. The magnification scale will be persisted in settings and in the cloud. 76 */ 77class MagnificationGestureHandler implements EventStreamTransformation { 78 private static final String LOG_TAG = "MagnificationEventHandler"; 79 80 private static final boolean DEBUG_STATE_TRANSITIONS = false; 81 private static final boolean DEBUG_DETECTING = false; 82 private static final boolean DEBUG_PANNING = false; 83 84 private static final int STATE_DELEGATING = 1; 85 private static final int STATE_DETECTING = 2; 86 private static final int STATE_VIEWPORT_DRAGGING = 3; 87 private static final int STATE_MAGNIFIED_INTERACTION = 4; 88 89 private static final float MIN_SCALE = 2.0f; 90 private static final float MAX_SCALE = 5.0f; 91 92 private final MagnificationController mMagnificationController; 93 private final DetectingStateHandler mDetectingStateHandler; 94 private final MagnifiedContentInteractionStateHandler mMagnifiedContentInteractionStateHandler; 95 private final StateViewportDraggingHandler mStateViewportDraggingHandler; 96 97 98 private final boolean mDetectControlGestures; 99 100 private EventStreamTransformation mNext; 101 102 private int mCurrentState; 103 private int mPreviousState; 104 105 private boolean mTranslationEnabledBeforePan; 106 107 private PointerCoords[] mTempPointerCoords; 108 private PointerProperties[] mTempPointerProperties; 109 110 private long mDelegatingStateDownTime; 111 112 public MagnificationGestureHandler(Context context, AccessibilityManagerService ams, 113 boolean detectControlGestures) { 114 mMagnificationController = ams.getMagnificationController(); 115 mDetectingStateHandler = new DetectingStateHandler(context); 116 mStateViewportDraggingHandler = new StateViewportDraggingHandler(); 117 mMagnifiedContentInteractionStateHandler = 118 new MagnifiedContentInteractionStateHandler(context); 119 mDetectControlGestures = detectControlGestures; 120 121 transitionToState(STATE_DETECTING); 122 } 123 124 @Override 125 public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { 126 if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) { 127 if (mNext != null) { 128 mNext.onMotionEvent(event, rawEvent, policyFlags); 129 } 130 return; 131 } 132 if (!mDetectControlGestures) { 133 if (mNext != null) { 134 dispatchTransformedEvent(event, rawEvent, policyFlags); 135 } 136 return; 137 } 138 mMagnifiedContentInteractionStateHandler.onMotionEvent(event, rawEvent, policyFlags); 139 switch (mCurrentState) { 140 case STATE_DELEGATING: { 141 handleMotionEventStateDelegating(event, rawEvent, policyFlags); 142 } 143 break; 144 case STATE_DETECTING: { 145 mDetectingStateHandler.onMotionEvent(event, rawEvent, policyFlags); 146 } 147 break; 148 case STATE_VIEWPORT_DRAGGING: { 149 mStateViewportDraggingHandler.onMotionEvent(event, rawEvent, policyFlags); 150 } 151 break; 152 case STATE_MAGNIFIED_INTERACTION: { 153 // mMagnifiedContentInteractionStateHandler handles events only 154 // if this is the current state since it uses ScaleGestureDetecotr 155 // and a GestureDetector which need well formed event stream. 156 } 157 break; 158 default: { 159 throw new IllegalStateException("Unknown state: " + mCurrentState); 160 } 161 } 162 } 163 164 @Override 165 public void onKeyEvent(KeyEvent event, int policyFlags) { 166 if (mNext != null) { 167 mNext.onKeyEvent(event, policyFlags); 168 } 169 } 170 171 @Override 172 public void onAccessibilityEvent(AccessibilityEvent event) { 173 if (mNext != null) { 174 mNext.onAccessibilityEvent(event); 175 } 176 } 177 178 @Override 179 public void setNext(EventStreamTransformation next) { 180 mNext = next; 181 } 182 183 @Override 184 public void clearEvents(int inputSource) { 185 if (inputSource == InputDevice.SOURCE_TOUCHSCREEN) { 186 clear(); 187 } 188 189 if (mNext != null) { 190 mNext.clearEvents(inputSource); 191 } 192 } 193 194 @Override 195 public void onDestroy() { 196 clear(); 197 } 198 199 private void clear() { 200 mCurrentState = STATE_DETECTING; 201 mDetectingStateHandler.clear(); 202 mStateViewportDraggingHandler.clear(); 203 mMagnifiedContentInteractionStateHandler.clear(); 204 } 205 206 private void handleMotionEventStateDelegating(MotionEvent event, 207 MotionEvent rawEvent, int policyFlags) { 208 switch (event.getActionMasked()) { 209 case MotionEvent.ACTION_DOWN: { 210 mDelegatingStateDownTime = event.getDownTime(); 211 } 212 break; 213 case MotionEvent.ACTION_UP: { 214 if (mDetectingStateHandler.mDelayedEventQueue == null) { 215 transitionToState(STATE_DETECTING); 216 } 217 } 218 break; 219 } 220 if (mNext != null) { 221 // We cache some events to see if the user wants to trigger magnification. 222 // If no magnification is triggered we inject these events with adjusted 223 // time and down time to prevent subsequent transformations being confused 224 // by stale events. After the cached events, which always have a down, are 225 // injected we need to also update the down time of all subsequent non cached 226 // events. All delegated events cached and non-cached are delivered here. 227 event.setDownTime(mDelegatingStateDownTime); 228 dispatchTransformedEvent(event, rawEvent, policyFlags); 229 } 230 } 231 232 private void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent, 233 int policyFlags) { 234 // If the event is within the magnified portion of the screen we have 235 // to change its location to be where the user thinks he is poking the 236 // UI which may have been magnified and panned. 237 final float eventX = event.getX(); 238 final float eventY = event.getY(); 239 if (mMagnificationController.isMagnifying() 240 && mMagnificationController.magnificationRegionContains(eventX, eventY)) { 241 final float scale = mMagnificationController.getScale(); 242 final float scaledOffsetX = mMagnificationController.getOffsetX(); 243 final float scaledOffsetY = mMagnificationController.getOffsetY(); 244 final int pointerCount = event.getPointerCount(); 245 PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount); 246 PointerProperties[] properties = getTempPointerPropertiesWithMinSize( 247 pointerCount); 248 for (int i = 0; i < pointerCount; i++) { 249 event.getPointerCoords(i, coords[i]); 250 coords[i].x = (coords[i].x - scaledOffsetX) / scale; 251 coords[i].y = (coords[i].y - scaledOffsetY) / scale; 252 event.getPointerProperties(i, properties[i]); 253 } 254 event = MotionEvent.obtain(event.getDownTime(), 255 event.getEventTime(), event.getAction(), pointerCount, properties, 256 coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, event.getSource(), 257 event.getFlags()); 258 } 259 mNext.onMotionEvent(event, rawEvent, policyFlags); 260 } 261 262 private PointerCoords[] getTempPointerCoordsWithMinSize(int size) { 263 final int oldSize = (mTempPointerCoords != null) ? mTempPointerCoords.length : 0; 264 if (oldSize < size) { 265 PointerCoords[] oldTempPointerCoords = mTempPointerCoords; 266 mTempPointerCoords = new PointerCoords[size]; 267 if (oldTempPointerCoords != null) { 268 System.arraycopy(oldTempPointerCoords, 0, mTempPointerCoords, 0, oldSize); 269 } 270 } 271 for (int i = oldSize; i < size; i++) { 272 mTempPointerCoords[i] = new PointerCoords(); 273 } 274 return mTempPointerCoords; 275 } 276 277 private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) { 278 final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length 279 : 0; 280 if (oldSize < size) { 281 PointerProperties[] oldTempPointerProperties = mTempPointerProperties; 282 mTempPointerProperties = new PointerProperties[size]; 283 if (oldTempPointerProperties != null) { 284 System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, 285 oldSize); 286 } 287 } 288 for (int i = oldSize; i < size; i++) { 289 mTempPointerProperties[i] = new PointerProperties(); 290 } 291 return mTempPointerProperties; 292 } 293 294 private void transitionToState(int state) { 295 if (DEBUG_STATE_TRANSITIONS) { 296 switch (state) { 297 case STATE_DELEGATING: { 298 Slog.i(LOG_TAG, "mCurrentState: STATE_DELEGATING"); 299 } 300 break; 301 case STATE_DETECTING: { 302 Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING"); 303 } 304 break; 305 case STATE_VIEWPORT_DRAGGING: { 306 Slog.i(LOG_TAG, "mCurrentState: STATE_VIEWPORT_DRAGGING"); 307 } 308 break; 309 case STATE_MAGNIFIED_INTERACTION: { 310 Slog.i(LOG_TAG, "mCurrentState: STATE_MAGNIFIED_INTERACTION"); 311 } 312 break; 313 default: { 314 throw new IllegalArgumentException("Unknown state: " + state); 315 } 316 } 317 } 318 mPreviousState = mCurrentState; 319 mCurrentState = state; 320 } 321 322 private interface MotionEventHandler { 323 324 void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags); 325 326 void clear(); 327 } 328 329 /** 330 * This class determines if the user is performing a scale or pan gesture. 331 */ 332 private final class MagnifiedContentInteractionStateHandler extends SimpleOnGestureListener 333 implements OnScaleGestureListener, MotionEventHandler { 334 335 private final ScaleGestureDetector mScaleGestureDetector; 336 337 private final GestureDetector mGestureDetector; 338 339 private final float mScalingThreshold; 340 341 private float mInitialScaleFactor = -1; 342 343 private boolean mScaling; 344 345 public MagnifiedContentInteractionStateHandler(Context context) { 346 final TypedValue scaleValue = new TypedValue(); 347 context.getResources().getValue( 348 com.android.internal.R.dimen.config_screen_magnification_scaling_threshold, 349 scaleValue, false); 350 mScalingThreshold = scaleValue.getFloat(); 351 mScaleGestureDetector = new ScaleGestureDetector(context, this); 352 mScaleGestureDetector.setQuickScaleEnabled(false); 353 mGestureDetector = new GestureDetector(context, this); 354 } 355 356 @Override 357 public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { 358 mScaleGestureDetector.onTouchEvent(event); 359 mGestureDetector.onTouchEvent(event); 360 if (mCurrentState != STATE_MAGNIFIED_INTERACTION) { 361 return; 362 } 363 if (event.getActionMasked() == MotionEvent.ACTION_UP) { 364 clear(); 365 mMagnificationController.persistScale(); 366 if (mPreviousState == STATE_VIEWPORT_DRAGGING) { 367 transitionToState(STATE_VIEWPORT_DRAGGING); 368 } else { 369 transitionToState(STATE_DETECTING); 370 } 371 } 372 } 373 374 @Override 375 public boolean onScroll(MotionEvent first, MotionEvent second, float distanceX, 376 float distanceY) { 377 if (mCurrentState != STATE_MAGNIFIED_INTERACTION) { 378 return true; 379 } 380 if (DEBUG_PANNING) { 381 Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX 382 + " scrollY: " + distanceY); 383 } 384 mMagnificationController.offsetMagnifiedRegionCenter(distanceX, distanceY, 385 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); 386 return true; 387 } 388 389 @Override 390 public boolean onScale(ScaleGestureDetector detector) { 391 if (!mScaling) { 392 if (mInitialScaleFactor < 0) { 393 mInitialScaleFactor = detector.getScaleFactor(); 394 } else { 395 final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor; 396 if (Math.abs(deltaScale) > mScalingThreshold) { 397 mScaling = true; 398 return true; 399 } 400 } 401 return false; 402 } 403 404 final float initialScale = mMagnificationController.getScale(); 405 final float targetScale = initialScale * detector.getScaleFactor(); 406 407 // Don't allow a gesture to move the user further outside the 408 // desired bounds for gesture-controlled scaling. 409 final float scale; 410 if (targetScale > MAX_SCALE && targetScale > initialScale) { 411 // The target scale is too big and getting bigger. 412 scale = MAX_SCALE; 413 } else if (targetScale < MIN_SCALE && targetScale < initialScale) { 414 // The target scale is too small and getting smaller. 415 scale = MIN_SCALE; 416 } else { 417 // The target scale may be outside our bounds, but at least 418 // it's moving in the right direction. This avoids a "jump" if 419 // we're at odds with some other service's desired bounds. 420 scale = targetScale; 421 } 422 423 final float pivotX = detector.getFocusX(); 424 final float pivotY = detector.getFocusY(); 425 mMagnificationController.setScale(scale, pivotX, pivotY, false, 426 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); 427 return true; 428 } 429 430 @Override 431 public boolean onScaleBegin(ScaleGestureDetector detector) { 432 return (mCurrentState == STATE_MAGNIFIED_INTERACTION); 433 } 434 435 @Override 436 public void onScaleEnd(ScaleGestureDetector detector) { 437 clear(); 438 } 439 440 @Override 441 public void clear() { 442 mInitialScaleFactor = -1; 443 mScaling = false; 444 } 445 } 446 447 /** 448 * This class handles motion events when the event dispatcher has 449 * determined that the user is performing a single-finger drag of the 450 * magnification viewport. 451 */ 452 private final class StateViewportDraggingHandler implements MotionEventHandler { 453 454 private boolean mLastMoveOutsideMagnifiedRegion; 455 456 @Override 457 public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { 458 final int action = event.getActionMasked(); 459 switch (action) { 460 case MotionEvent.ACTION_DOWN: { 461 throw new IllegalArgumentException("Unexpected event type: ACTION_DOWN"); 462 } 463 case MotionEvent.ACTION_POINTER_DOWN: { 464 clear(); 465 transitionToState(STATE_MAGNIFIED_INTERACTION); 466 } 467 break; 468 case MotionEvent.ACTION_MOVE: { 469 if (event.getPointerCount() != 1) { 470 throw new IllegalStateException("Should have one pointer down."); 471 } 472 final float eventX = event.getX(); 473 final float eventY = event.getY(); 474 if (mMagnificationController.magnificationRegionContains(eventX, eventY)) { 475 if (mLastMoveOutsideMagnifiedRegion) { 476 mLastMoveOutsideMagnifiedRegion = false; 477 mMagnificationController.setCenter(eventX, eventY, true, 478 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); 479 } else { 480 mMagnificationController.setCenter(eventX, eventY, false, 481 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); 482 } 483 } else { 484 mLastMoveOutsideMagnifiedRegion = true; 485 } 486 } 487 break; 488 case MotionEvent.ACTION_UP: { 489 if (!mTranslationEnabledBeforePan) { 490 mMagnificationController.reset(true); 491 } 492 clear(); 493 transitionToState(STATE_DETECTING); 494 } 495 break; 496 case MotionEvent.ACTION_POINTER_UP: { 497 throw new IllegalArgumentException( 498 "Unexpected event type: ACTION_POINTER_UP"); 499 } 500 } 501 } 502 503 @Override 504 public void clear() { 505 mLastMoveOutsideMagnifiedRegion = false; 506 } 507 } 508 509 /** 510 * This class handles motion events when the event dispatch has not yet 511 * determined what the user is doing. It watches for various tap events. 512 */ 513 private final class DetectingStateHandler implements MotionEventHandler { 514 515 private static final int MESSAGE_ON_ACTION_TAP_AND_HOLD = 1; 516 517 private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2; 518 519 private static final int ACTION_TAP_COUNT = 3; 520 521 private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout(); 522 523 private final int mMultiTapTimeSlop; 524 525 private final int mTapDistanceSlop; 526 527 private final int mMultiTapDistanceSlop; 528 529 private MotionEventInfo mDelayedEventQueue; 530 531 private MotionEvent mLastDownEvent; 532 533 private MotionEvent mLastTapUpEvent; 534 535 private int mTapCount; 536 537 public DetectingStateHandler(Context context) { 538 mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout() 539 + context.getResources().getInteger( 540 com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment); 541 mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 542 mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); 543 } 544 545 private final Handler mHandler = new Handler() { 546 @Override 547 public void handleMessage(Message message) { 548 final int type = message.what; 549 switch (type) { 550 case MESSAGE_ON_ACTION_TAP_AND_HOLD: { 551 MotionEvent event = (MotionEvent) message.obj; 552 final int policyFlags = message.arg1; 553 onActionTapAndHold(event, policyFlags); 554 } 555 break; 556 case MESSAGE_TRANSITION_TO_DELEGATING_STATE: { 557 transitionToState(STATE_DELEGATING); 558 sendDelayedMotionEvents(); 559 clear(); 560 } 561 break; 562 default: { 563 throw new IllegalArgumentException("Unknown message type: " + type); 564 } 565 } 566 } 567 }; 568 569 @Override 570 public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { 571 cacheDelayedMotionEvent(event, rawEvent, policyFlags); 572 final int action = event.getActionMasked(); 573 switch (action) { 574 case MotionEvent.ACTION_DOWN: { 575 mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); 576 if (!mMagnificationController.magnificationRegionContains( 577 event.getX(), event.getY())) { 578 transitionToDelegatingStateAndClear(); 579 return; 580 } 581 if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null 582 && GestureUtils.isMultiTap(mLastDownEvent, event, 583 mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { 584 Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD, 585 policyFlags, 0, event); 586 mHandler.sendMessageDelayed(message, 587 ViewConfiguration.getLongPressTimeout()); 588 } else if (mTapCount < ACTION_TAP_COUNT) { 589 Message message = mHandler.obtainMessage( 590 MESSAGE_TRANSITION_TO_DELEGATING_STATE); 591 mHandler.sendMessageDelayed(message, mMultiTapTimeSlop); 592 } 593 clearLastDownEvent(); 594 mLastDownEvent = MotionEvent.obtain(event); 595 } 596 break; 597 case MotionEvent.ACTION_POINTER_DOWN: { 598 if (mMagnificationController.isMagnifying()) { 599 transitionToState(STATE_MAGNIFIED_INTERACTION); 600 clear(); 601 } else { 602 transitionToDelegatingStateAndClear(); 603 } 604 } 605 break; 606 case MotionEvent.ACTION_MOVE: { 607 if (mLastDownEvent != null && mTapCount < ACTION_TAP_COUNT - 1) { 608 final double distance = GestureUtils.computeDistance(mLastDownEvent, 609 event, 0); 610 if (Math.abs(distance) > mTapDistanceSlop) { 611 transitionToDelegatingStateAndClear(); 612 } 613 } 614 } 615 break; 616 case MotionEvent.ACTION_UP: { 617 if (mLastDownEvent == null) { 618 return; 619 } 620 mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); 621 if (!mMagnificationController.magnificationRegionContains( 622 event.getX(), event.getY())) { 623 transitionToDelegatingStateAndClear(); 624 return; 625 } 626 if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop, 627 mTapDistanceSlop, 0)) { 628 transitionToDelegatingStateAndClear(); 629 return; 630 } 631 if (mLastTapUpEvent != null && !GestureUtils.isMultiTap(mLastTapUpEvent, 632 event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { 633 transitionToDelegatingStateAndClear(); 634 return; 635 } 636 mTapCount++; 637 if (DEBUG_DETECTING) { 638 Slog.i(LOG_TAG, "Tap count:" + mTapCount); 639 } 640 if (mTapCount == ACTION_TAP_COUNT) { 641 clear(); 642 onActionTap(event, policyFlags); 643 return; 644 } 645 clearLastTapUpEvent(); 646 mLastTapUpEvent = MotionEvent.obtain(event); 647 } 648 break; 649 case MotionEvent.ACTION_POINTER_UP: { 650 /* do nothing */ 651 } 652 break; 653 } 654 } 655 656 @Override 657 public void clear() { 658 mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); 659 mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); 660 clearTapDetectionState(); 661 clearDelayedMotionEvents(); 662 } 663 664 private void clearTapDetectionState() { 665 mTapCount = 0; 666 clearLastTapUpEvent(); 667 clearLastDownEvent(); 668 } 669 670 private void clearLastTapUpEvent() { 671 if (mLastTapUpEvent != null) { 672 mLastTapUpEvent.recycle(); 673 mLastTapUpEvent = null; 674 } 675 } 676 677 private void clearLastDownEvent() { 678 if (mLastDownEvent != null) { 679 mLastDownEvent.recycle(); 680 mLastDownEvent = null; 681 } 682 } 683 684 private void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent, 685 int policyFlags) { 686 MotionEventInfo info = MotionEventInfo.obtain(event, rawEvent, 687 policyFlags); 688 if (mDelayedEventQueue == null) { 689 mDelayedEventQueue = info; 690 } else { 691 MotionEventInfo tail = mDelayedEventQueue; 692 while (tail.mNext != null) { 693 tail = tail.mNext; 694 } 695 tail.mNext = info; 696 } 697 } 698 699 private void sendDelayedMotionEvents() { 700 while (mDelayedEventQueue != null) { 701 MotionEventInfo info = mDelayedEventQueue; 702 mDelayedEventQueue = info.mNext; 703 MagnificationGestureHandler.this.onMotionEvent(info.mEvent, info.mRawEvent, 704 info.mPolicyFlags); 705 info.recycle(); 706 } 707 } 708 709 private void clearDelayedMotionEvents() { 710 while (mDelayedEventQueue != null) { 711 MotionEventInfo info = mDelayedEventQueue; 712 mDelayedEventQueue = info.mNext; 713 info.recycle(); 714 } 715 } 716 717 private void transitionToDelegatingStateAndClear() { 718 transitionToState(STATE_DELEGATING); 719 sendDelayedMotionEvents(); 720 clear(); 721 } 722 723 private void onActionTap(MotionEvent up, int policyFlags) { 724 if (DEBUG_DETECTING) { 725 Slog.i(LOG_TAG, "onActionTap()"); 726 } 727 728 if (!mMagnificationController.isMagnifying()) { 729 final float targetScale = mMagnificationController.getPersistedScale(); 730 final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE); 731 mMagnificationController.setScaleAndCenter(scale, up.getX(), up.getY(), true, 732 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); 733 } else { 734 mMagnificationController.reset(true); 735 } 736 } 737 738 private void onActionTapAndHold(MotionEvent down, int policyFlags) { 739 if (DEBUG_DETECTING) { 740 Slog.i(LOG_TAG, "onActionTapAndHold()"); 741 } 742 743 clear(); 744 mTranslationEnabledBeforePan = mMagnificationController.isMagnifying(); 745 746 final float targetScale = mMagnificationController.getPersistedScale(); 747 final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE); 748 mMagnificationController.setScaleAndCenter(scale, down.getX(), down.getY(), true, 749 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); 750 751 transitionToState(STATE_VIEWPORT_DRAGGING); 752 } 753 } 754 755 private static final class MotionEventInfo { 756 757 private static final int MAX_POOL_SIZE = 10; 758 759 private static final Object sLock = new Object(); 760 761 private static MotionEventInfo sPool; 762 763 private static int sPoolSize; 764 765 private MotionEventInfo mNext; 766 767 private boolean mInPool; 768 769 public MotionEvent mEvent; 770 771 public MotionEvent mRawEvent; 772 773 public int mPolicyFlags; 774 775 public static MotionEventInfo obtain(MotionEvent event, MotionEvent rawEvent, 776 int policyFlags) { 777 synchronized (sLock) { 778 MotionEventInfo info; 779 if (sPoolSize > 0) { 780 sPoolSize--; 781 info = sPool; 782 sPool = info.mNext; 783 info.mNext = null; 784 info.mInPool = false; 785 } else { 786 info = new MotionEventInfo(); 787 } 788 info.initialize(event, rawEvent, policyFlags); 789 return info; 790 } 791 } 792 793 private void initialize(MotionEvent event, MotionEvent rawEvent, 794 int policyFlags) { 795 mEvent = MotionEvent.obtain(event); 796 mRawEvent = MotionEvent.obtain(rawEvent); 797 mPolicyFlags = policyFlags; 798 } 799 800 public void recycle() { 801 synchronized (sLock) { 802 if (mInPool) { 803 throw new IllegalStateException("Already recycled."); 804 } 805 clear(); 806 if (sPoolSize < MAX_POOL_SIZE) { 807 sPoolSize++; 808 mNext = sPool; 809 sPool = this; 810 mInPool = true; 811 } 812 } 813 } 814 815 private void clear() { 816 mEvent.recycle(); 817 mEvent = null; 818 mRawEvent.recycle(); 819 mRawEvent = null; 820 mPolicyFlags = 0; 821 } 822 } 823} 824