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;
17 
18 import static android.view.Surface.ROTATION_0;
19 import static android.view.Surface.ROTATION_270;
20 import static android.view.Surface.ROTATION_90;
21 import static android.widget.Toast.LENGTH_SHORT;
22 
23 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
24 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
25 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
26 import static com.android.launcher3.anim.Interpolators.DEACCEL;
27 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
28 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
29 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
30 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
31 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
32 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
33 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
34 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
35 import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
36 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
37 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
38 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
39 import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
40 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
41 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
42 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
43 import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
44 import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
45 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_CANCELED;
46 import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
47 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
48 import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
49 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
50 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
51 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
52 
53 import android.animation.Animator;
54 import android.animation.AnimatorListenerAdapter;
55 import android.animation.AnimatorSet;
56 import android.animation.ValueAnimator;
57 import android.annotation.TargetApi;
58 import android.app.Activity;
59 import android.app.ActivityManager;
60 import android.content.Context;
61 import android.content.Intent;
62 import android.graphics.Matrix;
63 import android.graphics.PointF;
64 import android.graphics.Rect;
65 import android.graphics.RectF;
66 import android.os.Build;
67 import android.os.IBinder;
68 import android.os.SystemClock;
69 import android.view.MotionEvent;
70 import android.view.View;
71 import android.view.View.OnApplyWindowInsetsListener;
72 import android.view.ViewTreeObserver.OnDrawListener;
73 import android.view.ViewTreeObserver.OnScrollChangedListener;
74 import android.view.WindowInsets;
75 import android.view.animation.Interpolator;
76 import android.widget.Toast;
77 import android.window.PictureInPictureSurfaceTransaction;
78 
79 import androidx.annotation.Nullable;
80 import androidx.annotation.UiThread;
81 
82 import com.android.launcher3.AbstractFloatingView;
83 import com.android.launcher3.DeviceProfile;
84 import com.android.launcher3.R;
85 import com.android.launcher3.Utilities;
86 import com.android.launcher3.anim.AnimationSuccessListener;
87 import com.android.launcher3.anim.AnimatorPlaybackController;
88 import com.android.launcher3.logging.StatsLogManager;
89 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
90 import com.android.launcher3.statemanager.BaseState;
91 import com.android.launcher3.statemanager.StatefulActivity;
92 import com.android.launcher3.tracing.InputConsumerProto;
93 import com.android.launcher3.tracing.SwipeHandlerProto;
94 import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
95 import com.android.launcher3.util.TraceHelper;
96 import com.android.launcher3.util.WindowBounds;
97 import com.android.quickstep.BaseActivityInterface.AnimationFactory;
98 import com.android.quickstep.GestureState.GestureEndTarget;
99 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
100 import com.android.quickstep.util.ActiveGestureLog;
101 import com.android.quickstep.util.ActivityInitListener;
102 import com.android.quickstep.util.AnimatorControllerWithResistance;
103 import com.android.quickstep.util.InputConsumerProxy;
104 import com.android.quickstep.util.InputProxyHandlerFactory;
105 import com.android.quickstep.util.LauncherSplitScreenListener;
106 import com.android.quickstep.util.MotionPauseDetector;
107 import com.android.quickstep.util.ProtoTracer;
108 import com.android.quickstep.util.RecentsOrientedState;
109 import com.android.quickstep.util.RectFSpringAnim;
110 import com.android.quickstep.util.StaggeredWorkspaceAnim;
111 import com.android.quickstep.util.SurfaceTransactionApplier;
112 import com.android.quickstep.util.SwipePipToHomeAnimator;
113 import com.android.quickstep.util.TaskViewSimulator;
114 import com.android.quickstep.util.VibratorWrapper;
115 import com.android.quickstep.views.RecentsView;
116 import com.android.quickstep.views.TaskView;
117 import com.android.systemui.shared.recents.model.ThumbnailData;
118 import com.android.systemui.shared.system.ActivityManagerWrapper;
119 import com.android.systemui.shared.system.InputConsumerController;
120 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
121 import com.android.systemui.shared.system.LatencyTrackerCompat;
122 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
123 import com.android.systemui.shared.system.TaskStackChangeListener;
124 import com.android.systemui.shared.system.TaskStackChangeListeners;
125 
126 import java.util.ArrayList;
127 import java.util.Arrays;
128 import java.util.HashMap;
129 import java.util.function.Consumer;
130 
131 /**
132  * Handles the navigation gestures when Launcher is the default home activity.
133  */
134 @TargetApi(Build.VERSION_CODES.R)
135 public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
136         Q extends RecentsView, S extends BaseState<S>>
137         extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener,
138         RecentsAnimationCallbacks.RecentsAnimationListener {
139     private static final String TAG = "AbsSwipeUpHandler";
140 
141     private static final String[] STATE_NAMES = DEBUG_STATES ? new String[17] : null;
142 
143     protected final BaseActivityInterface<S, T> mActivityInterface;
144     protected final InputConsumerProxy mInputConsumerProxy;
145     protected final ActivityInitListener mActivityInitListener;
146     // Callbacks to be made once the recents animation starts
147     private final ArrayList<Runnable> mRecentsAnimationStartCallbacks = new ArrayList<>();
148     private final OnScrollChangedListener mOnRecentsScrollListener = this::onRecentsViewScroll;
149 
150     // Null if the recents animation hasn't started yet or has been canceled or finished.
151     protected @Nullable RecentsAnimationController mRecentsAnimationController;
152     protected RecentsAnimationTargets mRecentsAnimationTargets;
153     protected T mActivity;
154     protected Q mRecentsView;
155     protected Runnable mGestureEndCallback;
156     protected MultiStateCallback mStateCallback;
157     protected boolean mCanceled;
158     private boolean mRecentsViewScrollLinked = false;
159     private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks =
160             new ActivityLifecycleCallbacksAdapter() {
161                 @Override
162                 public void onActivityDestroyed(Activity activity) {
163                     if (mActivity != activity) {
164                         return;
165                     }
166                     mRecentsView = null;
167                     mActivity = null;
168                 }
169             };
170 
getFlagForIndex(int index, String name)171     private static int getFlagForIndex(int index, String name) {
172         if (DEBUG_STATES) {
173             STATE_NAMES[index] = name;
174         }
175         return 1 << index;
176     }
177 
178     // Launcher UI related states
179     protected static final int STATE_LAUNCHER_PRESENT =
180             getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
181     protected static final int STATE_LAUNCHER_STARTED =
182             getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
183     protected static final int STATE_LAUNCHER_DRAWN =
184             getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
185     // Called when the Launcher has connected to the touch interaction service (and the taskbar
186     // ui controller is initialized)
187     protected static final int STATE_LAUNCHER_BIND_TO_SERVICE =
188             getFlagForIndex(3, "STATE_LAUNCHER_BIND_TO_SERVICE");
189 
190     // Internal initialization states
191     private static final int STATE_APP_CONTROLLER_RECEIVED =
192             getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
193 
194     // Interaction finish states
195     private static final int STATE_SCALED_CONTROLLER_HOME =
196             getFlagForIndex(5, "STATE_SCALED_CONTROLLER_HOME");
197     private static final int STATE_SCALED_CONTROLLER_RECENTS =
198             getFlagForIndex(6, "STATE_SCALED_CONTROLLER_RECENTS");
199 
200     protected static final int STATE_HANDLER_INVALIDATED =
201             getFlagForIndex(7, "STATE_HANDLER_INVALIDATED");
202     private static final int STATE_GESTURE_STARTED =
203             getFlagForIndex(8, "STATE_GESTURE_STARTED");
204     private static final int STATE_GESTURE_CANCELLED =
205             getFlagForIndex(9, "STATE_GESTURE_CANCELLED");
206     private static final int STATE_GESTURE_COMPLETED =
207             getFlagForIndex(10, "STATE_GESTURE_COMPLETED");
208 
209     private static final int STATE_CAPTURE_SCREENSHOT =
210             getFlagForIndex(11, "STATE_CAPTURE_SCREENSHOT");
211     protected static final int STATE_SCREENSHOT_CAPTURED =
212             getFlagForIndex(12, "STATE_SCREENSHOT_CAPTURED");
213     private static final int STATE_SCREENSHOT_VIEW_SHOWN =
214             getFlagForIndex(13, "STATE_SCREENSHOT_VIEW_SHOWN");
215 
216     private static final int STATE_RESUME_LAST_TASK =
217             getFlagForIndex(14, "STATE_RESUME_LAST_TASK");
218     private static final int STATE_START_NEW_TASK =
219             getFlagForIndex(15, "STATE_START_NEW_TASK");
220     private static final int STATE_CURRENT_TASK_FINISHED =
221             getFlagForIndex(16, "STATE_CURRENT_TASK_FINISHED");
222     private static final int STATE_FINISH_WITH_NO_END =
223             getFlagForIndex(17, "STATE_FINISH_WITH_NO_END");
224 
225     private static final int LAUNCHER_UI_STATES =
226             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED |
227                     STATE_LAUNCHER_BIND_TO_SERVICE;
228 
229     public static final long MAX_SWIPE_DURATION = 350;
230     public static final long HOME_DURATION = StaggeredWorkspaceAnim.DURATION_MS;
231 
232     public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
233     private static final float SWIPE_DURATION_MULTIPLIER =
234             Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
235     private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured";
236 
237     public static final long RECENTS_ATTACH_DURATION = 300;
238 
239     private static final float MAX_QUICK_SWITCH_RECENTS_SCALE_PROGRESS = 0.07f;
240 
241     /**
242      * Used as the page index for logging when we return to the last task at the end of the gesture.
243      */
244     private static final int LOG_NO_OP_PAGE_INDEX = -1;
245 
246     protected final TaskAnimationManager mTaskAnimationManager;
247 
248     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
249     private RunningWindowAnim[] mRunningWindowAnim;
250     // Possible second animation running at the same time as mRunningWindowAnim
251     private Animator mParallelRunningAnim;
252     private boolean mIsMotionPaused;
253     private boolean mHasMotionEverBeenPaused;
254 
255     private boolean mContinuingLastGesture;
256 
257     private ThumbnailData mTaskSnapshot;
258 
259     // Used to control launcher components throughout the swipe gesture.
260     private AnimatorControllerWithResistance mLauncherTransitionController;
261     private boolean mHasEndedLauncherTransition;
262 
263     private AnimationFactory mAnimationFactory = (t) -> { };
264 
265     private boolean mWasLauncherAlreadyVisible;
266 
267     private boolean mPassedOverviewThreshold;
268     private boolean mGestureStarted;
269     private boolean mLogDirectionUpOrLeft = true;
270     private PointF mDownPos;
271     private boolean mIsLikelyToStartNewTask;
272 
273     private final long mTouchTimeMs;
274     private long mLauncherFrameDrawnTime;
275 
276     private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
277 
278     private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
279     protected boolean mIsSwipingPipToHome;
280     // TODO(b/195473090) no split PIP for now, remove once we have more clarity
281     //  can try to have RectFSpringAnim evaluate multiple rects at once
282     private final SwipePipToHomeAnimator[] mSwipePipToHomeAnimators =
283             new SwipePipToHomeAnimator[2];
284 
285     // Interpolate RecentsView scale from start of quick switch scroll until this scroll threshold
286     private final float mQuickSwitchScaleScrollThreshold;
287 
AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState, TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs, boolean continuingLastGesture, InputConsumerController inputConsumer)288     public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
289             TaskAnimationManager taskAnimationManager, GestureState gestureState,
290             long touchTimeMs, boolean continuingLastGesture,
291             InputConsumerController inputConsumer) {
292         super(context, deviceState, gestureState);
293         mActivityInterface = gestureState.getActivityInterface();
294         mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
295         mInputConsumerProxy =
296                 new InputConsumerProxy(context,
297                         () -> mRecentsView.getPagedViewOrientedState().getRecentsActivityRotation(),
298                         inputConsumer, () -> {
299                     endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
300                     endLauncherTransitionController();
301                 }, new InputProxyHandlerFactory(mActivityInterface, mGestureState));
302         mTaskAnimationManager = taskAnimationManager;
303         mTouchTimeMs = touchTimeMs;
304         mContinuingLastGesture = continuingLastGesture;
305         mQuickSwitchScaleScrollThreshold = context.getResources().getDimension(
306                 R.dimen.quick_switch_scaling_scroll_threshold);
307 
308         initAfterSubclassConstructor();
309         initStateCallbacks();
310     }
311 
initStateCallbacks()312     private void initStateCallbacks() {
313         mStateCallback = new MultiStateCallback(STATE_NAMES);
314 
315         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
316                 this::onLauncherPresentAndGestureStarted);
317 
318         mStateCallback.runOnceAtState(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
319                 this::initializeLauncherAnimationController);
320 
321         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
322                 this::launcherFrameDrawn);
323 
324         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
325                         | STATE_GESTURE_CANCELLED,
326                 this::resetStateForAnimationCancel);
327 
328         mStateCallback.runOnceAtState(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
329                 this::resumeLastTask);
330         mStateCallback.runOnceAtState(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
331                 this::startNewTask);
332 
333         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
334                         | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT,
335                 this::switchToScreenshot);
336 
337         mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
338                         | STATE_SCALED_CONTROLLER_RECENTS,
339                 this::finishCurrentTransitionToRecents);
340 
341         mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
342                         | STATE_SCALED_CONTROLLER_HOME,
343                 this::finishCurrentTransitionToHome);
344         mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
345                 this::reset);
346 
347         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
348                         | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
349                         | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
350                         | STATE_GESTURE_STARTED,
351                 this::setupLauncherUiAfterSwipeUpToRecentsAnimation);
352 
353         mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED,
354                 this::continueComputingRecentsScrollIfNecessary);
355         mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED
356                         | STATE_RECENTS_SCROLLING_FINISHED,
357                 this::onSettledOnEndTarget);
358 
359         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
360         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
361                 this::invalidateHandlerWithLauncher);
362         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
363                 this::resetStateForAnimationCancel);
364         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_FINISH_WITH_NO_END,
365                 this::resetStateForAnimationCancel);
366 
367         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
368             mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
369                             | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
370                     (b) -> mRecentsView.setRunningTaskHidden(!b));
371         }
372     }
373 
onActivityInit(Boolean alreadyOnHome)374     protected boolean onActivityInit(Boolean alreadyOnHome) {
375         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
376             return false;
377         }
378 
379         T createdActivity = mActivityInterface.getCreatedActivity();
380         if (createdActivity != null) {
381             initTransitionEndpoints(createdActivity.getDeviceProfile());
382         }
383         final T activity = mActivityInterface.getCreatedActivity();
384         if (mActivity == activity) {
385             return true;
386         }
387 
388         if (mActivity != null) {
389             if (mStateCallback.hasStates(STATE_GESTURE_COMPLETED)) {
390                 // If the activity has restarted between setting the page scroll settling callback
391                 // and actually receiving the callback, just mark the gesture completed
392                 mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
393                 return true;
394             }
395 
396             // The launcher may have been recreated as a result of device rotation.
397             int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
398             initStateCallbacks();
399             mStateCallback.setState(oldState);
400         }
401         mWasLauncherAlreadyVisible = alreadyOnHome;
402         mActivity = activity;
403         // Override the visibility of the activity until the gesture actually starts and we swipe
404         // up, or until we transition home and the home animation is composed
405         if (alreadyOnHome) {
406             mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
407         } else {
408             mActivity.addForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
409         }
410 
411         mRecentsView = activity.getOverviewPanel();
412         mRecentsView.setOnPageTransitionEndCallback(null);
413 
414         mStateCallback.setState(STATE_LAUNCHER_PRESENT);
415         if (alreadyOnHome) {
416             onLauncherStart();
417         } else {
418             activity.runOnceOnStart(this::onLauncherStart);
419         }
420 
421         // Set up a entire animation lifecycle callback to notify the current recents view when
422         // the animation is canceled
423         mGestureState.runOnceAtState(STATE_RECENTS_ANIMATION_CANCELED, () -> {
424                 HashMap<Integer, ThumbnailData> snapshots =
425                         mGestureState.consumeRecentsAnimationCanceledSnapshot();
426                 if (snapshots != null) {
427                     mRecentsView.switchToScreenshot(snapshots, () -> {
428                         if (mRecentsAnimationController != null) {
429                             mRecentsAnimationController.cleanupScreenshot();
430                         }
431                     });
432                     mRecentsView.onRecentsAnimationComplete();
433                 }
434             });
435 
436         setupRecentsViewUi();
437         linkRecentsViewScroll();
438         activity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
439 
440         mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
441         return true;
442     }
443 
444     /**
445      * Return true if the window should be translated horizontally if the recents view scrolls
446      */
moveWindowWithRecentsScroll()447     protected boolean moveWindowWithRecentsScroll() {
448         return mGestureState.getEndTarget() != HOME;
449     }
450 
onLauncherStart()451     private void onLauncherStart() {
452         final T activity = mActivityInterface.getCreatedActivity();
453         if (mActivity != activity) {
454             return;
455         }
456         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
457             return;
458         }
459         // RecentsView never updates the display rotation until swipe-up, force update
460         // RecentsOrientedState before passing to TaskViewSimulator.
461         mRecentsView.updateRecentsRotation();
462         runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
463                 .setOrientationState(mRecentsView.getPagedViewOrientedState()));
464 
465         // If we've already ended the gesture and are going home, don't prepare recents UI,
466         // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
467         if (mGestureState.getEndTarget() != HOME) {
468             Runnable initAnimFactory = () -> {
469                 mAnimationFactory = mActivityInterface.prepareRecentsUI(mDeviceState,
470                         mWasLauncherAlreadyVisible, this::onAnimatorPlaybackControllerCreated);
471                 maybeUpdateRecentsAttachedState(false /* animate */);
472                 if (mGestureState.getEndTarget() != null) {
473                     // Update the end target in case the gesture ended before we init.
474                     mAnimationFactory.setEndTarget(mGestureState.getEndTarget());
475                 }
476             };
477             if (mWasLauncherAlreadyVisible) {
478                 // Launcher is visible, but might be about to stop. Thus, if we prepare recents
479                 // now, it might get overridden by moveToRestState() in onStop(). To avoid this,
480                 // wait until the next gesture (and possibly launcher) starts.
481                 mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, initAnimFactory);
482             } else {
483                 initAnimFactory.run();
484             }
485         }
486         AbstractFloatingView.closeAllOpenViewsExcept(activity, mWasLauncherAlreadyVisible,
487                 AbstractFloatingView.TYPE_LISTENER);
488 
489         if (mWasLauncherAlreadyVisible) {
490             mStateCallback.setState(STATE_LAUNCHER_DRAWN);
491         } else {
492             Object traceToken = TraceHelper.INSTANCE.beginSection("WTS-init");
493             View dragLayer = activity.getDragLayer();
494             dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
495                 boolean mHandled = false;
496 
497                 @Override
498                 public void onDraw() {
499                     if (mHandled) {
500                         return;
501                     }
502                     mHandled = true;
503 
504                     TraceHelper.INSTANCE.endSection(traceToken);
505                     dragLayer.post(() ->
506                             dragLayer.getViewTreeObserver().removeOnDrawListener(this));
507                     if (activity != mActivity) {
508                         return;
509                     }
510 
511                     mStateCallback.setState(STATE_LAUNCHER_DRAWN);
512                 }
513             });
514         }
515 
516         activity.getRootView().setOnApplyWindowInsetsListener(this);
517         mStateCallback.setState(STATE_LAUNCHER_STARTED);
518     }
519 
onLauncherBindToService()520     private void onLauncherBindToService() {
521         mStateCallback.setState(STATE_LAUNCHER_BIND_TO_SERVICE);
522         flushOnRecentsAnimationAndLauncherBound();
523     }
524 
onLauncherPresentAndGestureStarted()525     private void onLauncherPresentAndGestureStarted() {
526         // Re-setup the recents UI when gesture starts, as the state could have been changed during
527         // that time by a previous window transition.
528         setupRecentsViewUi();
529 
530         // For the duration of the gesture, in cases where an activity is launched while the
531         // activity is not yet resumed, finish the animation to ensure we get resumed
532         mGestureState.getActivityInterface().setOnDeferredActivityLaunchCallback(
533                 mOnDeferredActivityLaunch);
534 
535         mGestureState.runOnceAtState(STATE_END_TARGET_SET,
536                 () -> {
537                     mDeviceState.getRotationTouchHelper()
538                             .onEndTargetCalculated(mGestureState.getEndTarget(),
539                                     mActivityInterface);
540                 });
541 
542         notifyGestureStartedAsync();
543     }
544 
onDeferredActivityLaunch()545     private void onDeferredActivityLaunch() {
546         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
547             mActivityInterface.switchRunningTaskViewToScreenshot(
548                     null, () -> {
549                         mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
550                     });
551         } else {
552             mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
553         }
554     }
555 
setupRecentsViewUi()556     private void setupRecentsViewUi() {
557         if (mContinuingLastGesture) {
558             updateSysUiFlags(mCurrentShift.value);
559             return;
560         }
561         notifyGestureAnimationStartToRecents();
562     }
563 
notifyGestureAnimationStartToRecents()564     protected void notifyGestureAnimationStartToRecents() {
565         ActivityManager.RunningTaskInfo[] runningTasks;
566         if (mIsSwipeForStagedSplit) {
567             int[] splitTaskIds =
568                     LauncherSplitScreenListener.INSTANCE.getNoCreate().getRunningSplitTaskIds();
569             runningTasks = new ActivityManager.RunningTaskInfo[splitTaskIds.length];
570             for (int i = 0; i < splitTaskIds.length; i++) {
571                 int taskId = splitTaskIds[i];
572                 // Order matters here, we want first indexed RunningTaskInfo to be leftTop task
573                 for (ActivityManager.RunningTaskInfo rti : mGestureState.getRunningTasks()) {
574                     if (taskId == rti.taskId) {
575                         runningTasks[i] = rti;
576                         break;
577                     }
578 
579                 }
580             }
581         } else {
582             runningTasks = new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()};
583         }
584         mRecentsView.onGestureAnimationStart(runningTasks);
585     }
586 
launcherFrameDrawn()587     private void launcherFrameDrawn() {
588         mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
589     }
590 
initializeLauncherAnimationController()591     private void initializeLauncherAnimationController() {
592         buildAnimationController();
593 
594         Object traceToken = TraceHelper.INSTANCE.beginSection("logToggleRecents",
595                 TraceHelper.FLAG_IGNORE_BINDERS);
596         LatencyTrackerCompat.logToggleRecents(
597                 mContext, (int) (mLauncherFrameDrawnTime - mTouchTimeMs));
598         TraceHelper.INSTANCE.endSection(traceToken);
599 
600         // This method is only called when STATE_GESTURE_STARTED is set, so we can enable the
601         // high-res thumbnail loader here once we are sure that we will end up in an overview state
602         RecentsModel.INSTANCE.get(mContext).getThumbnailCache()
603                 .getHighResLoadingState().setVisible(true);
604     }
605 
getMotionPauseListener()606     public MotionPauseDetector.OnMotionPauseListener getMotionPauseListener() {
607         return new MotionPauseDetector.OnMotionPauseListener() {
608             @Override
609             public void onMotionPauseDetected() {
610                 mHasMotionEverBeenPaused = true;
611                 maybeUpdateRecentsAttachedState(true/* animate */, true/* moveFocusedTask */);
612                 performHapticFeedback();
613             }
614 
615             @Override
616             public void onMotionPauseChanged(boolean isPaused) {
617                 mIsMotionPaused = isPaused;
618             }
619         };
620     }
621 
622     private void maybeUpdateRecentsAttachedState() {
623         maybeUpdateRecentsAttachedState(true /* animate */);
624     }
625 
626     private void maybeUpdateRecentsAttachedState(boolean animate) {
627         maybeUpdateRecentsAttachedState(animate, false /* moveFocusedTask */);
628     }
629 
630     /**
631      * Determines whether to show or hide RecentsView. The window is always
632      * synchronized with its corresponding TaskView in RecentsView, so if
633      * RecentsView is shown, it will appear to be attached to the window.
634      *
635      * Note this method has no effect unless the navigation mode is NO_BUTTON.
636      * @param animate whether to animate when attaching RecentsView
637      * @param moveFocusedTask whether to move focused task to front when attaching
638      */
639     private void maybeUpdateRecentsAttachedState(boolean animate, boolean moveFocusedTask) {
640         if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
641             return;
642         }
643         RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null
644                 ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
645                 : null;
646         final boolean recentsAttachedToAppWindow;
647         if (mGestureState.getEndTarget() != null) {
648             recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow;
649         } else if (mContinuingLastGesture
650                 && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
651             recentsAttachedToAppWindow = true;
652         } else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) {
653             // The window is going away so make sure recents is always visible in this case.
654             recentsAttachedToAppWindow = true;
655         } else {
656             recentsAttachedToAppWindow = mHasMotionEverBeenPaused || mIsLikelyToStartNewTask;
657         }
658         if (moveFocusedTask && !mAnimationFactory.hasRecentsEverAttachedToAppWindow()
659                 && recentsAttachedToAppWindow) {
660             // Only move focused task if RecentsView has never been attached before, to avoid
661             // TaskView jumping to new position as we move the tasks.
662             mRecentsView.moveFocusedTaskToFront();
663         }
664         mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
665 
666         // Reapply window transform throughout the attach animation, as the animation affects how
667         // much the window is bound by overscroll (vs moving freely).
668         if (animate) {
669             ValueAnimator reapplyWindowTransformAnim = ValueAnimator.ofFloat(0, 1);
670             reapplyWindowTransformAnim.addUpdateListener(anim -> {
671                 if (mRunningWindowAnim == null || mRunningWindowAnim.length == 0) {
672                     applyScrollAndTransform();
673                 }
674             });
675             reapplyWindowTransformAnim.setDuration(RECENTS_ATTACH_DURATION).start();
676             mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED,
677                     reapplyWindowTransformAnim::cancel);
678         } else {
679             applyScrollAndTransform();
680         }
681     }
682 
683     public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
684         setIsLikelyToStartNewTask(isLikelyToStartNewTask, true /* animate */);
685     }
686 
687     private void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask, boolean animate) {
688         if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
689             mIsLikelyToStartNewTask = isLikelyToStartNewTask;
690             maybeUpdateRecentsAttachedState(animate);
691         }
692     }
693 
694     private void buildAnimationController() {
695         if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
696             return;
697         }
698         initTransitionEndpoints(mActivity.getDeviceProfile());
699         mAnimationFactory.createActivityInterface(mTransitionDragLength);
700     }
701 
702     /**
703      * We don't want to change mLauncherTransitionController if mGestureState.getEndTarget() == HOME
704      * (it has its own animation) or if we explicitly ended the controller already.
705      * @return Whether we can create the launcher controller or update its progress.
706      */
707     private boolean canCreateNewOrUpdateExistingLauncherTransitionController() {
708         return mGestureState.getEndTarget() != HOME && !mHasEndedLauncherTransition;
709     }
710 
711     @Override
712     public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
713         WindowInsets result = view.onApplyWindowInsets(windowInsets);
714         buildAnimationController();
715         // Reapply the current shift to ensure it takes new insets into account, e.g. when long
716         // pressing to stash taskbar without moving the finger.
717         updateFinalShift();
718         return result;
719     }
720 
721     private void onAnimatorPlaybackControllerCreated(AnimatorControllerWithResistance anim) {
722         boolean isFirstCreation = mLauncherTransitionController == null;
723         mLauncherTransitionController = anim;
724         if (isFirstCreation) {
725             mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, () -> {
726                 // Wait until the gesture is started (touch slop was passed) to start in sync with
727                 // mWindowTransitionController. This ensures we don't hide the taskbar background
728                 // when long pressing to stash it, for instance.
729                 mLauncherTransitionController.getNormalController().dispatchOnStart();
730                 updateLauncherTransitionProgress();
731             });
732         }
733     }
734 
735     public Intent getLaunchIntent() {
736         return mGestureState.getOverviewIntent();
737     }
738 
739     /**
740      * Called when the value of {@link #mCurrentShift} changes
741      */
742     @UiThread
743     @Override
744     public void updateFinalShift() {
745         final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
746         if (passed != mPassedOverviewThreshold) {
747             mPassedOverviewThreshold = passed;
748             if (mDeviceState.isTwoButtonNavMode() && !mGestureState.isHandlingAtomicEvent()) {
749                 performHapticFeedback();
750             }
751         }
752 
753         updateSysUiFlags(mCurrentShift.value);
754         applyScrollAndTransform();
755 
756         updateLauncherTransitionProgress();
757     }
758 
759     private void updateLauncherTransitionProgress() {
760         if (mLauncherTransitionController == null
761                 || !canCreateNewOrUpdateExistingLauncherTransitionController()) {
762             return;
763         }
764         mLauncherTransitionController.setProgress(
765                 Math.max(mCurrentShift.value, getScaleProgressDueToScroll()), mDragLengthFactor);
766     }
767 
768     /**
769      * @param windowProgress 0 == app, 1 == overview
770      */
771     private void updateSysUiFlags(float windowProgress) {
772         if (mRecentsAnimationController != null && mRecentsView != null) {
773             TaskView runningTask = mRecentsView.getRunningTaskView();
774             TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen();
775             int centermostTaskFlags = centermostTask == null ? 0
776                     : centermostTask.getThumbnail().getSysUiStatusNavFlags();
777             boolean swipeUpThresholdPassed = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
778             boolean quickswitchThresholdPassed = centermostTask != runningTask;
779 
780             // We will handle the sysui flags based on the centermost task view.
781             mRecentsAnimationController.setUseLauncherSystemBarFlags(swipeUpThresholdPassed
782                     ||  (quickswitchThresholdPassed && centermostTaskFlags != 0));
783             mRecentsAnimationController.setSplitScreenMinimized(swipeUpThresholdPassed);
784             // Provide a hint to WM the direction that we will be settling in case the animation
785             // needs to be canceled
786             mRecentsAnimationController.setWillFinishToHome(swipeUpThresholdPassed);
787 
788             if (swipeUpThresholdPassed) {
789                 mActivity.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0);
790             } else {
791                 mActivity.getSystemUiController().updateUiState(
792                         UI_STATE_FULLSCREEN_TASK, centermostTaskFlags);
793             }
794         }
795     }
796 
797     @Override
798     public void onRecentsAnimationStart(RecentsAnimationController controller,
799             RecentsAnimationTargets targets) {
800         super.onRecentsAnimationStart(controller, targets);
801         ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
802         mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(targets);
803         mRecentsAnimationController = controller;
804         mRecentsAnimationTargets = targets;
805 
806         // Only initialize the device profile, if it has not been initialized before, as in some
807         // configurations targets.homeContentInsets may not be correct.
808         if (mActivity == null) {
809             RemoteAnimationTargetCompat primaryTaskTarget = targets.apps[0];
810             // orientation state is independent of which remote target handle we use since both
811             // should be pointing to the same one. Just choose index 0 for now since that works for
812             // both split and non-split
813             RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
814                     .getOrientationState();
815             DeviceProfile dp = orientationState.getLauncherDeviceProfile();
816             if (targets.minimizedHomeBounds != null && primaryTaskTarget != null) {
817                 Rect overviewStackBounds = mActivityInterface
818                         .getOverviewWindowBounds(targets.minimizedHomeBounds, primaryTaskTarget);
819                 dp = dp.getMultiWindowProfile(mContext,
820                         new WindowBounds(overviewStackBounds, targets.homeContentInsets));
821             } else {
822                 // If we are not in multi-window mode, home insets should be same as system insets.
823                 dp = dp.copy(mContext);
824             }
825             dp.updateInsets(targets.homeContentInsets);
826             dp.updateIsSeascape(mContext);
827             initTransitionEndpoints(dp);
828             orientationState.setMultiWindowMode(dp.isMultiWindowMode);
829         }
830 
831         // Notify when the animation starts
832         flushOnRecentsAnimationAndLauncherBound();
833 
834         TaskViewUtils.setSplitAuxiliarySurfacesShown(mRecentsAnimationTargets.nonApps,
835                 false /*shown*/, true /*animate*/);
836 
837         // Only add the callback to enable the input consumer after we actually have the controller
838         mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
839                 mRecentsAnimationController::enableInputConsumer);
840         mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
841 
842         mPassedOverviewThreshold = false;
843     }
844 
845     @Override
846     public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
847         ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
848         mActivityInitListener.unregister();
849         mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
850 
851         if (mRecentsAnimationTargets != null) {
852             TaskViewUtils.setSplitAuxiliarySurfacesShown(mRecentsAnimationTargets.nonApps,
853                     true /*shown*/, true /*animate*/);
854         }
855 
856         // Defer clearing the controller and the targets until after we've updated the state
857         mRecentsAnimationController = null;
858         mRecentsAnimationTargets = null;
859         if (mRecentsView != null) {
860             mRecentsView.setRecentsAnimationTargets(null, null);
861         }
862     }
863 
864     @UiThread
865     public void onGestureStarted(boolean isLikelyToStartNewTask) {
866         mActivityInterface.closeOverlay();
867         TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
868 
869         if (mRecentsView != null) {
870             mRecentsView.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
871                 boolean mHandled = false;
872 
873                 @Override
874                 public void onDraw() {
875                     if (mHandled) {
876                         return;
877                     }
878                     mHandled = true;
879 
880                     InteractionJankMonitorWrapper.begin(mRecentsView,
881                             InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH, 2000 /* ms timeout */);
882                     InteractionJankMonitorWrapper.begin(mRecentsView,
883                             InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
884 
885                     mRecentsView.post(() ->
886                             mRecentsView.getViewTreeObserver().removeOnDrawListener(this));
887                 }
888             });
889         }
890         notifyGestureStartedAsync();
891         setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
892         mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
893         mGestureStarted = true;
894         SystemUiProxy.INSTANCE.get(mContext).notifySwipeUpGestureStarted();
895     }
896 
897     /**
898      * Notifies the launcher that the swipe gesture has started. This can be called multiple times.
899      */
900     @UiThread
901     private void notifyGestureStartedAsync() {
902         final T curActivity = mActivity;
903         if (curActivity != null) {
904             // Once the gesture starts, we can no longer transition home through the button, so
905             // reset the force override of the activity visibility
906             mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
907         }
908     }
909 
910     /**
911      * Called as a result on ACTION_CANCEL to return the UI to the start state.
912      */
913     @UiThread
914     public void onGestureCancelled() {
915         updateDisplacement(0);
916         mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
917         handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */);
918     }
919 
920     /**
921      * @param endVelocity The velocity in the direction of the nav bar to the middle of the screen.
922      * @param velocity The x and y components of the velocity when the gesture ends.
923      * @param downPos The x and y value of where the gesture started.
924      */
925     @UiThread
926     public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
927         float flingThreshold = mContext.getResources()
928                 .getDimension(R.dimen.quickstep_fling_threshold_speed);
929         boolean isFling = mGestureStarted && !mIsMotionPaused
930                 && Math.abs(endVelocity) > flingThreshold;
931         mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
932         boolean isVelocityVertical = Math.abs(velocity.y) > Math.abs(velocity.x);
933         if (isVelocityVertical) {
934             mLogDirectionUpOrLeft = velocity.y < 0;
935         } else {
936             mLogDirectionUpOrLeft = velocity.x < 0;
937         }
938         mDownPos = downPos;
939         handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
940     }
941 
942     private void endRunningWindowAnim(boolean cancel) {
943         if (mRunningWindowAnim != null) {
944             if (cancel) {
945                 for (RunningWindowAnim r : mRunningWindowAnim) {
946                     if (r != null) {
947                         r.cancel();
948                     }
949                 }
950             } else {
951                 for (RunningWindowAnim r : mRunningWindowAnim) {
952                     if (r != null) {
953                         r.end();
954                     }
955                 }
956             }
957         }
958         if (mParallelRunningAnim != null) {
959             // Unlike the above animation, the parallel animation won't have anything to take up
960             // the work if it's canceled, so just end it instead.
961             mParallelRunningAnim.end();
962         }
963     }
964 
965     private void onSettledOnEndTarget() {
966         // Fast-finish the attaching animation if it's still running.
967         maybeUpdateRecentsAttachedState(false);
968         final GestureEndTarget endTarget = mGestureState.getEndTarget();
969         // Wait until the given View (if supplied) draws before resuming the last task.
970         View postResumeLastTask = mActivityInterface.onSettledOnEndTarget(endTarget);
971 
972         if (endTarget != NEW_TASK) {
973             InteractionJankMonitorWrapper.cancel(
974                     InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
975         }
976         if (endTarget != HOME) {
977             InteractionJankMonitorWrapper.cancel(
978                     InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
979         }
980 
981         switch (endTarget) {
982             case HOME:
983                 mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
984                 // Notify swipe-to-home (recents animation) is finished
985                 SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
986                 LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
987                 break;
988             case RECENTS:
989                 mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
990                         | STATE_SCREENSHOT_VIEW_SHOWN);
991                 break;
992             case NEW_TASK:
993                 mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT);
994                 break;
995             case LAST_TASK:
996                 if (postResumeLastTask != null) {
997                     ViewUtils.postFrameDrawn(postResumeLastTask,
998                             () -> mStateCallback.setState(STATE_RESUME_LAST_TASK));
999                 } else {
1000                     mStateCallback.setState(STATE_RESUME_LAST_TASK);
1001                 }
1002                 if (mRecentsAnimationTargets != null) {
1003                     TaskViewUtils.setSplitAuxiliarySurfacesShown(mRecentsAnimationTargets.nonApps,
1004                             true /*shown*/, false /*animate*/);
1005                 }
1006                 break;
1007         }
1008         ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + endTarget);
1009     }
1010 
1011     /** @return Whether this was the task we were waiting to appear, and thus handled it. */
1012     protected boolean handleTaskAppeared(RemoteAnimationTargetCompat[] appearedTaskTarget) {
1013         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
1014             return false;
1015         }
1016         boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTarget).anyMatch(
1017                 targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId());
1018         if (mStateCallback.hasStates(STATE_START_NEW_TASK) && hasStartedTaskBefore) {
1019             reset();
1020             return true;
1021         }
1022         return false;
1023     }
1024 
1025     private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
1026             boolean isCancel) {
1027         if (mGestureState.isHandlingAtomicEvent()) {
1028             // Button mode, this is only used to go to recents
1029             return RECENTS;
1030         }
1031         final GestureEndTarget endTarget;
1032         final boolean goingToNewTask;
1033         if (mRecentsView != null) {
1034             if (!hasTargets()) {
1035                 // If there are no running tasks, then we can assume that this is a continuation of
1036                 // the last gesture, but after the recents animation has finished
1037                 goingToNewTask = true;
1038             } else {
1039                 final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
1040                 final int taskToLaunch = mRecentsView.getNextPage();
1041                 goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
1042             }
1043         } else {
1044             goingToNewTask = false;
1045         }
1046         final boolean reachedOverviewThreshold = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
1047         if (!isFling) {
1048             if (isCancel) {
1049                 endTarget = LAST_TASK;
1050             } else if (mDeviceState.isFullyGesturalNavMode()) {
1051                 if (mIsMotionPaused) {
1052                     endTarget = RECENTS;
1053                 } else if (goingToNewTask) {
1054                     endTarget = NEW_TASK;
1055                 } else {
1056                     endTarget = !reachedOverviewThreshold ? LAST_TASK : HOME;
1057                 }
1058             } else {
1059                 endTarget = reachedOverviewThreshold && mGestureStarted
1060                         ? RECENTS
1061                         : goingToNewTask
1062                                 ? NEW_TASK
1063                                 : LAST_TASK;
1064             }
1065         } else {
1066             // If swiping at a diagonal, base end target on the faster velocity.
1067             boolean isSwipeUp = endVelocity < 0;
1068             boolean willGoToNewTaskOnSwipeUp =
1069                     goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity);
1070 
1071             if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
1072                 endTarget = HOME;
1073             } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp) {
1074                 // If swiping at a diagonal, base end target on the faster velocity.
1075                 endTarget = NEW_TASK;
1076             } else if (isSwipeUp) {
1077                 endTarget = !reachedOverviewThreshold && willGoToNewTaskOnSwipeUp
1078                         ? NEW_TASK : RECENTS;
1079             } else {
1080                 endTarget = goingToNewTask ? NEW_TASK : LAST_TASK;
1081             }
1082         }
1083 
1084         if (mDeviceState.isOverviewDisabled() && (endTarget == RECENTS || endTarget == LAST_TASK)) {
1085             return LAST_TASK;
1086         }
1087         return endTarget;
1088     }
1089 
1090     @UiThread
1091     private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity,
1092             boolean isCancel) {
1093         long duration = MAX_SWIPE_DURATION;
1094         float currentShift = mCurrentShift.value;
1095         final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
1096                 isFling, isCancel);
1097         // Set the state, but don't notify until the animation completes
1098         mGestureState.setEndTarget(endTarget, false /* isAtomic */);
1099         mAnimationFactory.setEndTarget(endTarget);
1100 
1101         float endShift = endTarget.isLauncher ? 1 : 0;
1102         final float startShift;
1103         if (!isFling) {
1104             long expectedDuration = Math.abs(Math.round((endShift - currentShift)
1105                     * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
1106             duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
1107             startShift = currentShift;
1108         } else {
1109             startShift = Utilities.boundToRange(currentShift - velocity.y
1110                     * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
1111             if (mTransitionDragLength > 0) {
1112                     float distanceToTravel = (endShift - currentShift) * mTransitionDragLength;
1113 
1114                     // we want the page's snap velocity to approximately match the velocity at
1115                     // which the user flings, so we scale the duration by a value near to the
1116                     // derivative of the scroll interpolator at zero, ie. 2.
1117                     long baseDuration = Math.round(Math.abs(distanceToTravel / velocity.y));
1118                     duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
1119             }
1120         }
1121         Interpolator interpolator;
1122         S state = mActivityInterface.stateFromGestureEndTarget(endTarget);
1123         if (state.displayOverviewTasksAsGrid(mDp)) {
1124             interpolator = ACCEL_DEACCEL;
1125         } else if (endTarget == RECENTS) {
1126             interpolator = OVERSHOOT_1_2;
1127         } else {
1128             interpolator = DEACCEL;
1129         }
1130 
1131         if (endTarget.isLauncher) {
1132             mInputConsumerProxy.enable();
1133         }
1134         if (endTarget == HOME) {
1135             duration = HOME_DURATION;
1136             // Early detach the nav bar once the endTarget is determined as HOME
1137             if (mRecentsAnimationController != null) {
1138                 mRecentsAnimationController.detachNavigationBarFromApp(true);
1139             }
1140         } else if (endTarget == RECENTS) {
1141             if (mRecentsView != null) {
1142                 int nearestPage = mRecentsView.getDestinationPage();
1143                 boolean isScrolling = false;
1144                 if (mRecentsView.getNextPage() != nearestPage) {
1145                     // We shouldn't really scroll to the next page when swiping up to recents.
1146                     // Only allow settling on the next page if it's nearest to the center.
1147                     mRecentsView.snapToPage(nearestPage, Math.toIntExact(duration));
1148                     isScrolling = true;
1149                 }
1150                 if (mRecentsView.getScroller().getDuration() > MAX_SWIPE_DURATION) {
1151                     mRecentsView.snapToPage(mRecentsView.getNextPage(), (int) MAX_SWIPE_DURATION);
1152                     isScrolling = true;
1153                 }
1154                 if (!mGestureState.isHandlingAtomicEvent() || isScrolling) {
1155                     duration = Math.max(duration, mRecentsView.getScroller().getDuration());
1156                 }
1157             }
1158         }
1159 
1160         // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
1161         // or resumeLastTask().
1162         if (mRecentsView != null) {
1163             mRecentsView.setOnPageTransitionEndCallback(
1164                     () -> mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED));
1165         } else {
1166             mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
1167         }
1168 
1169         animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocity);
1170     }
1171 
1172     private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTask) {
1173         StatsLogManager.EventEnum event;
1174         switch (endTarget) {
1175             case HOME:
1176                 event = LAUNCHER_HOME_GESTURE;
1177                 break;
1178             case RECENTS:
1179                 event = LAUNCHER_OVERVIEW_GESTURE;
1180                 break;
1181             case LAST_TASK:
1182             case NEW_TASK:
1183                 event = mLogDirectionUpOrLeft ? LAUNCHER_QUICKSWITCH_LEFT
1184                         : LAUNCHER_QUICKSWITCH_RIGHT;
1185                 break;
1186             default:
1187                 event = IGNORE;
1188         }
1189         StatsLogger logger = StatsLogManager.newInstance(mContext).logger()
1190                 .withSrcState(LAUNCHER_STATE_BACKGROUND)
1191                 .withDstState(endTarget.containerType);
1192         if (targetTask != null) {
1193             logger.withItemInfo(targetTask.getItemInfo());
1194         }
1195 
1196         DeviceProfile dp = mDp;
1197         if (dp == null || mDownPos == null) {
1198             // We probably never received an animation controller, skip logging.
1199             return;
1200         }
1201         int pageIndex = endTarget == LAST_TASK
1202                 ? LOG_NO_OP_PAGE_INDEX
1203                 : mRecentsView.getNextPage();
1204         // TODO: set correct container using the pageIndex
1205         logger.log(event);
1206     }
1207 
1208     /** Animates to the given progress, where 0 is the current app and 1 is overview. */
1209     @UiThread
1210     private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
1211             GestureEndTarget target, PointF velocityPxPerMs) {
1212         runOnRecentsAnimationAndLauncherBound(() -> animateToProgressInternal(start, end, duration,
1213                 interpolator, target, velocityPxPerMs));
1214     }
1215 
1216     protected abstract HomeAnimationFactory createHomeAnimationFactory(
1217             ArrayList<IBinder> launchCookies, long duration, boolean isTargetTranslucent,
1218             boolean appCanEnterPip, RemoteAnimationTargetCompat runningTaskTarget);
1219 
1220     private final TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
1221         @Override
1222         public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
1223                 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
1224             if (task.taskId == mGestureState.getRunningTaskId()
1225                     && task.configuration.windowConfiguration.getActivityType()
1226                     != ACTIVITY_TYPE_HOME) {
1227                 // Since this is an edge case, just cancel and relaunch with default activity
1228                 // options (since we don't know if there's an associated app icon to launch from)
1229                 endRunningWindowAnim(true /* cancel */);
1230                 TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
1231                         mActivityRestartListener);
1232                 ActivityManagerWrapper.getInstance().startActivityFromRecents(task.taskId, null);
1233             }
1234         }
1235     };
1236 
1237     @UiThread
1238     private void animateToProgressInternal(float start, float end, long duration,
1239             Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
1240         maybeUpdateRecentsAttachedState();
1241 
1242         // If we are transitioning to launcher, then listen for the activity to be restarted while
1243         // the transition is in progress
1244         if (mGestureState.getEndTarget().isLauncher) {
1245             TaskStackChangeListeners.getInstance().registerTaskStackListener(
1246                     mActivityRestartListener);
1247 
1248             mParallelRunningAnim = mActivityInterface.getParallelAnimationToLauncher(
1249                     mGestureState.getEndTarget(), duration,
1250                     mTaskAnimationManager.getCurrentCallbacks());
1251             if (mParallelRunningAnim != null) {
1252                 mParallelRunningAnim.addListener(new AnimatorListenerAdapter() {
1253                     @Override
1254                     public void onAnimationEnd(Animator animation) {
1255                         mParallelRunningAnim = null;
1256                     }
1257                 });
1258                 mParallelRunningAnim.start();
1259             }
1260         }
1261 
1262         if (mGestureState.getEndTarget() == HOME) {
1263             getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
1264             final RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null
1265                     ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
1266                     : null;
1267             final ArrayList<IBinder> cookies = runningTaskTarget != null
1268                     ? runningTaskTarget.taskInfo.launchCookies
1269                     : new ArrayList<>();
1270             boolean isTranslucent = runningTaskTarget != null && runningTaskTarget.isTranslucent;
1271             boolean appCanEnterPip = !mDeviceState.isPipActive()
1272                     && runningTaskTarget != null
1273                     && runningTaskTarget.allowEnterPip
1274                     && runningTaskTarget.taskInfo.pictureInPictureParams != null
1275                     && runningTaskTarget.taskInfo.pictureInPictureParams.isAutoEnterEnabled();
1276             HomeAnimationFactory homeAnimFactory =
1277                     createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
1278                             runningTaskTarget);
1279             mIsSwipingPipToHome = !mIsSwipeForStagedSplit
1280                     && homeAnimFactory.supportSwipePipToHome() && appCanEnterPip;
1281             final RectFSpringAnim[] windowAnim;
1282             if (mIsSwipingPipToHome) {
1283                 mSwipePipToHomeAnimator = createWindowAnimationToPip(
1284                         homeAnimFactory, runningTaskTarget, start);
1285                 mSwipePipToHomeAnimators[0] = mSwipePipToHomeAnimator;
1286                 windowAnim = mSwipePipToHomeAnimators;
1287             } else {
1288                 mSwipePipToHomeAnimator = null;
1289                 windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
1290 
1291                 windowAnim[0].addAnimatorListener(new AnimationSuccessListener() {
1292                     @Override
1293                     public void onAnimationSuccess(Animator animator) {
1294                         if (mRecentsAnimationController == null) {
1295                             // If the recents animation is interrupted, we still end the running
1296                             // animation (not canceled) so this is still called. In that case,
1297                             // we can skip doing any future work here for the current gesture.
1298                             return;
1299                         }
1300                         // Finalize the state and notify of the change
1301                         mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
1302                     }
1303                 });
1304             }
1305             mRunningWindowAnim = new RunningWindowAnim[windowAnim.length];
1306             for (int i = 0, windowAnimLength = windowAnim.length; i < windowAnimLength; i++) {
1307                 RectFSpringAnim windowAnimation = windowAnim[i];
1308                 if (windowAnimation == null) {
1309                     continue;
1310                 }
1311                 windowAnimation.start(mContext, velocityPxPerMs);
1312                 mRunningWindowAnim[i] = RunningWindowAnim.wrap(windowAnimation);
1313             }
1314             homeAnimFactory.setSwipeVelocity(velocityPxPerMs.y);
1315             homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
1316             mLauncherTransitionController = null;
1317 
1318             if (mRecentsView != null) {
1319                 mRecentsView.onPrepareGestureEndAnimation(null, mGestureState.getEndTarget(),
1320                         getRemoteTaskViewSimulators());
1321             }
1322         } else {
1323             AnimatorSet animatorSet = new AnimatorSet();
1324             ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);
1325             windowAnim.addUpdateListener(valueAnimator -> {
1326                 computeRecentsScrollIfInvisible();
1327             });
1328             windowAnim.addListener(new AnimationSuccessListener() {
1329                 @Override
1330                 public void onAnimationSuccess(Animator animator) {
1331                     if (mRecentsAnimationController == null) {
1332                         // If the recents animation is interrupted, we still end the running
1333                         // animation (not canceled) so this is still called. In that case, we can
1334                         // skip doing any future work here for the current gesture.
1335                         return;
1336                     }
1337                     if (mRecentsView != null) {
1338                         int taskToLaunch = mRecentsView.getNextPage();
1339                         int runningTask = getLastAppearedTaskIndex();
1340                         boolean hasStartedNewTask = hasStartedNewTask();
1341                         if (target == NEW_TASK && taskToLaunch == runningTask
1342                                 && !hasStartedNewTask) {
1343                             // We are about to launch the current running task, so use LAST_TASK
1344                             // state instead of NEW_TASK. This could happen, for example, if our
1345                             // scroll is aborted after we determined the target to be NEW_TASK.
1346                             mGestureState.setEndTarget(LAST_TASK);
1347                         } else if (target == LAST_TASK && hasStartedNewTask) {
1348                             // We are about to re-launch the previously running task, but we can't
1349                             // just finish the controller like we normally would because that would
1350                             // instead resume the last task that appeared, and not ensure that this
1351                             // task is restored to the top. To address this, re-launch the task as
1352                             // if it were a new task.
1353                             mGestureState.setEndTarget(NEW_TASK);
1354                         }
1355                     }
1356                     mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
1357                 }
1358             });
1359             animatorSet.play(windowAnim);
1360             if (mRecentsView != null) {
1361                 mRecentsView.onPrepareGestureEndAnimation(
1362                         animatorSet, mGestureState.getEndTarget(),
1363                         getRemoteTaskViewSimulators());
1364             }
1365             animatorSet.setDuration(duration).setInterpolator(interpolator);
1366             animatorSet.start();
1367             mRunningWindowAnim = new RunningWindowAnim[]{RunningWindowAnim.wrap(animatorSet)};
1368         }
1369     }
1370 
1371     private int calculateWindowRotation(RemoteAnimationTargetCompat runningTaskTarget,
1372             RecentsOrientedState orientationState) {
1373         if (runningTaskTarget.rotationChange != 0
1374                 && TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
1375             return Math.abs(runningTaskTarget.rotationChange) == ROTATION_90
1376                     ? ROTATION_270 : ROTATION_90;
1377         } else {
1378             return orientationState.getDisplayRotation();
1379         }
1380     }
1381 
1382     /**
1383      * TODO(b/195473090) handle multiple task simulators (if needed) for PIP
1384      */
1385     private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
1386             RemoteAnimationTargetCompat runningTaskTarget, float startProgress) {
1387         // Directly animate the app to PiP (picture-in-picture) mode
1388         final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask();
1389         final RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
1390                 .getOrientationState();
1391         final int windowRotation = calculateWindowRotation(runningTaskTarget, orientationState);
1392         final int homeRotation = orientationState.getRecentsActivityRotation();
1393 
1394         final Matrix[] homeToWindowPositionMaps = new Matrix[mRemoteTargetHandles.length];
1395         final RectF startRect = updateProgressForStartRect(homeToWindowPositionMaps,
1396                 startProgress)[0];
1397         final Matrix homeToWindowPositionMap = homeToWindowPositionMaps[0];
1398         // Move the startRect to Launcher space as floatingIconView runs in Launcher
1399         final Matrix windowToHomePositionMap = new Matrix();
1400         homeToWindowPositionMap.invert(windowToHomePositionMap);
1401         windowToHomePositionMap.mapRect(startRect);
1402 
1403         final Rect destinationBounds = SystemUiProxy.INSTANCE.get(mContext)
1404                 .startSwipePipToHome(taskInfo.topActivity,
1405                         taskInfo.topActivityInfo,
1406                         runningTaskTarget.taskInfo.pictureInPictureParams,
1407                         homeRotation,
1408                         mDp.hotseatBarSizePx);
1409         final SwipePipToHomeAnimator.Builder builder = new SwipePipToHomeAnimator.Builder()
1410                 .setContext(mContext)
1411                 .setTaskId(runningTaskTarget.taskId)
1412                 .setComponentName(taskInfo.topActivity)
1413                 .setLeash(runningTaskTarget.leash.getSurfaceControl())
1414                 .setSourceRectHint(
1415                         runningTaskTarget.taskInfo.pictureInPictureParams.getSourceRectHint())
1416                 .setAppBounds(taskInfo.configuration.windowConfiguration.getBounds())
1417                 .setHomeToWindowPositionMap(homeToWindowPositionMap)
1418                 .setStartBounds(startRect)
1419                 .setDestinationBounds(destinationBounds)
1420                 .setCornerRadius(mRecentsView.getPipCornerRadius())
1421                 .setAttachedView(mRecentsView);
1422         // We would assume home and app window always in the same rotation While homeRotation
1423         // is not ROTATION_0 (which implies the rotation is turned on in launcher settings).
1424         if (homeRotation == ROTATION_0
1425                 && (windowRotation == ROTATION_90 || windowRotation == ROTATION_270)) {
1426             builder.setFromRotation(mRemoteTargetHandles[0].getTaskViewSimulator(), windowRotation,
1427                     taskInfo.displayCutoutInsets);
1428         }
1429         final SwipePipToHomeAnimator swipePipToHomeAnimator = builder.build();
1430         AnimatorPlaybackController activityAnimationToHome =
1431                 homeAnimFactory.createActivityAnimationToHome();
1432         swipePipToHomeAnimator.addAnimatorListener(new AnimatorListenerAdapter() {
1433             private boolean mHasAnimationEnded;
1434             @Override
1435             public void onAnimationStart(Animator animation) {
1436                 if (mHasAnimationEnded) return;
1437                 // Ensure Launcher ends in NORMAL state
1438                 activityAnimationToHome.dispatchOnStart();
1439             }
1440 
1441             @Override
1442             public void onAnimationEnd(Animator animation) {
1443                 if (mHasAnimationEnded) return;
1444                 mHasAnimationEnded = true;
1445                 activityAnimationToHome.getAnimationPlayer().end();
1446                 if (mRecentsAnimationController == null) {
1447                     // If the recents animation is interrupted, we still end the running
1448                     // animation (not canceled) so this is still called. In that case, we can
1449                     // skip doing any future work here for the current gesture.
1450                     return;
1451                 }
1452                 // Finalize the state and notify of the change
1453                 mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
1454             }
1455         });
1456         setupWindowAnimation(new RectFSpringAnim[]{swipePipToHomeAnimator});
1457         return swipePipToHomeAnimator;
1458     }
1459 
1460     private void computeRecentsScrollIfInvisible() {
1461         if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
1462             // Views typically don't compute scroll when invisible as an optimization,
1463             // but in our case we need to since the window offset depends on the scroll.
1464             mRecentsView.computeScroll();
1465         }
1466     }
1467 
1468     private void continueComputingRecentsScrollIfNecessary() {
1469         if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)
1470                 && !mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)
1471                 && !mCanceled) {
1472             computeRecentsScrollIfInvisible();
1473             mRecentsView.postOnAnimation(this::continueComputingRecentsScrollIfNecessary);
1474         }
1475     }
1476 
1477     /**
1478      * Creates an animation that transforms the current app window into the home app.
1479      * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
1480      * @param homeAnimationFactory The home animation factory.
1481      */
1482     @Override
1483     protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress,
1484             HomeAnimationFactory homeAnimationFactory) {
1485         RectFSpringAnim[] anim =
1486                 super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
1487         setupWindowAnimation(anim);
1488         return anim;
1489     }
1490 
1491     private void setupWindowAnimation(RectFSpringAnim[] anims) {
1492         anims[0].addOnUpdateListener((r, p) -> {
1493             updateSysUiFlags(Math.max(p, mCurrentShift.value));
1494         });
1495         anims[0].addAnimatorListener(new AnimationSuccessListener() {
1496             @Override
1497             public void onAnimationSuccess(Animator animator) {
1498                 if (mRecentsView != null) {
1499                     mRecentsView.post(mRecentsView::resetTaskVisuals);
1500                 }
1501                 // Make sure recents is in its final state
1502                 maybeUpdateRecentsAttachedState(false);
1503                 mActivityInterface.onSwipeUpToHomeComplete(mDeviceState);
1504             }
1505         });
1506         if (mRecentsAnimationTargets != null) {
1507             mRecentsAnimationTargets.addReleaseCheck(anims[0]);
1508         }
1509     }
1510 
1511     public void onConsumerAboutToBeSwitched() {
1512         if (mActivity != null) {
1513             // In the off chance that the gesture ends before Launcher is started, we should clear
1514             // the callback here so that it doesn't update with the wrong state
1515             mActivity.clearRunOnceOnStartCallback();
1516             resetLauncherListeners();
1517         }
1518         if (mGestureState.isRecentsAnimationRunning() && mGestureState.getEndTarget() != null
1519                 && !mGestureState.getEndTarget().isLauncher) {
1520             // Continued quick switch.
1521             cancelCurrentAnimation();
1522         } else {
1523             mStateCallback.setStateOnUiThread(STATE_FINISH_WITH_NO_END);
1524             reset();
1525         }
1526     }
1527 
1528     public boolean isCanceled() {
1529         return mCanceled;
1530     }
1531 
1532     @UiThread
1533     private void resumeLastTask() {
1534         if (mRecentsAnimationController != null) {
1535             mRecentsAnimationController.finish(false /* toRecents */, null);
1536             ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
1537         }
1538         doLogGesture(LAST_TASK, null);
1539         reset();
1540     }
1541 
1542     @UiThread
1543     private void startNewTask() {
1544         TaskView taskToLaunch = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
1545         startNewTask(success -> {
1546             if (!success) {
1547                 reset();
1548                 // We couldn't launch the task, so take user to overview so they can
1549                 // decide what to do instead of staying in this broken state.
1550                 endLauncherTransitionController();
1551                 updateSysUiFlags(1 /* windowProgress == overview */);
1552             }
1553             doLogGesture(NEW_TASK, taskToLaunch);
1554         });
1555     }
1556 
1557     /**
1558      * Called when we successfully startNewTask() on the task that was previously running. Normally
1559      * we call resumeLastTask() when returning to the previously running task, but this handles a
1560      * specific edge case: if we switch from A to B, and back to A before B appears, we need to
1561      * start A again to ensure it stays on top.
1562      */
1563     @androidx.annotation.CallSuper
1564     protected void onRestartPreviouslyAppearedTask() {
1565         // Finish the controller here, since we won't get onTaskAppeared() for a task that already
1566         // appeared.
1567         if (mRecentsAnimationController != null) {
1568             mRecentsAnimationController.finish(false, null);
1569         }
1570         reset();
1571     }
1572 
1573     private void reset() {
1574         mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
1575         if (mActivity != null) {
1576             mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
1577         }
1578     }
1579 
1580     /**
1581      * Cancels any running animation so that the active target can be overriden by a new swipe
1582      * handler (in case of quick switch).
1583      */
1584     private void cancelCurrentAnimation() {
1585         mCanceled = true;
1586         mCurrentShift.cancelAnimation();
1587 
1588         // Cleanup when switching handlers
1589         mInputConsumerProxy.unregisterCallback();
1590         mActivityInitListener.unregister();
1591         ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mActivityRestartListener);
1592         mTaskSnapshot = null;
1593     }
1594 
1595     private void invalidateHandler() {
1596         if (!ENABLE_QUICKSTEP_LIVE_TILE.get() || !mActivityInterface.isInLiveTileMode()
1597                 || mGestureState.getEndTarget() != RECENTS) {
1598             mInputConsumerProxy.destroy();
1599             mTaskAnimationManager.setLiveTileCleanUpHandler(null);
1600         }
1601         mInputConsumerProxy.unregisterCallback();
1602         endRunningWindowAnim(false /* cancel */);
1603 
1604         if (mGestureEndCallback != null) {
1605             mGestureEndCallback.run();
1606         }
1607 
1608         mActivityInitListener.unregister();
1609         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
1610                 mActivityRestartListener);
1611         mTaskSnapshot = null;
1612     }
1613 
1614     private void invalidateHandlerWithLauncher() {
1615         endLauncherTransitionController();
1616 
1617         mRecentsView.onGestureAnimationEnd();
1618         resetLauncherListeners();
1619     }
1620 
1621     private void endLauncherTransitionController() {
1622         mHasEndedLauncherTransition = true;
1623 
1624         if (mLauncherTransitionController != null) {
1625             // End the animation, but stay at the same visual progress.
1626             mLauncherTransitionController.getNormalController().dispatchSetInterpolator(
1627                     t -> Utilities.boundToRange(mCurrentShift.value, 0, 1));
1628             mLauncherTransitionController.getNormalController().getAnimationPlayer().end();
1629             mLauncherTransitionController = null;
1630         }
1631 
1632         if (mRecentsView != null) {
1633             mRecentsView.abortScrollerAnimation();
1634         }
1635     }
1636 
1637     /**
1638      * Unlike invalidateHandlerWithLauncher, this is called even when switching consumers, e.g. on
1639      * continued quick switch gesture, which cancels the previous handler but doesn't invalidate it.
1640      */
1641     private void resetLauncherListeners() {
1642         // Reset the callback for deferred activity launches
1643         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
1644             mActivityInterface.setOnDeferredActivityLaunchCallback(null);
1645         }
1646         mActivity.getRootView().setOnApplyWindowInsetsListener(null);
1647 
1648         mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
1649     }
1650 
1651     private void resetStateForAnimationCancel() {
1652         boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
1653         mActivityInterface.onTransitionCancelled(wasVisible, mGestureState.getEndTarget());
1654 
1655         if (mRecentsAnimationTargets != null) {
1656             TaskViewUtils.setSplitAuxiliarySurfacesShown(mRecentsAnimationTargets.nonApps,
1657                     true /*shown*/, false /*animate*/);
1658         }
1659 
1660         // Leave the pending invisible flag, as it may be used by wallpaper open animation.
1661         if (mActivity != null) {
1662             mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
1663         }
1664     }
1665 
1666     protected void switchToScreenshot() {
1667         if (!hasTargets()) {
1668             // If there are no targets, then we don't need to capture anything
1669             mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
1670         } else {
1671             final int runningTaskId = mGestureState.getRunningTaskId();
1672             final boolean refreshView = !ENABLE_QUICKSTEP_LIVE_TILE.get() /* refreshView */;
1673             boolean finishTransitionPosted = false;
1674             if (mRecentsAnimationController != null) {
1675                 // Update the screenshot of the task
1676                 if (mTaskSnapshot == null) {
1677                     UI_HELPER_EXECUTOR.execute(() -> {
1678                         if (mRecentsAnimationController == null) return;
1679                         final ThumbnailData taskSnapshot =
1680                                 mRecentsAnimationController.screenshotTask(runningTaskId);
1681                         MAIN_EXECUTOR.execute(() -> {
1682                             mTaskSnapshot = taskSnapshot;
1683                             if (!updateThumbnail(runningTaskId, refreshView)) {
1684                                 setScreenshotCapturedState();
1685                             }
1686                         });
1687                     });
1688                     return;
1689                 }
1690                 finishTransitionPosted = updateThumbnail(runningTaskId, refreshView);
1691             }
1692             if (!finishTransitionPosted) {
1693                 setScreenshotCapturedState();
1694             }
1695         }
1696     }
1697 
1698     // Returns whether finish transition was posted.
1699     private boolean updateThumbnail(int runningTaskId, boolean refreshView) {
1700         boolean finishTransitionPosted = false;
1701         final TaskView taskView;
1702         if (mGestureState.getEndTarget() == HOME || mGestureState.getEndTarget() == NEW_TASK) {
1703             // Capture the screenshot before finishing the transition to home or quickswitching to
1704             // ensure it's taken in the correct orientation, but no need to update the thumbnail.
1705             taskView = null;
1706         } else {
1707             taskView = mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot, refreshView);
1708         }
1709         if (taskView != null && refreshView && !mCanceled) {
1710             // Defer finishing the animation until the next launcher frame with the
1711             // new thumbnail
1712             finishTransitionPosted = ViewUtils.postFrameDrawn(taskView,
1713                     () -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
1714                     this::isCanceled);
1715         }
1716         return finishTransitionPosted;
1717     }
1718 
1719     private void setScreenshotCapturedState() {
1720         // If we haven't posted a draw callback, set the state immediately.
1721         Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT,
1722                 TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS);
1723         mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
1724         TraceHelper.INSTANCE.endSection(traceToken);
1725     }
1726 
1727     private void finishCurrentTransitionToRecents() {
1728         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
1729             mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
1730             if (mRecentsAnimationController != null) {
1731                 mRecentsAnimationController.detachNavigationBarFromApp(true);
1732             }
1733         } else if (!hasTargets() || mRecentsAnimationController == null) {
1734             // If there are no targets or the animation not started, then there is nothing to finish
1735             mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
1736         } else {
1737             mRecentsAnimationController.finish(true /* toRecents */,
1738                     () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
1739         }
1740         ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
1741     }
1742 
1743     private void finishCurrentTransitionToHome() {
1744         if (!hasTargets() || mRecentsAnimationController == null) {
1745             // If there are no targets or the animation not started, then there is nothing to finish
1746             mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
1747         } else {
1748             maybeFinishSwipeToHome();
1749             finishRecentsControllerToHome(
1750                     () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
1751         }
1752         ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
1753         doLogGesture(HOME, mRecentsView == null ? null : mRecentsView.getCurrentPageTaskView());
1754     }
1755 
1756     /**
1757      * Notifies SysUI that transition is finished if applicable and also pass leash transactions
1758      * from Launcher to WM.
1759      * This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
1760      */
1761     private void maybeFinishSwipeToHome() {
1762         if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
1763             SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
1764                     mSwipePipToHomeAnimator.getComponentName(),
1765                     mSwipePipToHomeAnimator.getDestinationBounds(),
1766                     mSwipePipToHomeAnimator.getContentOverlay());
1767             mRecentsAnimationController.setFinishTaskTransaction(
1768                     mSwipePipToHomeAnimator.getTaskId(),
1769                     mSwipePipToHomeAnimator.getFinishTransaction(),
1770                     mSwipePipToHomeAnimator.getContentOverlay());
1771             mIsSwipingPipToHome = false;
1772         } else if (mIsSwipeForStagedSplit) {
1773             // Transaction to hide the task to avoid flicker for entering PiP from split-screen.
1774             PictureInPictureSurfaceTransaction tx =
1775                     new PictureInPictureSurfaceTransaction.Builder()
1776                             .setAlpha(0f)
1777                             .build();
1778             int[] taskIds =
1779                         LauncherSplitScreenListener.INSTANCE.getNoCreate().getRunningSplitTaskIds();
1780             for (int taskId : taskIds) {
1781                 mRecentsAnimationController.setFinishTaskTransaction(taskId,
1782                         tx, null /* overlay */);
1783             }
1784         }
1785     }
1786 
1787     protected abstract void finishRecentsControllerToHome(Runnable callback);
1788 
1789     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
1790         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
1791             return;
1792         }
1793         endLauncherTransitionController();
1794         mRecentsView.onSwipeUpAnimationSuccess();
1795         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
1796             mTaskAnimationManager.setLiveTileCleanUpHandler(() -> {
1797                 mRecentsView.cleanupRemoteTargets();
1798                 mInputConsumerProxy.destroy();
1799             });
1800             mTaskAnimationManager.enableLiveTileRestartListener();
1801         }
1802 
1803         SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
1804         doLogGesture(RECENTS, mRecentsView.getCurrentPageTaskView());
1805         reset();
1806     }
1807 
1808     private static boolean isNotInRecents(RemoteAnimationTargetCompat app) {
1809         return app.isNotInRecents
1810                 || app.activityType == ACTIVITY_TYPE_HOME;
1811     }
1812 
1813     /**
1814      * To be called at the end of constructor of subclasses. This calls various methods which can
1815      * depend on proper class initialization.
1816      */
1817     protected void initAfterSubclassConstructor() {
1818         initTransitionEndpoints(mRemoteTargetHandles[0].getTaskViewSimulator()
1819                         .getOrientationState().getLauncherDeviceProfile());
1820     }
1821 
1822     protected void performHapticFeedback() {
1823         VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
1824     }
1825 
1826     public Consumer<MotionEvent> getRecentsViewDispatcher(float navbarRotation) {
1827         return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null;
1828     }
1829 
1830     public void setGestureEndCallback(Runnable gestureEndCallback) {
1831         mGestureEndCallback = gestureEndCallback;
1832     }
1833 
1834     protected void linkRecentsViewScroll() {
1835         SurfaceTransactionApplier.create(mRecentsView, applier -> {
1836             runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
1837                             .setSyncTransactionApplier(applier));
1838             runOnRecentsAnimationAndLauncherBound(() ->
1839                     mRecentsAnimationTargets.addReleaseCheck(applier));
1840         });
1841 
1842         mRecentsView.addOnScrollChangedListener(mOnRecentsScrollListener);
1843         runOnRecentsAnimationAndLauncherBound(() ->
1844                 mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
1845                         mRecentsAnimationTargets));
1846         mRecentsViewScrollLinked = true;
1847     }
1848 
1849     private void onRecentsViewScroll() {
1850         if (moveWindowWithRecentsScroll()) {
1851             updateFinalShift();
1852         }
1853     }
1854 
1855     protected void startNewTask(Consumer<Boolean> resultCallback) {
1856         // Launch the task user scrolled to (mRecentsView.getNextPage()).
1857         if (!mCanceled) {
1858             TaskView nextTask = mRecentsView.getNextPageTaskView();
1859             if (nextTask != null) {
1860                 int taskId = nextTask.getTask().key.id;
1861                 mGestureState.updateLastStartedTaskId(taskId);
1862                 boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
1863                         .contains(taskId);
1864                 nextTask.launchTask(success -> {
1865                     resultCallback.accept(success);
1866                     if (success) {
1867                         if (hasTaskPreviouslyAppeared) {
1868                             onRestartPreviouslyAppearedTask();
1869                         }
1870                     } else {
1871                         mActivityInterface.onLaunchTaskFailed();
1872                         if (mRecentsAnimationController != null) {
1873                             mRecentsAnimationController.finish(true /* toRecents */, null);
1874                         }
1875                     }
1876                 }, true /* freezeTaskList */);
1877             } else {
1878                 mActivityInterface.onLaunchTaskFailed();
1879                 Toast.makeText(mContext, R.string.activity_not_available, LENGTH_SHORT).show();
1880                 if (mRecentsAnimationController != null) {
1881                     mRecentsAnimationController.finish(true /* toRecents */, null);
1882                 }
1883             }
1884         }
1885         mCanceled = false;
1886     }
1887 
1888     /**
1889      * Runs the given {@param action} if the recents animation has already started and Launcher has
1890      * been created and bound to the TouchInteractionService, or queues it to be run when it this
1891      * next happens.
1892      */
1893     private void runOnRecentsAnimationAndLauncherBound(Runnable action) {
1894         mRecentsAnimationStartCallbacks.add(action);
1895         flushOnRecentsAnimationAndLauncherBound();
1896     }
1897 
1898     private void flushOnRecentsAnimationAndLauncherBound() {
1899         if (mRecentsAnimationTargets == null ||
1900                 !mStateCallback.hasStates(STATE_LAUNCHER_BIND_TO_SERVICE)) {
1901             return;
1902         }
1903 
1904         if (!mRecentsAnimationStartCallbacks.isEmpty()) {
1905             for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
1906                 action.run();
1907             }
1908             mRecentsAnimationStartCallbacks.clear();
1909         }
1910     }
1911 
1912     /**
1913      * TODO can we remove this now that we don't finish the controller until onTaskAppeared()?
1914      * @return whether the recents animation has started and there are valid app targets.
1915      */
1916     protected boolean hasTargets() {
1917         return mRecentsAnimationTargets != null && mRecentsAnimationTargets.hasTargets();
1918     }
1919 
1920     @Override
1921     public void onRecentsAnimationFinished(RecentsAnimationController controller) {
1922         if (!controller.getFinishTargetIsLauncher()) {
1923             TaskViewUtils.setSplitAuxiliarySurfacesShown(mRecentsAnimationTargets.nonApps,
1924                     true /*shown*/, true /*animate*/);
1925         }
1926         mRecentsAnimationController = null;
1927         mRecentsAnimationTargets = null;
1928         if (mRecentsView != null) {
1929             mRecentsView.setRecentsAnimationTargets(null, null);
1930         }
1931     }
1932 
1933     @Override
1934     public void onTasksAppeared(RemoteAnimationTargetCompat[] appearedTaskTargets) {
1935         if (mRecentsAnimationController != null) {
1936             if (handleTaskAppeared(appearedTaskTargets)) {
1937                 mRecentsAnimationController.finish(false /* toRecents */,
1938                         null /* onFinishComplete */);
1939                 mActivityInterface.onLaunchTaskSuccess();
1940                 ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
1941             }
1942         }
1943     }
1944 
1945     /**
1946      * @return The index of the TaskView in RecentsView whose taskId matches the task that will
1947      * resume if we finish the controller.
1948      */
1949     protected int getLastAppearedTaskIndex() {
1950         return mGestureState.getLastAppearedTaskId() != -1
1951                 ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
1952                 : mRecentsView.getRunningTaskIndex();
1953     }
1954 
1955     /**
1956      * @return Whether we are continuing a gesture that already landed on a new task,
1957      * but before that task appeared.
1958      */
1959     protected boolean hasStartedNewTask() {
1960         return mGestureState.getLastStartedTaskId() != -1;
1961     }
1962 
1963     /**
1964      * Registers a callback to run when the activity is ready.
1965      */
1966     public void initWhenReady() {
1967         // Preload the plan
1968         RecentsModel.INSTANCE.get(mContext).getTasks(null);
1969 
1970         mActivityInitListener.register();
1971     }
1972 
1973     /**
1974      * Applies the transform on the recents animation
1975      */
1976     protected void applyScrollAndTransform() {
1977         // No need to apply any transform if there is ongoing swipe-pip-to-home animator since
1978         // that animator handles the leash solely.
1979         boolean notSwipingPipToHome = mRecentsAnimationTargets != null && !mIsSwipingPipToHome;
1980         boolean setRecentsScroll = mRecentsViewScrollLinked && mRecentsView != null;
1981         for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
1982             AnimatorControllerWithResistance playbackController =
1983                     remoteHandle.getPlaybackController();
1984             if (playbackController != null) {
1985                 playbackController.setProgress(Math.max(mCurrentShift.value,
1986                         getScaleProgressDueToScroll()), mDragLengthFactor);
1987             }
1988 
1989             if (notSwipingPipToHome) {
1990                 TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator();
1991                 if (setRecentsScroll) {
1992                     taskViewSimulator.setScroll(mRecentsView.getScrollOffset());
1993                 }
1994                 taskViewSimulator.apply(remoteHandle.getTransformParams());
1995             }
1996         }
1997         ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
1998     }
1999 
2000     // Scaling of RecentsView during quick switch based on amount of recents scroll
2001     private float getScaleProgressDueToScroll() {
2002         if (mActivity == null || !mActivity.getDeviceProfile().isTablet || mRecentsView == null
2003                 || !mRecentsViewScrollLinked) {
2004             return 0;
2005         }
2006 
2007         float scrollOffset = Math.abs(mRecentsView.getScrollOffset(mRecentsView.getCurrentPage()));
2008         int maxScrollOffset = mRecentsView.getPagedOrientationHandler().getPrimaryValue(
2009                 mRecentsView.getLastComputedTaskSize().width(),
2010                 mRecentsView.getLastComputedTaskSize().height());
2011         maxScrollOffset += mRecentsView.getPageSpacing();
2012 
2013         float maxScaleProgress =
2014                 MAX_QUICK_SWITCH_RECENTS_SCALE_PROGRESS * mRecentsView.getMaxScaleForFullScreen();
2015         float scaleProgress = maxScaleProgress;
2016 
2017         if (scrollOffset < mQuickSwitchScaleScrollThreshold) {
2018             scaleProgress = Utilities.mapToRange(scrollOffset, 0, mQuickSwitchScaleScrollThreshold,
2019                     0, maxScaleProgress, ACCEL_DEACCEL);
2020         } else if (scrollOffset > (maxScrollOffset - mQuickSwitchScaleScrollThreshold)) {
2021             scaleProgress = Utilities.mapToRange(scrollOffset,
2022                     (maxScrollOffset - mQuickSwitchScaleScrollThreshold), maxScrollOffset,
2023                     maxScaleProgress, 0, ACCEL_DEACCEL);
2024         }
2025 
2026         return scaleProgress;
2027     }
2028 
2029     /**
2030      * Used for winscope tracing, see launcher_trace.proto
2031      * @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
2032      * @param inputConsumerProto The parent of this proto message.
2033      */
2034     public void writeToProto(InputConsumerProto.Builder inputConsumerProto) {
2035         SwipeHandlerProto.Builder swipeHandlerProto = SwipeHandlerProto.newBuilder();
2036 
2037         mGestureState.writeToProto(swipeHandlerProto);
2038 
2039         swipeHandlerProto.setIsRecentsAttachedToAppWindow(
2040                 mAnimationFactory.isRecentsAttachedToAppWindow());
2041         swipeHandlerProto.setScrollOffset(mRecentsView == null
2042                 ? 0
2043                 : mRecentsView.getScrollOffset());
2044         swipeHandlerProto.setAppToOverviewProgress(mCurrentShift.value);
2045 
2046         inputConsumerProto.setSwipeHandler(swipeHandlerProto);
2047     }
2048 
2049     public interface Factory {
2050         AbsSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs);
2051     }
2052 }
2053