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