1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.quickstep.inputconsumers; 17 18 import static android.view.MotionEvent.ACTION_CANCEL; 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 import static android.view.MotionEvent.ACTION_UP; 24 import static android.view.MotionEvent.INVALID_POINTER_ID; 25 26 import static com.android.launcher3.PagedView.ACTION_MOVE_ALLOW_EASY_FLING; 27 import static com.android.launcher3.PagedView.DEBUG_FAILED_QUICKSWITCH; 28 import static com.android.launcher3.Utilities.EDGE_NAV_BAR; 29 import static com.android.launcher3.Utilities.squaredHypot; 30 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 31 import static com.android.launcher3.util.VelocityUtils.PX_PER_MS; 32 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID; 33 34 import android.content.Context; 35 import android.content.ContextWrapper; 36 import android.content.Intent; 37 import android.graphics.PointF; 38 import android.util.Log; 39 import android.view.MotionEvent; 40 import android.view.VelocityTracker; 41 import android.window.TransitionInfo; 42 43 import androidx.annotation.UiThread; 44 45 import com.android.launcher3.R; 46 import com.android.launcher3.Utilities; 47 import com.android.launcher3.testing.TestLogging; 48 import com.android.launcher3.testing.shared.TestProtocol; 49 import com.android.launcher3.util.Preconditions; 50 import com.android.launcher3.util.TraceHelper; 51 import com.android.quickstep.AbsSwipeUpHandler; 52 import com.android.quickstep.AbsSwipeUpHandler.Factory; 53 import com.android.quickstep.GestureState; 54 import com.android.quickstep.InputConsumer; 55 import com.android.quickstep.RecentsAnimationCallbacks; 56 import com.android.quickstep.RecentsAnimationController; 57 import com.android.quickstep.RecentsAnimationDeviceState; 58 import com.android.quickstep.RecentsAnimationTargets; 59 import com.android.quickstep.RotationTouchHelper; 60 import com.android.quickstep.TaskAnimationManager; 61 import com.android.quickstep.util.ActiveGestureProtoLogProxy; 62 import com.android.quickstep.util.CachedEventDispatcher; 63 import com.android.quickstep.util.MotionPauseDetector; 64 import com.android.quickstep.util.NavBarPosition; 65 import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver; 66 import com.android.systemui.shared.system.InputMonitorCompat; 67 68 import java.util.function.Consumer; 69 70 /** 71 * Input consumer for handling events originating from an activity other than Launcher 72 */ 73 public class OtherActivityInputConsumer extends ContextWrapper implements InputConsumer { 74 75 private static final String TAG = "OtherActivityInputConsumer"; 76 private static final boolean DEBUG = true; 77 78 public static final String DOWN_EVT = "OtherActivityInputConsumer.DOWN"; 79 private static final String UP_EVT = "OtherActivityInputConsumer.UP"; 80 81 // Minimum angle of a gesture's coordinate where a release goes to overview. 82 public static final int OVERVIEW_MIN_DEGREES = 15; 83 84 private final RecentsAnimationDeviceState mDeviceState; 85 private final NavBarPosition mNavBarPosition; 86 private final TaskAnimationManager mTaskAnimationManager; 87 private final GestureState mGestureState; 88 private final RotationTouchHelper mRotationTouchHelper; 89 private RecentsAnimationCallbacks mActiveCallbacks; 90 private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher(); 91 private final InputMonitorCompat mInputMonitorCompat; 92 private final InputEventReceiver mInputEventReceiver; 93 private final AbsSwipeUpHandler.Factory mHandlerFactory; 94 95 private final Consumer<OtherActivityInputConsumer> mOnCompleteCallback; 96 private final MotionPauseDetector mMotionPauseDetector; 97 private final float mMotionPauseMinDisplacement; 98 99 private VelocityTracker mVelocityTracker; 100 101 private AbsSwipeUpHandler mInteractionHandler; 102 private final FinishImmediatelyHandler mCleanupHandler = new FinishImmediatelyHandler(); 103 104 private final boolean mIsDeferredDownTarget; 105 private final PointF mDownPos = new PointF(); 106 private final PointF mLastPos = new PointF(); 107 private int mActivePointerId = INVALID_POINTER_ID; 108 109 // Distance after which we start dragging the window. 110 private final float mTouchSlop; 111 112 private final float mSquaredTouchSlop; 113 private final boolean mDisableHorizontalSwipe; 114 115 // Slop used to check when we start moving window. 116 private boolean mPassedWindowMoveSlop; 117 // Slop used to determine when we say that the gesture has started. 118 private boolean mPassedPilferInputSlop; 119 // Same as mPassedPilferInputSlop, except when continuing a gesture mPassedPilferInputSlop is 120 // initially true while this one is false. 121 private boolean mPassedSlopOnThisGesture; 122 123 // Might be displacement in X or Y, depending on the direction we are swiping from the nav bar. 124 private float mStartDisplacement; 125 126 // The callback called upon finishing the recents transition if it was force-canceled 127 private Runnable mForceFinishRecentsTransitionCallback; 128 OtherActivityInputConsumer( Context base, RecentsAnimationDeviceState deviceState, TaskAnimationManager taskAnimationManager, GestureState gestureState, boolean isDeferredDownTarget, Consumer<OtherActivityInputConsumer> onCompleteCallback, InputMonitorCompat inputMonitorCompat, InputEventReceiver inputEventReceiver, boolean disableHorizontalSwipe, Factory handlerFactory)129 public OtherActivityInputConsumer( 130 Context base, 131 RecentsAnimationDeviceState deviceState, 132 TaskAnimationManager taskAnimationManager, 133 GestureState gestureState, 134 boolean isDeferredDownTarget, 135 Consumer<OtherActivityInputConsumer> onCompleteCallback, 136 InputMonitorCompat inputMonitorCompat, 137 InputEventReceiver inputEventReceiver, 138 boolean disableHorizontalSwipe, 139 Factory handlerFactory) { 140 super(base); 141 mDeviceState = deviceState; 142 mNavBarPosition = mDeviceState.getNavBarPosition(); 143 mTaskAnimationManager = taskAnimationManager; 144 mGestureState = gestureState; 145 mHandlerFactory = handlerFactory; 146 147 mMotionPauseDetector = new MotionPauseDetector(base, false, 148 mNavBarPosition.isLeftEdge() || mNavBarPosition.isRightEdge() 149 ? MotionEvent.AXIS_X : MotionEvent.AXIS_Y); 150 mMotionPauseMinDisplacement = base.getResources().getDimension( 151 R.dimen.motion_pause_detector_min_displacement_from_app); 152 mOnCompleteCallback = onCompleteCallback; 153 mVelocityTracker = VelocityTracker.obtain(); 154 mInputMonitorCompat = inputMonitorCompat; 155 mInputEventReceiver = inputEventReceiver; 156 157 boolean continuingPreviousGesture = mTaskAnimationManager.isRecentsAnimationRunning(); 158 mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget; 159 160 mTouchSlop = mDeviceState.getTouchSlop(); 161 mSquaredTouchSlop = mDeviceState.getSquaredTouchSlop(); 162 163 mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture; 164 mStartDisplacement = continuingPreviousGesture ? 0 : -mTouchSlop; 165 mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe; 166 mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(this); 167 } 168 169 @Override getType()170 public int getType() { 171 return TYPE_OTHER_ACTIVITY; 172 } 173 174 @Override getDisplayId()175 public int getDisplayId() { 176 return mGestureState.getDisplayId(); 177 } 178 179 @Override isConsumerDetachedFromGesture()180 public boolean isConsumerDetachedFromGesture() { 181 return true; 182 } 183 forceCancelGesture(MotionEvent ev)184 private void forceCancelGesture(MotionEvent ev) { 185 int action = ev.getAction(); 186 ev.setAction(ACTION_CANCEL); 187 finishTouchTracking(ev); 188 ev.setAction(action); 189 } 190 191 @Override onMotionEvent(MotionEvent ev)192 public void onMotionEvent(MotionEvent ev) { 193 if (mVelocityTracker == null) { 194 return; 195 } 196 197 // Proxy events to recents view 198 if (mPassedWindowMoveSlop && mInteractionHandler != null 199 && !mRecentsViewDispatcher.hasConsumer()) { 200 mRecentsViewDispatcher.setConsumer(mInteractionHandler 201 .getRecentsViewDispatcher(mNavBarPosition.getRotation())); 202 int action = ev.getAction(); 203 ev.setAction(ACTION_MOVE_ALLOW_EASY_FLING); 204 mRecentsViewDispatcher.dispatchEvent(ev); 205 ev.setAction(action); 206 } 207 int edgeFlags = ev.getEdgeFlags(); 208 ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR); 209 210 if (mGestureState.isTrackpadGesture()) { 211 // Disable scrolling in RecentsView for 3-finger trackpad gesture. We don't know if a 212 // trackpad motion event is 3-finger or 4-finger with the U API until ACTION_MOVE (we 213 // skip ACTION_POINTER_UP events in TouchInteractionService), so in order to make sure 214 // that RecentsView always get a closed sequence of motion events and yet disable 215 // 3-finger scroll, we do the following (1) always dispatch ACTION_DOWN and ACTION_UP 216 // trackpad multi-finger motion events. (2) only dispatch 4-finger ACTION_MOVE motion 217 // events. 218 switch (ev.getActionMasked()) { 219 case ACTION_MOVE -> { 220 if (mGestureState.isFourFingerTrackpadGesture()) { 221 mRecentsViewDispatcher.dispatchEvent(ev); 222 } 223 } 224 default -> mRecentsViewDispatcher.dispatchEvent(ev); 225 } 226 } else { 227 mRecentsViewDispatcher.dispatchEvent(ev); 228 } 229 ev.setEdgeFlags(edgeFlags); 230 231 mVelocityTracker.addMovement(ev); 232 if (ev.getActionMasked() == ACTION_POINTER_UP) { 233 mVelocityTracker.clear(); 234 mMotionPauseDetector.clear(); 235 } 236 237 switch (ev.getActionMasked()) { 238 case ACTION_DOWN: { 239 // Until we detect the gesture, handle events as we receive them 240 mInputEventReceiver.setBatchingEnabled(false); 241 242 TraceHelper.INSTANCE.beginSection(DOWN_EVT); 243 mActivePointerId = ev.getPointerId(0); 244 mDownPos.set(ev.getX(), ev.getY()); 245 mLastPos.set(mDownPos); 246 247 // Start the window animation on down to give more time for launcher to draw if the 248 // user didn't start the gesture over the back button 249 if (DEBUG) { 250 Log.d(TAG, "ACTION_DOWN: mIsDeferredDownTarget=" + mIsDeferredDownTarget); 251 } 252 if (!mIsDeferredDownTarget) { 253 startTouchTrackingForWindowAnimation(ev.getEventTime()); 254 } 255 256 TraceHelper.INSTANCE.endSection(); 257 break; 258 } 259 case ACTION_POINTER_DOWN: { 260 if (!mPassedPilferInputSlop) { 261 // Cancel interaction in case of multi-touch interaction 262 int ptrIdx = ev.getActionIndex(); 263 if (!mRotationTouchHelper.isInSwipeUpTouchRegion(ev, ptrIdx)) { 264 forceCancelGesture(ev); 265 } 266 } 267 break; 268 } 269 case ACTION_POINTER_UP: { 270 int ptrIdx = ev.getActionIndex(); 271 int ptrId = ev.getPointerId(ptrIdx); 272 if (ptrId == mActivePointerId) { 273 final int newPointerIdx = ptrIdx == 0 ? 1 : 0; 274 mDownPos.set( 275 ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x), 276 ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y)); 277 mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx)); 278 mActivePointerId = ev.getPointerId(newPointerIdx); 279 } 280 break; 281 } 282 case ACTION_MOVE: { 283 int pointerIndex = ev.findPointerIndex(mActivePointerId); 284 if (pointerIndex == INVALID_POINTER_ID) { 285 break; 286 } 287 mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); 288 float displacement = getDisplacement(ev); 289 float displacementX = mLastPos.x - mDownPos.x; 290 float displacementY = mLastPos.y - mDownPos.y; 291 292 if (!mPassedWindowMoveSlop) { 293 if (!mIsDeferredDownTarget) { 294 // Normal gesture, ensure we pass the drag slop before we start tracking 295 // the gesture 296 if (mGestureState.isTrackpadGesture() || Math.abs(displacement) 297 > mTouchSlop) { 298 mPassedWindowMoveSlop = true; 299 mStartDisplacement = -mTouchSlop; 300 } 301 } 302 } 303 304 float horizontalDist = Math.abs(displacementX); 305 float upDist = -displacement; 306 boolean isTrackpadGesture = mGestureState.isTrackpadGesture(); 307 float squaredHypot = squaredHypot(displacementX, displacementY); 308 boolean isInExtendedSlopRegion = mGestureState.isInExtendedSlopRegion(); 309 boolean passedSlop = isTrackpadGesture 310 || (squaredHypot >= mSquaredTouchSlop 311 && !isInExtendedSlopRegion); 312 if (DEBUG) { 313 Log.d(TAG, "ACTION_MOVE: passedSlop=" + passedSlop 314 + " ( " + isTrackpadGesture 315 + " || (" + squaredHypot + " >= " + mSquaredTouchSlop 316 + " && " + !isInExtendedSlopRegion + " ))"); 317 } 318 319 if (!mPassedSlopOnThisGesture && passedSlop) { 320 mPassedSlopOnThisGesture = true; 321 } 322 // Until passing slop, we don't know what direction we're going, so assume 323 // we're quick switching to avoid translating recents away when continuing 324 // the gesture (in which case mPassedPilferInputSlop starts as true). 325 boolean haveNotPassedSlopOnContinuedGesture = 326 !mPassedSlopOnThisGesture && mPassedPilferInputSlop; 327 double degrees = Math.toDegrees(Math.atan(upDist / horizontalDist)); 328 329 // Regarding degrees >= -OVERVIEW_MIN_DEGREES - Trackpad gestures can start anywhere 330 // on the screen, allowing downward swipes. We want to impose the same angle in that 331 // scenario. 332 boolean swipeWithinQuickSwitchRange = degrees <= OVERVIEW_MIN_DEGREES 333 && (!mGestureState.isTrackpadGesture() || degrees >= -OVERVIEW_MIN_DEGREES); 334 boolean isLikelyToStartNewTask = 335 haveNotPassedSlopOnContinuedGesture || swipeWithinQuickSwitchRange; 336 337 if (DEBUG) { 338 Log.d(TAG, "ACTION_MOVE: mPassedPilferInputSlop=" + mPassedPilferInputSlop); 339 } 340 if (!mPassedPilferInputSlop) { 341 if (passedSlop) { 342 // Horizontal gesture is not allowed in this region 343 boolean isHorizontalSwipeWhenDisabled = 344 (mDisableHorizontalSwipe && Math.abs(displacementX) > Math.abs( 345 displacementY)); 346 // Do not allow quick switch for trackpad 3-finger gestures 347 // TODO(b/261815244): might need to impose stronger conditions for the swipe 348 // angle 349 boolean noQuickSwitchForThreeFingerGesture = isLikelyToStartNewTask 350 && mGestureState.isThreeFingerTrackpadGesture(); 351 boolean noQuickstepForFourFingerGesture = !isLikelyToStartNewTask 352 && mGestureState.isFourFingerTrackpadGesture(); 353 if (isHorizontalSwipeWhenDisabled || noQuickSwitchForThreeFingerGesture 354 || noQuickstepForFourFingerGesture) { 355 forceCancelGesture(ev); 356 break; 357 } 358 359 mPassedPilferInputSlop = true; 360 361 if (mIsDeferredDownTarget) { 362 // Deferred gesture, start the animation and gesture tracking once 363 // we pass the actual touch slop 364 startTouchTrackingForWindowAnimation(ev.getEventTime()); 365 } 366 if (!mPassedWindowMoveSlop) { 367 mPassedWindowMoveSlop = true; 368 mStartDisplacement = -mTouchSlop; 369 } 370 notifyGestureStarted(isLikelyToStartNewTask); 371 } 372 } 373 374 if (mInteractionHandler != null) { 375 if (mPassedWindowMoveSlop) { 376 // Move 377 mInteractionHandler.updateDisplacement(displacement - mStartDisplacement); 378 } 379 380 if (mDeviceState.isFullyGesturalNavMode() 381 || mGestureState.isTrackpadGesture()) { 382 boolean minSwipeMet = upDist >= Math.max(mMotionPauseMinDisplacement, 383 mInteractionHandler.getThresholdToAllowMotionPause()); 384 mInteractionHandler.setCanSlowSwipeGoHome(minSwipeMet); 385 mMotionPauseDetector.setDisallowPause(!minSwipeMet 386 || isLikelyToStartNewTask); 387 mMotionPauseDetector.addPosition(ev); 388 mInteractionHandler.setIsLikelyToStartNewTask(isLikelyToStartNewTask); 389 } 390 } 391 break; 392 } 393 case ACTION_CANCEL: 394 case ACTION_UP: { 395 if (DEBUG_FAILED_QUICKSWITCH && !mPassedWindowMoveSlop) { 396 float displacementX = mLastPos.x - mDownPos.x; 397 float displacementY = mLastPos.y - mDownPos.y; 398 Log.d("Quickswitch", "mPassedWindowMoveSlop=false" 399 + " disp=" + squaredHypot(displacementX, displacementY) 400 + " slop=" + mSquaredTouchSlop); 401 } 402 finishTouchTracking(ev); 403 break; 404 } 405 } 406 } 407 notifyGestureStarted(boolean isLikelyToStartNewTask)408 private void notifyGestureStarted(boolean isLikelyToStartNewTask) { 409 if (mInteractionHandler == null) { 410 return; 411 } 412 TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers"); 413 mInputMonitorCompat.pilferPointers(); 414 // Once we detect the gesture, we can enable batching to reduce further updates 415 mInputEventReceiver.setBatchingEnabled(true); 416 417 // Notify the handler that the gesture has actually started 418 mInteractionHandler.onGestureStarted(isLikelyToStartNewTask); 419 } 420 startTouchTrackingForWindowAnimation(long touchTimeMs)421 private void startTouchTrackingForWindowAnimation(long touchTimeMs) { 422 mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs); 423 mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished); 424 mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler.getMotionPauseListener()); 425 mMotionPauseDetector.setIsTrackpadGesture(mGestureState.isTrackpadGesture()); 426 mInteractionHandler.initWhenReady( 427 "OtherActivityInputConsumer.startTouchTrackingForWindowAnimation"); 428 ActiveGestureProtoLogProxy.logGestureStartSwipeHandler( 429 mInteractionHandler.getClass().getSimpleName()); 430 431 if (DEBUG) { 432 Log.d(TAG, "startTouchTrackingForWindowAnimation: isRecentsAnimationRunning=" 433 + mTaskAnimationManager.isRecentsAnimationRunning()); 434 } 435 if (mTaskAnimationManager.isRecentsAnimationRunning()) { 436 mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState); 437 mActiveCallbacks.removeListener(mCleanupHandler); 438 mActiveCallbacks.addListener(mInteractionHandler); 439 mTaskAnimationManager.notifyRecentsAnimationState(mInteractionHandler); 440 notifyGestureStarted(true /*isLikelyToStartNewTask*/); 441 } else { 442 Intent intent = new Intent(mInteractionHandler.getLaunchIntent()); 443 intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId()); 444 mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(mGestureState, intent, 445 mInteractionHandler); 446 } 447 } 448 449 /** 450 * Returns whether this input consumer has started touch tracking (if touch tracking is not 451 * deferred). 452 */ hasStartedTouchTracking()453 public boolean hasStartedTouchTracking() { 454 return mInteractionHandler != null; 455 } 456 457 /** 458 * Called when the gesture has ended. Does not correlate to the completion of the interaction as 459 * the animation can still be running. 460 */ finishTouchTracking(MotionEvent ev)461 private void finishTouchTracking(MotionEvent ev) { 462 TraceHelper.INSTANCE.beginSection(UP_EVT); 463 if (DEBUG) { 464 Log.d(TAG, "finishTouchTracking: mPassedWindowMoveSlop=" + mPassedWindowMoveSlop); 465 Log.d(TAG, "finishTouchTracking: mInteractionHandler=" + mInteractionHandler); 466 Log.d(TAG, "finishTouchTracking: ev=" + ev); 467 } 468 469 boolean isCanceled = ev.getActionMasked() == ACTION_CANCEL; 470 if (mPassedWindowMoveSlop && mInteractionHandler != null) { 471 if (isCanceled) { 472 mInteractionHandler.onGestureCancelled(); 473 } else { 474 mVelocityTracker.computeCurrentVelocity(PX_PER_MS); 475 float velocityXPxPerMs = mVelocityTracker.getXVelocity(mActivePointerId); 476 float velocityYPxPerMs = mVelocityTracker.getYVelocity(mActivePointerId); 477 float velocityPxPerMs = mNavBarPosition.isRightEdge() 478 ? velocityXPxPerMs 479 : mNavBarPosition.isLeftEdge() 480 ? -velocityXPxPerMs 481 : velocityYPxPerMs; 482 mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement); 483 mInteractionHandler.onGestureEnded(velocityPxPerMs, 484 new PointF(velocityXPxPerMs, velocityYPxPerMs), 485 Math.abs(mDownPos.x - mLastPos.x) > mTouchSlop); 486 } 487 } else { 488 // Since we start touch tracking on DOWN, we may reach this state without actually 489 // starting the gesture. In that case, we need to clean-up an unfinished or un-started 490 // animation. 491 if (DEBUG) { 492 Log.d(TAG, "finishTouchTracking: mActiveCallbacks=" + mActiveCallbacks); 493 } 494 if (mActiveCallbacks != null && mInteractionHandler != null) { 495 if (DEBUG) { 496 Log.d(TAG, "finishTouchTracking: isRecentsAnimationRunning=" 497 + mTaskAnimationManager.isRecentsAnimationRunning()); 498 } 499 if (mTaskAnimationManager.isRecentsAnimationRunning()) { 500 // The animation started, but with no movement, in this case, there will be no 501 // animateToProgress so we have to manually finish here. In the case of 502 // ACTION_CANCEL, someone else may be doing something so finish synchronously. 503 mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */, 504 isCanceled /* forceFinish */, mForceFinishRecentsTransitionCallback); 505 } else { 506 // The animation hasn't started yet, so insert a replacement handler into the 507 // callbacks which immediately finishes the animation after it starts. 508 mActiveCallbacks.addListener(mCleanupHandler); 509 } 510 } 511 onConsumerAboutToBeSwitched(); 512 onInteractionGestureFinished(); 513 } 514 cleanupAfterGesture(); 515 TraceHelper.INSTANCE.endSection(); 516 } 517 cleanupAfterGesture()518 private void cleanupAfterGesture() { 519 if (mVelocityTracker != null) { 520 mVelocityTracker.recycle(); 521 mVelocityTracker = null; 522 } 523 mMotionPauseDetector.clear(); 524 // Clear ref to recents view and launcher activity on action up or cancel to avoid leak 525 mRecentsViewDispatcher.clearConsumer(); 526 } 527 528 @Override notifyOrientationSetup()529 public void notifyOrientationSetup() { 530 mRotationTouchHelper.onStartGesture(); 531 } 532 533 @Override onConsumerAboutToBeSwitched()534 public void onConsumerAboutToBeSwitched() { 535 Preconditions.assertUIThread(); 536 if (mInteractionHandler != null) { 537 // The consumer is being switched while we are active. Set up the shared state to be 538 // used by the next animation 539 removeListener(); 540 mInteractionHandler.onConsumerAboutToBeSwitched(); 541 } 542 } 543 544 @UiThread onInteractionGestureFinished()545 private void onInteractionGestureFinished() { 546 Preconditions.assertUIThread(); 547 removeListener(); 548 mInteractionHandler = null; 549 cleanupAfterGesture(); 550 mOnCompleteCallback.accept(this); 551 } 552 removeListener()553 private void removeListener() { 554 if (mActiveCallbacks != null && mInteractionHandler != null) { 555 mActiveCallbacks.removeListener(mInteractionHandler); 556 } 557 } 558 getDisplacement(MotionEvent ev)559 private float getDisplacement(MotionEvent ev) { 560 if (mNavBarPosition.isRightEdge()) { 561 return ev.getX() - mDownPos.x; 562 } else if (mNavBarPosition.isLeftEdge()) { 563 return mDownPos.x - ev.getX(); 564 } else { 565 return ev.getY() - mDownPos.y; 566 } 567 } 568 569 @Override allowInterceptByParent()570 public boolean allowInterceptByParent() { 571 return !mPassedPilferInputSlop; 572 } 573 574 /** 575 * Sets a callback to be called when the recents transition is force-canceled by another input 576 * consumer being made active. 577 */ setForceFinishRecentsTransitionCallback(Runnable r)578 public void setForceFinishRecentsTransitionCallback(Runnable r) { 579 mForceFinishRecentsTransitionCallback = r; 580 } 581 582 /** 583 * A listener which just finishes the animation immediately after starting. Replaces 584 * AbsSwipeUpHandler if the gesture itself finishes before the animation even starts. 585 */ 586 private static class FinishImmediatelyHandler 587 implements RecentsAnimationCallbacks.RecentsAnimationListener { 588 589 @Override onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets, TransitionInfo transitionInfo)590 public void onRecentsAnimationStart(RecentsAnimationController controller, 591 RecentsAnimationTargets targets, TransitionInfo transitionInfo) { 592 if (DEBUG) { 593 Log.d(TAG, "FinishImmediatelyHandler: queuing callback"); 594 } 595 Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> { 596 if (DEBUG) { 597 Log.d(TAG, "FinishImmediatelyHandler: running callback"); 598 } 599 controller.finish(false /* toRecents */, null); 600 }); 601 } 602 } 603 } 604