• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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