• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.quickstep;
17 
18 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
19 import static android.view.Surface.ROTATION_0;
20 import static android.view.Surface.ROTATION_270;
21 import static android.view.Surface.ROTATION_90;
22 import static android.widget.Toast.LENGTH_SHORT;
23 
24 import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
25 import static com.android.app.animation.Interpolators.DECELERATE;
26 import static com.android.app.animation.Interpolators.OVERSHOOT_1_2;
27 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
28 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
29 import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
30 import static com.android.launcher3.PagedView.INVALID_PAGE;
31 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
32 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
33 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
34 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
35 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
36 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
37 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
38 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
39 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
40 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
41 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
42 import static com.android.quickstep.GestureState.GestureEndTarget.ALL_APPS;
43 import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
44 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
45 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
46 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
47 import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
48 import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
49 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_CANCELED;
50 import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
51 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
52 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
53 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED;
54 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.LAUNCHER_DESTROYED;
55 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
56 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
57 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
58 
59 import android.animation.Animator;
60 import android.animation.AnimatorListenerAdapter;
61 import android.animation.AnimatorSet;
62 import android.animation.ValueAnimator;
63 import android.annotation.TargetApi;
64 import android.app.Activity;
65 import android.app.ActivityManager;
66 import android.app.TaskInfo;
67 import android.app.WindowConfiguration;
68 import android.content.Context;
69 import android.content.Intent;
70 import android.content.res.Resources;
71 import android.graphics.Matrix;
72 import android.graphics.PointF;
73 import android.graphics.Rect;
74 import android.graphics.RectF;
75 import android.os.Build;
76 import android.os.IBinder;
77 import android.os.SystemClock;
78 import android.util.Log;
79 import android.view.MotionEvent;
80 import android.view.RemoteAnimationTarget;
81 import android.view.View;
82 import android.view.View.OnApplyWindowInsetsListener;
83 import android.view.ViewGroup;
84 import android.view.ViewTreeObserver.OnDrawListener;
85 import android.view.ViewTreeObserver.OnScrollChangedListener;
86 import android.view.WindowInsets;
87 import android.view.animation.Interpolator;
88 import android.widget.Toast;
89 import android.window.PictureInPictureSurfaceTransaction;
90 
91 import androidx.annotation.Nullable;
92 import androidx.annotation.UiThread;
93 
94 import com.android.internal.util.LatencyTracker;
95 import com.android.launcher3.AbstractFloatingView;
96 import com.android.launcher3.DeviceProfile;
97 import com.android.launcher3.LauncherPrefs;
98 import com.android.launcher3.R;
99 import com.android.launcher3.Utilities;
100 import com.android.launcher3.anim.AnimationSuccessListener;
101 import com.android.launcher3.anim.AnimatorPlaybackController;
102 import com.android.launcher3.dragndrop.DragView;
103 import com.android.launcher3.logging.StatsLogManager;
104 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
105 import com.android.launcher3.statehandlers.DesktopVisibilityController;
106 import com.android.launcher3.statemanager.BaseState;
107 import com.android.launcher3.statemanager.StatefulActivity;
108 import com.android.launcher3.taskbar.TaskbarThresholdUtils;
109 import com.android.launcher3.taskbar.TaskbarUIController;
110 import com.android.launcher3.uioverrides.QuickstepLauncher;
111 import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
112 import com.android.launcher3.util.DisplayController;
113 import com.android.launcher3.util.SafeCloseable;
114 import com.android.launcher3.util.TraceHelper;
115 import com.android.launcher3.util.VibratorWrapper;
116 import com.android.launcher3.util.WindowBounds;
117 import com.android.quickstep.BaseActivityInterface.AnimationFactory;
118 import com.android.quickstep.GestureState.GestureEndTarget;
119 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
120 import com.android.quickstep.util.ActiveGestureErrorDetector;
121 import com.android.quickstep.util.ActiveGestureLog;
122 import com.android.quickstep.util.ActivityInitListener;
123 import com.android.quickstep.util.AnimatorControllerWithResistance;
124 import com.android.quickstep.util.InputConsumerProxy;
125 import com.android.quickstep.util.InputProxyHandlerFactory;
126 import com.android.quickstep.util.MotionPauseDetector;
127 import com.android.quickstep.util.RecentsOrientedState;
128 import com.android.quickstep.util.RectFSpringAnim;
129 import com.android.quickstep.util.StaggeredWorkspaceAnim;
130 import com.android.quickstep.util.SurfaceTransaction;
131 import com.android.quickstep.util.SurfaceTransactionApplier;
132 import com.android.quickstep.util.SwipePipToHomeAnimator;
133 import com.android.quickstep.util.TaskViewSimulator;
134 import com.android.quickstep.views.DesktopTaskView;
135 import com.android.quickstep.views.RecentsView;
136 import com.android.quickstep.views.TaskView;
137 import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
138 import com.android.systemui.shared.recents.model.Task;
139 import com.android.systemui.shared.recents.model.ThumbnailData;
140 import com.android.systemui.shared.system.ActivityManagerWrapper;
141 import com.android.systemui.shared.system.InputConsumerController;
142 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
143 import com.android.systemui.shared.system.SysUiStatsLog;
144 import com.android.systemui.shared.system.TaskStackChangeListener;
145 import com.android.systemui.shared.system.TaskStackChangeListeners;
146 import com.android.wm.shell.common.TransactionPool;
147 import com.android.wm.shell.startingsurface.SplashScreenExitAnimationUtils;
148 
149 import java.util.ArrayList;
150 import java.util.Arrays;
151 import java.util.HashMap;
152 import java.util.Objects;
153 import java.util.Optional;
154 import java.util.OptionalInt;
155 import java.util.function.Consumer;
156 
157 /**
158  * Handles the navigation gestures when Launcher is the default home activity.
159  */
160 @TargetApi(Build.VERSION_CODES.R)
161 public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
162         Q extends RecentsView, S extends BaseState<S>>
163         extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener,
164         RecentsAnimationCallbacks.RecentsAnimationListener {
165     private static final String TAG = "AbsSwipeUpHandler";
166 
167     private static final ArrayList<String> STATE_NAMES = new ArrayList<>();
168 
169     protected final BaseActivityInterface<S, T> mActivityInterface;
170     protected final InputConsumerProxy mInputConsumerProxy;
171     protected final ActivityInitListener mActivityInitListener;
172     // Callbacks to be made once the recents animation starts
173     private final ArrayList<Runnable> mRecentsAnimationStartCallbacks = new ArrayList<>();
174     private final OnScrollChangedListener mOnRecentsScrollListener = this::onRecentsViewScroll;
175 
176     // Null if the recents animation hasn't started yet or has been canceled or finished.
177     protected @Nullable RecentsAnimationController mRecentsAnimationController;
178     protected @Nullable RecentsAnimationController mDeferredCleanupRecentsAnimationController;
179     protected RecentsAnimationTargets mRecentsAnimationTargets;
180     protected T mActivity;
181     protected @Nullable Q mRecentsView;
182     protected Runnable mGestureEndCallback;
183     protected MultiStateCallback mStateCallback;
184     protected boolean mCanceled;
185     private boolean mRecentsViewScrollLinked = false;
186     private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks =
187             new ActivityLifecycleCallbacksAdapter() {
188                 @Override
189                 public void onActivityDestroyed(Activity activity) {
190                     if (mActivity != activity) {
191                         return;
192                     }
193                     ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
194                     mRecentsView = null;
195                     mActivity = null;
196                     mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
197                 }
198             };
199 
200     private static int FLAG_COUNT = 0;
getNextStateFlag(String name)201     private static int getNextStateFlag(String name) {
202         if (DEBUG_STATES) {
203             STATE_NAMES.add(name);
204         }
205         int index = 1 << FLAG_COUNT;
206         FLAG_COUNT++;
207         return index;
208     }
209 
210     // Launcher UI related states
211     protected static final int STATE_LAUNCHER_PRESENT =
212             getNextStateFlag("STATE_LAUNCHER_PRESENT");
213     protected static final int STATE_LAUNCHER_STARTED =
214             getNextStateFlag("STATE_LAUNCHER_STARTED");
215     protected static final int STATE_LAUNCHER_DRAWN =
216             getNextStateFlag("STATE_LAUNCHER_DRAWN");
217     // Called when the Launcher has connected to the touch interaction service (and the taskbar
218     // ui controller is initialized)
219     protected static final int STATE_LAUNCHER_BIND_TO_SERVICE =
220             getNextStateFlag("STATE_LAUNCHER_BIND_TO_SERVICE");
221 
222     // Internal initialization states
223     private static final int STATE_APP_CONTROLLER_RECEIVED =
224             getNextStateFlag("STATE_APP_CONTROLLER_RECEIVED");
225 
226     // Interaction finish states
227     private static final int STATE_SCALED_CONTROLLER_HOME =
228             getNextStateFlag("STATE_SCALED_CONTROLLER_HOME");
229     private static final int STATE_SCALED_CONTROLLER_RECENTS =
230             getNextStateFlag("STATE_SCALED_CONTROLLER_RECENTS");
231 
232     protected static final int STATE_HANDLER_INVALIDATED =
233             getNextStateFlag("STATE_HANDLER_INVALIDATED");
234     private static final int STATE_GESTURE_STARTED =
235             getNextStateFlag("STATE_GESTURE_STARTED");
236     private static final int STATE_GESTURE_CANCELLED =
237             getNextStateFlag("STATE_GESTURE_CANCELLED");
238     private static final int STATE_GESTURE_COMPLETED =
239             getNextStateFlag("STATE_GESTURE_COMPLETED");
240 
241     private static final int STATE_CAPTURE_SCREENSHOT =
242             getNextStateFlag("STATE_CAPTURE_SCREENSHOT");
243     protected static final int STATE_SCREENSHOT_CAPTURED =
244             getNextStateFlag("STATE_SCREENSHOT_CAPTURED");
245     private static final int STATE_SCREENSHOT_VIEW_SHOWN =
246             getNextStateFlag("STATE_SCREENSHOT_VIEW_SHOWN");
247 
248     private static final int STATE_RESUME_LAST_TASK =
249             getNextStateFlag("STATE_RESUME_LAST_TASK");
250     private static final int STATE_START_NEW_TASK =
251             getNextStateFlag("STATE_START_NEW_TASK");
252     private static final int STATE_CURRENT_TASK_FINISHED =
253             getNextStateFlag("STATE_CURRENT_TASK_FINISHED");
254     private static final int STATE_FINISH_WITH_NO_END =
255             getNextStateFlag("STATE_FINISH_WITH_NO_END");
256     private static final int STATE_SETTLED_ON_ALL_APPS =
257             getNextStateFlag("STATE_SETTLED_ON_ALL_APPS");
258 
259     private static final int LAUNCHER_UI_STATES =
260             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED |
261                     STATE_LAUNCHER_BIND_TO_SERVICE;
262 
263     public static final long MAX_SWIPE_DURATION = 350;
264 
265     public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
266     private static final float SWIPE_DURATION_MULTIPLIER =
267             Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
268     private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured";
269 
270     public static final long RECENTS_ATTACH_DURATION = 300;
271 
272     private static final float MAX_QUICK_SWITCH_RECENTS_SCALE_PROGRESS = 0.07f;
273 
274     // Controls task thumbnail splash's reveal animation after landing on a task from quickswitch.
275     // These values match WindowManager/Shell starting_window_app_reveal_* config values.
276     private static final int SPLASH_FADE_OUT_DURATION = 133;
277     private static final int SPLASH_APP_REVEAL_DELAY = 83;
278     private static final int SPLASH_APP_REVEAL_DURATION = 266;
279     private static final int SPLASH_ANIMATION_DURATION = 349;
280 
281     /**
282      * Used as the page index for logging when we return to the last task at the end of the gesture.
283      */
284     private static final int LOG_NO_OP_PAGE_INDEX = -1;
285 
286     protected final TaskAnimationManager mTaskAnimationManager;
287 
288     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
289     private RunningWindowAnim[] mRunningWindowAnim;
290     // Possible second animation running at the same time as mRunningWindowAnim
291     private Animator mParallelRunningAnim;
292     private boolean mIsMotionPaused;
293     private boolean mHasMotionEverBeenPaused;
294 
295     private boolean mContinuingLastGesture;
296 
297     // Cache of recently-updated task snapshots, mapping task id to ThumbnailData
298     private HashMap<Integer, ThumbnailData> mTaskSnapshotCache = new HashMap<>();
299 
300     // Used to control launcher components throughout the swipe gesture.
301     private AnimatorControllerWithResistance mLauncherTransitionController;
302     private boolean mHasEndedLauncherTransition;
303 
304     private AnimationFactory mAnimationFactory = (t) -> { };
305 
306     private boolean mWasLauncherAlreadyVisible;
307 
308     private boolean mGestureStarted;
309     private boolean mLogDirectionUpOrLeft = true;
310     private boolean mIsLikelyToStartNewTask;
311     private boolean mIsInAllAppsRegion;
312 
313     private final long mTouchTimeMs;
314     private long mLauncherFrameDrawnTime;
315 
316     private final int mSplashMainWindowShiftLength;
317 
318     private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
319 
320     private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
321     protected boolean mIsSwipingPipToHome;
322     // TODO(b/195473090) no split PIP for now, remove once we have more clarity
323     //  can try to have RectFSpringAnim evaluate multiple rects at once
324     private final SwipePipToHomeAnimator[] mSwipePipToHomeAnimators =
325             new SwipePipToHomeAnimator[2];
326 
327     // Interpolate RecentsView scale from start of quick switch scroll until this scroll threshold
328     private final float mQuickSwitchScaleScrollThreshold;
329 
330     private final int mTaskbarAppWindowThreshold;
331     private final int mTaskbarHomeOverviewThreshold;
332     private final int mTaskbarCatchUpThreshold;
333     private final boolean mTaskbarAlreadyOpen;
334     private final boolean mIsTaskbarAllAppsOpen;
335     private final boolean mIsTransientTaskbar;
336     // May be set to false when mIsTransientTaskbar is true.
337     private boolean mCanSlowSwipeGoHome = true;
338     // Indicates whether the divider is shown, only used when split screen is activated.
339     private boolean mIsDividerShown = true;
340     private boolean mStartMovingTasks;
341 
342     @Nullable
343     private RemoteAnimationTargets.ReleaseCheck mSwipePipToHomeReleaseCheck = null;
344 
AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState, TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs, boolean continuingLastGesture, InputConsumerController inputConsumer)345     public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
346             TaskAnimationManager taskAnimationManager, GestureState gestureState,
347             long touchTimeMs, boolean continuingLastGesture,
348             InputConsumerController inputConsumer) {
349         super(context, deviceState, gestureState);
350         mActivityInterface = gestureState.getActivityInterface();
351         mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
352         mInputConsumerProxy =
353                 new InputConsumerProxy(context, /* rotationSupplier = */ () -> {
354                     if (mRecentsView == null) {
355                         return ROTATION_0;
356                     }
357                     return mRecentsView.getPagedViewOrientedState().getRecentsActivityRotation();
358                 }, inputConsumer, /* callback = */ () -> {
359                     endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
360                     endLauncherTransitionController();
361                 }, new InputProxyHandlerFactory(mActivityInterface, mGestureState));
362         mTaskAnimationManager = taskAnimationManager;
363         mTouchTimeMs = touchTimeMs;
364         mContinuingLastGesture = continuingLastGesture;
365 
366         Resources res = context.getResources();
367         mQuickSwitchScaleScrollThreshold = res
368                 .getDimension(R.dimen.quick_switch_scaling_scroll_threshold);
369 
370         mSplashMainWindowShiftLength = -res
371                 .getDimensionPixelSize(R.dimen.starting_surface_exit_animation_window_shift_length);
372 
373         initTransitionEndpoints(mRemoteTargetHandles[0].getTaskViewSimulator()
374                 .getOrientationState().getLauncherDeviceProfile());
375         initStateCallbacks();
376 
377         mIsTransientTaskbar = mDp.isTaskbarPresent
378                 && DisplayController.isTransientTaskbar(mActivity);
379         TaskbarUIController controller = mActivityInterface.getTaskbarController();
380         mTaskbarAlreadyOpen = controller != null && !controller.isTaskbarStashed();
381         mIsTaskbarAllAppsOpen = controller != null && controller.isTaskbarAllAppsOpen();
382         mTaskbarAppWindowThreshold =
383                 TaskbarThresholdUtils.getAppWindowThreshold(res, mDp);
384         boolean swipeWillNotShowTaskbar = mTaskbarAlreadyOpen || mGestureState.isTrackpadGesture();
385         mTaskbarHomeOverviewThreshold = swipeWillNotShowTaskbar
386                 ? 0
387                 : TaskbarThresholdUtils.getHomeOverviewThreshold(res, mDp);
388         mTaskbarCatchUpThreshold = TaskbarThresholdUtils.getCatchUpThreshold(res, mDp);
389     }
390 
391     @Nullable
getTrackedEventForState(int stateFlag)392     private static ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateFlag) {
393         if (stateFlag == STATE_GESTURE_STARTED) {
394             return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_STARTED;
395         } else if (stateFlag == STATE_GESTURE_COMPLETED) {
396             return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_COMPLETED;
397         } else if (stateFlag == STATE_GESTURE_CANCELLED) {
398             return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_CANCELLED;
399         } else if (stateFlag == STATE_SCREENSHOT_CAPTURED) {
400             return ActiveGestureErrorDetector.GestureEvent.STATE_SCREENSHOT_CAPTURED;
401         } else if (stateFlag == STATE_CAPTURE_SCREENSHOT) {
402             return ActiveGestureErrorDetector.GestureEvent.STATE_CAPTURE_SCREENSHOT;
403         } else if (stateFlag == STATE_HANDLER_INVALIDATED) {
404             return ActiveGestureErrorDetector.GestureEvent.STATE_HANDLER_INVALIDATED;
405         } else if (stateFlag == STATE_LAUNCHER_DRAWN) {
406             return ActiveGestureErrorDetector.GestureEvent.STATE_LAUNCHER_DRAWN;
407         }
408         return null;
409     }
410 
initStateCallbacks()411     private void initStateCallbacks() {
412         mStateCallback = new MultiStateCallback(
413                 STATE_NAMES.toArray(new String[0]), AbsSwipeUpHandler::getTrackedEventForState);
414 
415         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
416                 this::onLauncherPresentAndGestureStarted);
417 
418         mStateCallback.runOnceAtState(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
419                 this::initializeLauncherAnimationController);
420 
421         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
422                 this::launcherFrameDrawn);
423 
424         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
425                         | STATE_GESTURE_CANCELLED,
426                 this::resetStateForAnimationCancel);
427 
428         mStateCallback.runOnceAtState(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
429                 this::resumeLastTask);
430         mStateCallback.runOnceAtState(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
431                 this::startNewTask);
432 
433         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
434                         | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT,
435                 this::switchToScreenshot);
436 
437         mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
438                         | STATE_SCALED_CONTROLLER_RECENTS,
439                 this::finishCurrentTransitionToRecents);
440 
441         mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
442                         | STATE_SCALED_CONTROLLER_HOME,
443                 this::finishCurrentTransitionToHome);
444         mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
445                 this::reset);
446         mStateCallback.runOnceAtState(STATE_SETTLED_ON_ALL_APPS | STATE_SCREENSHOT_CAPTURED
447                         | STATE_GESTURE_COMPLETED,
448                 this::finishCurrentTransitionToAllApps);
449 
450         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
451                         | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
452                         | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
453                         | STATE_GESTURE_STARTED,
454                 this::setupLauncherUiAfterSwipeUpToRecentsAnimation);
455 
456         mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED,
457                 this::continueComputingRecentsScrollIfNecessary);
458         mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED
459                         | STATE_RECENTS_SCROLLING_FINISHED,
460                 this::onSettledOnEndTarget);
461 
462         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
463         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
464                 this::invalidateHandlerWithLauncher);
465         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
466                 this::resetStateForAnimationCancel);
467         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_FINISH_WITH_NO_END,
468                 this::resetStateForAnimationCancel);
469     }
470 
onActivityInit(Boolean alreadyOnHome)471     protected boolean onActivityInit(Boolean alreadyOnHome) {
472         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
473             return false;
474         }
475 
476         T createdActivity = mActivityInterface.getCreatedActivity();
477         if (createdActivity != null) {
478             initTransitionEndpoints(createdActivity.getDeviceProfile());
479         }
480         final T activity = mActivityInterface.getCreatedActivity();
481         if (mActivity == activity) {
482             return true;
483         }
484 
485         if (mActivity != null) {
486             if (mStateCallback.hasStates(STATE_GESTURE_COMPLETED)) {
487                 // If the activity has restarted between setting the page scroll settling callback
488                 // and actually receiving the callback, just mark the gesture completed
489                 mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
490                 return true;
491             }
492 
493             // The launcher may have been recreated as a result of device rotation.
494             int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
495             initStateCallbacks();
496             mStateCallback.setState(oldState);
497         }
498         mWasLauncherAlreadyVisible = alreadyOnHome;
499         mActivity = activity;
500         // Override the visibility of the activity until the gesture actually starts and we swipe
501         // up, or until we transition home and the home animation is composed
502         if (alreadyOnHome) {
503             mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
504         } else {
505             mActivity.addForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
506         }
507 
508         mRecentsView = activity.getOverviewPanel();
509         mRecentsView.setOnPageTransitionEndCallback(null);
510 
511         mStateCallback.setState(STATE_LAUNCHER_PRESENT);
512         if (alreadyOnHome) {
513             onLauncherStart();
514         } else {
515             activity.runOnceOnStart(this::onLauncherStart);
516         }
517 
518         // Set up a entire animation lifecycle callback to notify the current recents view when
519         // the animation is canceled
520         mGestureState.runOnceAtState(STATE_RECENTS_ANIMATION_CANCELED, () -> {
521             if (mRecentsView == null) return;
522 
523             HashMap<Integer, ThumbnailData> snapshots =
524                     mGestureState.consumeRecentsAnimationCanceledSnapshot();
525             if (snapshots != null) {
526                 mRecentsView.switchToScreenshot(snapshots, () -> {
527                     if (mRecentsAnimationController != null) {
528                         mRecentsAnimationController.cleanupScreenshot();
529                     } else if (mDeferredCleanupRecentsAnimationController != null) {
530                         mDeferredCleanupRecentsAnimationController.cleanupScreenshot();
531                         mDeferredCleanupRecentsAnimationController = null;
532                     }
533                 });
534                 mRecentsView.onRecentsAnimationComplete();
535             }
536         });
537 
538         setupRecentsViewUi();
539         mRecentsView.runOnPageScrollsInitialized(this::linkRecentsViewScroll);
540         activity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
541 
542         mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
543         return true;
544     }
545 
546     /**
547      * Return true if the window should be translated horizontally if the recents view scrolls
548      */
moveWindowWithRecentsScroll()549     protected boolean moveWindowWithRecentsScroll() {
550         return mGestureState.getEndTarget() != HOME;
551     }
552 
onLauncherStart()553     private void onLauncherStart() {
554         final T activity = mActivityInterface.getCreatedActivity();
555         if (mActivity != activity) {
556             return;
557         }
558         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
559             return;
560         }
561         // RecentsView never updates the display rotation until swipe-up, force update
562         // RecentsOrientedState before passing to TaskViewSimulator.
563         mRecentsView.updateRecentsRotation();
564         runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
565                 .setOrientationState(mRecentsView.getPagedViewOrientedState()));
566 
567         // If we've already ended the gesture and are going home, don't prepare recents UI,
568         // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
569         if (mGestureState.getEndTarget() != HOME) {
570             Runnable initAnimFactory = () -> {
571                 mAnimationFactory = mActivityInterface.prepareRecentsUI(mDeviceState,
572                         mWasLauncherAlreadyVisible, this::onAnimatorPlaybackControllerCreated);
573                 maybeUpdateRecentsAttachedState(false /* animate */);
574                 if (mGestureState.getEndTarget() != null) {
575                     // Update the end target in case the gesture ended before we init.
576                     mAnimationFactory.setEndTarget(mGestureState.getEndTarget());
577                 }
578             };
579             if (mWasLauncherAlreadyVisible) {
580                 // Launcher is visible, but might be about to stop. Thus, if we prepare recents
581                 // now, it might get overridden by moveToRestState() in onStop(). To avoid this,
582                 // wait until the next gesture (and possibly launcher) starts.
583                 mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, initAnimFactory);
584             } else {
585                 initAnimFactory.run();
586             }
587         }
588         AbstractFloatingView.closeAllOpenViewsExcept(activity, mWasLauncherAlreadyVisible,
589                 AbstractFloatingView.TYPE_LISTENER);
590 
591         if (mWasLauncherAlreadyVisible) {
592             mStateCallback.setState(STATE_LAUNCHER_DRAWN);
593         } else {
594             SafeCloseable traceToken = TraceHelper.INSTANCE.beginAsyncSection("WTS-init");
595             View dragLayer = activity.getDragLayer();
596             dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
597                 boolean mHandled = false;
598 
599                 @Override
600                 public void onDraw() {
601                     if (mHandled) {
602                         return;
603                     }
604                     mHandled = true;
605 
606                     traceToken.close();
607                     dragLayer.post(() ->
608                             dragLayer.getViewTreeObserver().removeOnDrawListener(this));
609                     if (activity != mActivity) {
610                         return;
611                     }
612 
613                     mStateCallback.setState(STATE_LAUNCHER_DRAWN);
614                 }
615             });
616         }
617 
618         activity.getRootView().setOnApplyWindowInsetsListener(this);
619         mStateCallback.setState(STATE_LAUNCHER_STARTED);
620     }
621 
onLauncherBindToService()622     private void onLauncherBindToService() {
623         mStateCallback.setState(STATE_LAUNCHER_BIND_TO_SERVICE);
624         flushOnRecentsAnimationAndLauncherBound();
625     }
626 
onLauncherPresentAndGestureStarted()627     private void onLauncherPresentAndGestureStarted() {
628         // Re-setup the recents UI when gesture starts, as the state could have been changed during
629         // that time by a previous window transition.
630         setupRecentsViewUi();
631 
632         // For the duration of the gesture, in cases where an activity is launched while the
633         // activity is not yet resumed, finish the animation to ensure we get resumed
634         mGestureState.getActivityInterface().setOnDeferredActivityLaunchCallback(
635                 mOnDeferredActivityLaunch);
636 
637         mGestureState.runOnceAtState(STATE_END_TARGET_SET,
638                 () -> {
639                     mDeviceState.getRotationTouchHelper()
640                             .onEndTargetCalculated(mGestureState.getEndTarget(),
641                                     mActivityInterface);
642                 });
643 
644         notifyGestureStarted();
645     }
646 
onDeferredActivityLaunch()647     private void onDeferredActivityLaunch() {
648         mActivityInterface.switchRunningTaskViewToScreenshot(
649                 null, () -> {
650                     mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
651                 });
652     }
653 
setupRecentsViewUi()654     private void setupRecentsViewUi() {
655         if (mContinuingLastGesture) {
656             updateSysUiFlags(mCurrentShift.value);
657             return;
658         }
659         notifyGestureAnimationStartToRecents();
660     }
661 
notifyGestureAnimationStartToRecents()662     protected void notifyGestureAnimationStartToRecents() {
663         Task[] runningTasks;
664         TopTaskTracker.CachedTaskInfo cachedTaskInfo = mGestureState.getRunningTask();
665         if (mIsSwipeForSplit) {
666             int[] splitTaskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds();
667             runningTasks = cachedTaskInfo.getPlaceholderTasks(splitTaskIds);
668         } else {
669             runningTasks = cachedTaskInfo.getPlaceholderTasks();
670         }
671 
672         // Safeguard against any null tasks being sent to recents view, happens when quickswitching
673         // very quickly w/ split tasks because TopTaskTracker provides stale information compared to
674         // actual running tasks in the recents animation.
675         // TODO(b/236226779), Proper fix (ag/22237143)
676         if (Arrays.stream(runningTasks).anyMatch(Objects::isNull)) {
677             return;
678         }
679         mRecentsView.onGestureAnimationStart(runningTasks, mDeviceState.getRotationTouchHelper());
680     }
681 
launcherFrameDrawn()682     private void launcherFrameDrawn() {
683         mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
684     }
685 
initializeLauncherAnimationController()686     private void initializeLauncherAnimationController() {
687         buildAnimationController();
688 
689         try (SafeCloseable c = TraceHelper.INSTANCE.allowIpcs("logToggleRecents")) {
690             LatencyTracker.getInstance(mContext).logAction(LatencyTracker.ACTION_TOGGLE_RECENTS,
691                     (int) (mLauncherFrameDrawnTime - mTouchTimeMs));
692         }
693 
694         // This method is only called when STATE_GESTURE_STARTED is set, so we can enable the
695         // high-res thumbnail loader here once we are sure that we will end up in an overview state
696         RecentsModel.INSTANCE.get(mContext).getThumbnailCache()
697                 .getHighResLoadingState().setVisible(true);
698     }
699 
getMotionPauseListener()700     public MotionPauseDetector.OnMotionPauseListener getMotionPauseListener() {
701         return new MotionPauseDetector.OnMotionPauseListener() {
702             @Override
703             public void onMotionPauseDetected() {
704                 mHasMotionEverBeenPaused = true;
705                 maybeUpdateRecentsAttachedState(true/* animate */, true/* moveRunningTask */);
706                 Optional.ofNullable(mActivityInterface.getTaskbarController())
707                         .ifPresent(TaskbarUIController::startTranslationSpring);
708                 if (!mIsInAllAppsRegion) {
709                     performHapticFeedback();
710                 }
711             }
712 
713             @Override
714             public void onMotionPauseChanged(boolean isPaused) {
715                 mIsMotionPaused = isPaused;
716             }
717         };
718     }
719 
720     private void maybeUpdateRecentsAttachedState() {
721         maybeUpdateRecentsAttachedState(true /* animate */);
722     }
723 
724     protected void maybeUpdateRecentsAttachedState(boolean animate) {
725         maybeUpdateRecentsAttachedState(animate, false /* moveRunningTask */);
726     }
727 
728     /**
729      * Determines whether to show or hide RecentsView. The window is always
730      * synchronized with its corresponding TaskView in RecentsView, so if
731      * RecentsView is shown, it will appear to be attached to the window.
732      *
733      * Note this method has no effect unless the navigation mode is NO_BUTTON.
734      * @param animate whether to animate when attaching RecentsView
735      * @param moveRunningTask whether to move running task to front when attaching
736      */
737     private void maybeUpdateRecentsAttachedState(boolean animate, boolean moveRunningTask) {
738         if ((!mDeviceState.isFullyGesturalNavMode() && !mGestureState.isTrackpadGesture())
739                 || mRecentsView == null) {
740             return;
741         }
742         // looking at single target is fine here since either app of a split pair would
743         // have their "isInRecents" field set? (that's what this is used for below)
744         RemoteAnimationTarget runningTaskTarget = mRecentsAnimationTargets != null
745                 ? mRecentsAnimationTargets
746                 .findTask(mGestureState.getTopRunningTaskId())
747                 : null;
748         final boolean recentsAttachedToAppWindow;
749         if (mIsInAllAppsRegion) {
750             recentsAttachedToAppWindow = false;
751         } else if (mGestureState.getEndTarget() != null) {
752             recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow;
753         } else if (mContinuingLastGesture
754                 && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
755             recentsAttachedToAppWindow = true;
756         } else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) {
757             // The window is going away so make sure recents is always visible in this case.
758             recentsAttachedToAppWindow = true;
759         } else {
760             recentsAttachedToAppWindow = mHasMotionEverBeenPaused || mIsLikelyToStartNewTask;
761         }
762         if (moveRunningTask && !mAnimationFactory.hasRecentsEverAttachedToAppWindow()
763                 && recentsAttachedToAppWindow) {
764             // Only move running task if RecentsView has never been attached before, to avoid
765             // TaskView jumping to new position as we move the tasks.
766             mRecentsView.moveRunningTaskToFront();
767         }
768         mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
769 
770         // Reapply window transform throughout the attach animation, as the animation affects how
771         // much the window is bound by overscroll (vs moving freely).
772         if (animate) {
773             ValueAnimator reapplyWindowTransformAnim = ValueAnimator.ofFloat(0, 1);
774             reapplyWindowTransformAnim.addUpdateListener(anim -> {
775                 if (mRunningWindowAnim == null || mRunningWindowAnim.length == 0) {
776                     applyScrollAndTransform();
777                 }
778             });
779             reapplyWindowTransformAnim.setDuration(RECENTS_ATTACH_DURATION).start();
780             mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED,
781                     reapplyWindowTransformAnim::cancel);
782         } else {
783             applyScrollAndTransform();
784         }
785     }
786 
787     /**
788      * Returns threshold that needs to be met in order for motion pause to be allowed.
789      */
790     public float getThresholdToAllowMotionPause() {
791         return mIsTransientTaskbar
792                 ? mTaskbarHomeOverviewThreshold
793                 : 0;
794     }
795 
796     public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
797         setIsLikelyToStartNewTask(isLikelyToStartNewTask, true /* animate */);
798     }
799 
800     private void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask, boolean animate) {
801         if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
802             mIsLikelyToStartNewTask = isLikelyToStartNewTask;
803             maybeUpdateRecentsAttachedState(animate);
804         }
805     }
806 
807     /**
808      * Update whether user is currently dragging in a region that will trigger all apps.
809      */
810     private void setIsInAllAppsRegion(boolean isInAllAppsRegion) {
811         if (mIsInAllAppsRegion == isInAllAppsRegion
812                 || !mActivityInterface.allowAllAppsFromOverview()) {
813             return;
814         }
815         mIsInAllAppsRegion = isInAllAppsRegion;
816 
817         // Newly entering or exiting the zone - do haptic and animate recent tasks.
818         VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
819         maybeUpdateRecentsAttachedState(true);
820 
821         if (mActivity != null) {
822             mActivity.getAppsView().getSearchUiManager().prepareToFocusEditText(mIsInAllAppsRegion);
823         }
824 
825         // Draw active task below Launcher so that All Apps can appear over it.
826         runActionOnRemoteHandles(remoteTargetHandle ->
827                 remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(isInAllAppsRegion));
828     }
829 
830 
831     private void buildAnimationController() {
832         if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
833             return;
834         }
835         initTransitionEndpoints(mActivity.getDeviceProfile());
836         mAnimationFactory.createActivityInterface(mTransitionDragLength);
837     }
838 
839     /**
840      * We don't want to change mLauncherTransitionController if mGestureState.getEndTarget() == HOME
841      * (it has its own animation) or if we explicitly ended the controller already.
842      * @return Whether we can create the launcher controller or update its progress.
843      */
844     private boolean canCreateNewOrUpdateExistingLauncherTransitionController() {
845         return mGestureState.getEndTarget() != HOME
846                 && !mHasEndedLauncherTransition && mActivity != null;
847     }
848 
849     @Override
850     public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
851         WindowInsets result = view.onApplyWindowInsets(windowInsets);
852         // Don't rebuild animation when we are animating the IME, because it will cause a loop
853         // where the insets change -> animation changes (updating ime) -> insets change -> ...
854         if (windowInsets.isVisible(WindowInsets.Type.ime())) {
855             return result;
856         }
857         buildAnimationController();
858         // Reapply the current shift to ensure it takes new insets into account, e.g. when long
859         // pressing to stash taskbar without moving the finger.
860         onCurrentShiftUpdated();
861         return result;
862     }
863 
864     private void onAnimatorPlaybackControllerCreated(AnimatorControllerWithResistance anim) {
865         boolean isFirstCreation = mLauncherTransitionController == null;
866         mLauncherTransitionController = anim;
867         if (isFirstCreation) {
868             mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, () -> {
869                 // Wait until the gesture is started (touch slop was passed) to start in sync with
870                 // mWindowTransitionController. This ensures we don't hide the taskbar background
871                 // when long pressing to stash it, for instance.
872                 mLauncherTransitionController.getNormalController().dispatchOnStart();
873                 updateLauncherTransitionProgress();
874             });
875         }
876     }
877 
878     public Intent getLaunchIntent() {
879         return mGestureState.getOverviewIntent();
880     }
881 
882     /**
883      * Called when the value of {@link #mCurrentShift} changes
884      */
885     @UiThread
886     @Override
887     public void onCurrentShiftUpdated() {
888         float threshold = LauncherPrefs.get(mContext).get(ALL_APPS_OVERVIEW_THRESHOLD) / 100f;
889         setIsInAllAppsRegion(mCurrentShift.value >= threshold);
890         updateSysUiFlags(mCurrentShift.value);
891         applyScrollAndTransform();
892 
893         updateLauncherTransitionProgress();
894     }
895 
896     private void updateLauncherTransitionProgress() {
897         if (mLauncherTransitionController == null
898                 || !canCreateNewOrUpdateExistingLauncherTransitionController()) {
899             return;
900         }
901         mLauncherTransitionController.setProgress(
902                 Math.max(mCurrentShift.value, getScaleProgressDueToScroll()), mDragLengthFactor);
903     }
904 
905     /**
906      * @param windowProgress 0 == app, 1 == overview
907      */
908     private void updateSysUiFlags(float windowProgress) {
909         if (mRecentsAnimationController != null && mRecentsView != null) {
910             TaskView runningTask = mRecentsView.getRunningTaskView();
911             TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen();
912             int centermostTaskFlags = centermostTask == null ? 0
913                     : centermostTask.getThumbnail().getSysUiStatusNavFlags();
914             boolean swipeUpThresholdPassed = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
915             boolean quickswitchThresholdPassed = centermostTask != runningTask;
916 
917             // We will handle the sysui flags based on the centermost task view.
918             mRecentsAnimationController.setUseLauncherSystemBarFlags(swipeUpThresholdPassed
919                     ||  (quickswitchThresholdPassed && centermostTaskFlags != 0));
920             mRecentsAnimationController.setSplitScreenMinimized(mContext, swipeUpThresholdPassed);
921             // Provide a hint to WM the direction that we will be settling in case the animation
922             // needs to be canceled
923             mRecentsAnimationController.setWillFinishToHome(swipeUpThresholdPassed);
924 
925             if (swipeUpThresholdPassed) {
926                 mActivity.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0);
927             } else {
928                 mActivity.getSystemUiController().updateUiState(
929                         UI_STATE_FULLSCREEN_TASK, centermostTaskFlags);
930             }
931         }
932     }
933 
934     @Override
935     public void onRecentsAnimationStart(RecentsAnimationController controller,
936             RecentsAnimationTargets targets) {
937         super.onRecentsAnimationStart(controller, targets);
938         if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && targets.hasDesktopTasks()) {
939             mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets);
940         } else {
941             int untrimmedAppCount = mRemoteTargetHandles.length;
942             mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(targets);
943             if (mRemoteTargetHandles.length < untrimmedAppCount && mIsSwipeForSplit) {
944                 updateIsGestureForSplit(mRemoteTargetHandles.length);
945                 setupRecentsViewUi();
946             }
947         }
948         mRecentsAnimationController = controller;
949         mRecentsAnimationTargets = targets;
950         mSwipePipToHomeReleaseCheck = new RemoteAnimationTargets.ReleaseCheck();
951         mSwipePipToHomeReleaseCheck.setCanRelease(true);
952         mRecentsAnimationTargets.addReleaseCheck(mSwipePipToHomeReleaseCheck);
953 
954         // Only initialize the device profile, if it has not been initialized before, as in some
955         // configurations targets.homeContentInsets may not be correct.
956         if (mActivity == null) {
957             RemoteAnimationTarget primaryTaskTarget = targets.apps[0];
958             // orientation state is independent of which remote target handle we use since both
959             // should be pointing to the same one. Just choose index 0 for now since that works for
960             // both split and non-split
961             RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
962                     .getOrientationState();
963             DeviceProfile dp = orientationState.getLauncherDeviceProfile();
964             if (targets.minimizedHomeBounds != null && primaryTaskTarget != null) {
965                 Rect overviewStackBounds = mActivityInterface
966                         .getOverviewWindowBounds(targets.minimizedHomeBounds, primaryTaskTarget);
967                 dp = dp.getMultiWindowProfile(mContext,
968                         new WindowBounds(overviewStackBounds, targets.homeContentInsets));
969             } else {
970                 // If we are not in multi-window mode, home insets should be same as system insets.
971                 dp = dp.copy(mContext);
972             }
973             dp.updateInsets(targets.homeContentInsets);
974             dp.updateIsSeascape(mContext);
975             initTransitionEndpoints(dp);
976             orientationState.setMultiWindowMode(dp.isMultiWindowMode);
977         }
978 
979         // Notify when the animation starts
980         flushOnRecentsAnimationAndLauncherBound();
981 
982         // Only add the callback to enable the input consumer after we actually have the controller
983         mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
984                 this::startInterceptingTouchesForGesture);
985         mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
986     }
987 
988     @Override
989     public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
990         ActiveGestureLog.INSTANCE.addLog(
991                 /* event= */ "cancelRecentsAnimation",
992                 /* gestureEvent= */ CANCEL_RECENTS_ANIMATION);
993         mActivityInitListener.unregister();
994         // Cache the recents animation controller so we can defer its cleanup to after having
995         // properly cleaned up the screenshot without accidentally using it.
996         mDeferredCleanupRecentsAnimationController = mRecentsAnimationController;
997         mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
998         // Defer clearing the controller and the targets until after we've updated the state
999         mRecentsAnimationController = null;
1000         mRecentsAnimationTargets = null;
1001         if (mRecentsView != null) {
1002             mRecentsView.setRecentsAnimationTargets(null, null);
1003         }
1004     }
1005 
1006     @UiThread
1007     public void onGestureStarted(boolean isLikelyToStartNewTask) {
1008         mActivityInterface.closeOverlay();
1009         TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
1010 
1011         if (mRecentsView != null) {
1012             final View rv = mRecentsView;
1013             mRecentsView.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
1014                 boolean mHandled = false;
1015 
1016                 @Override
1017                 public void onDraw() {
1018                     if (mHandled) {
1019                         return;
1020                     }
1021                     mHandled = true;
1022 
1023                     InteractionJankMonitorWrapper.begin(mRecentsView,
1024                             InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH, 2000 /* ms timeout */);
1025                     InteractionJankMonitorWrapper.begin(mRecentsView,
1026                             InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
1027                     InteractionJankMonitorWrapper.begin(mRecentsView,
1028                             InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS);
1029 
1030                     rv.post(() -> rv.getViewTreeObserver().removeOnDrawListener(this));
1031                 }
1032             });
1033         }
1034         notifyGestureStarted();
1035         setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
1036 
1037         if (mIsTransientTaskbar && !mTaskbarAlreadyOpen && !isLikelyToStartNewTask) {
1038             setClampScrollOffset(true);
1039         }
1040         mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
1041         mGestureStarted = true;
1042     }
1043 
1044     /**
1045      * Sets whether or not we should clamp the scroll offset.
1046      * This is used to avoid x-axis movement when swiping up transient taskbar.
1047      * @param clampScrollOffset When true, we clamp the scroll to 0 before the clamp threshold is
1048      *                          met.
1049      */
1050     private void setClampScrollOffset(boolean clampScrollOffset) {
1051         if (!mIsTransientTaskbar) {
1052             return;
1053         }
1054         if (mRecentsView == null) {
1055             mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT,
1056                     () -> mRecentsView.setClampScrollOffset(clampScrollOffset));
1057             return;
1058         }
1059         mRecentsView.setClampScrollOffset(clampScrollOffset);
1060     }
1061 
1062 
1063     /**
1064      * Notifies the launcher that the swipe gesture has started. This can be called multiple times.
1065      */
1066     @UiThread
1067     private void notifyGestureStarted() {
1068         final T curActivity = mActivity;
1069         if (curActivity != null) {
1070             // Once the gesture starts, we can no longer transition home through the button, so
1071             // reset the force override of the activity visibility
1072             mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
1073         }
1074     }
1075 
1076     /**
1077      * Called as a result on ACTION_CANCEL to return the UI to the start state.
1078      */
1079     @UiThread
1080     public void onGestureCancelled() {
1081         updateDisplacement(0);
1082         mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
1083         handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */);
1084     }
1085 
1086     /**
1087      * @param endVelocityPxPerMs The velocity in the direction of the nav bar to the middle of the
1088      *                           screen.
1089      * @param velocityPxPerMs The x and y components of the velocity when the gesture ends.
1090      */
1091     @UiThread
1092     public void onGestureEnded(float endVelocityPxPerMs, PointF velocityPxPerMs) {
1093         float flingThreshold = mContext.getResources()
1094                 .getDimension(R.dimen.quickstep_fling_threshold_speed);
1095         boolean isFling = mGestureStarted && !mIsMotionPaused
1096                 && Math.abs(endVelocityPxPerMs) > flingThreshold;
1097         mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
1098         boolean isVelocityVertical = Math.abs(velocityPxPerMs.y) > Math.abs(velocityPxPerMs.x);
1099         if (isVelocityVertical) {
1100             mLogDirectionUpOrLeft = velocityPxPerMs.y < 0;
1101         } else {
1102             mLogDirectionUpOrLeft = velocityPxPerMs.x < 0;
1103         }
1104         Runnable handleNormalGestureEndCallback = () -> handleNormalGestureEnd(
1105                 endVelocityPxPerMs, isFling, velocityPxPerMs, /* isCancel= */ false);
1106         if (mRecentsView != null) {
1107             mRecentsView.runOnPageScrollsInitialized(handleNormalGestureEndCallback);
1108         } else {
1109             handleNormalGestureEndCallback.run();
1110         }
1111     }
1112 
1113     private void endRunningWindowAnim(boolean cancel) {
1114         if (mRunningWindowAnim != null) {
1115             if (cancel) {
1116                 for (RunningWindowAnim r : mRunningWindowAnim) {
1117                     if (r != null) {
1118                         r.cancel();
1119                     }
1120                 }
1121             } else {
1122                 for (RunningWindowAnim r : mRunningWindowAnim) {
1123                     if (r != null) {
1124                         r.end();
1125                     }
1126                 }
1127             }
1128         }
1129         if (mParallelRunningAnim != null) {
1130             // Unlike the above animation, the parallel animation won't have anything to take up
1131             // the work if it's canceled, so just end it instead.
1132             mParallelRunningAnim.end();
1133         }
1134     }
1135 
1136     private void onSettledOnEndTarget() {
1137         // Fast-finish the attaching animation if it's still running.
1138         maybeUpdateRecentsAttachedState(false);
1139         final GestureEndTarget endTarget = mGestureState.getEndTarget();
1140         // Wait until the given View (if supplied) draws before resuming the last task.
1141         View postResumeLastTask = mActivityInterface.onSettledOnEndTarget(endTarget);
1142 
1143         if (endTarget != NEW_TASK) {
1144             InteractionJankMonitorWrapper.cancel(
1145                     InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
1146         }
1147         if (endTarget != HOME) {
1148             InteractionJankMonitorWrapper.cancel(
1149                     InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
1150         }
1151         if (endTarget != RECENTS) {
1152             InteractionJankMonitorWrapper.cancel(
1153                     InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS);
1154         }
1155 
1156         switch (endTarget) {
1157             case ALL_APPS:
1158                 mStateCallback.setState(STATE_SETTLED_ON_ALL_APPS | STATE_CAPTURE_SCREENSHOT);
1159                 break;
1160             case HOME:
1161                 mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
1162                 // Notify the SysUI to use fade-in animation when entering PiP
1163                 SystemUiProxy.INSTANCE.get(mContext).setPipAnimationTypeToAlpha();
1164                 if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
1165                     // Notify the SysUI to stash desktop apps if they are visible
1166                     DesktopVisibilityController desktopVisibilityController =
1167                             mActivityInterface.getDesktopVisibilityController();
1168                     if (desktopVisibilityController != null) {
1169                         desktopVisibilityController.onHomeActionTriggered();
1170                     }
1171                 }
1172                 break;
1173             case RECENTS:
1174                 mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
1175                         | STATE_SCREENSHOT_VIEW_SHOWN);
1176                 break;
1177             case NEW_TASK:
1178                 mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT);
1179                 break;
1180             case LAST_TASK:
1181                 if (postResumeLastTask != null) {
1182                     ViewUtils.postFrameDrawn(postResumeLastTask,
1183                             () -> mStateCallback.setState(STATE_RESUME_LAST_TASK));
1184                 } else {
1185                     mStateCallback.setState(STATE_RESUME_LAST_TASK);
1186                 }
1187                 // Restore the divider as it resumes the last top-tasks.
1188                 setDividerShown(true);
1189                 break;
1190         }
1191         ActiveGestureLog.INSTANCE.addLog(
1192                 /* event= */ "onSettledOnEndTarget " + endTarget,
1193                 /* gestureEvent= */ ON_SETTLED_ON_END_TARGET);
1194     }
1195 
1196     /** @return Whether this was the task we were waiting to appear, and thus handled it. */
1197     protected boolean handleTaskAppeared(RemoteAnimationTarget[] appearedTaskTarget) {
1198         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
1199             return false;
1200         }
1201         boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTarget).anyMatch(
1202                 mGestureState.mLastStartedTaskIdPredicate);
1203         if (mStateCallback.hasStates(STATE_START_NEW_TASK) && hasStartedTaskBefore) {
1204             reset();
1205             return true;
1206         }
1207         return false;
1208     }
1209 
1210     private float dpiFromPx(float pixels) {
1211         return Utilities.dpiFromPx(pixels, mContext.getResources().getDisplayMetrics().densityDpi);
1212     }
1213 
1214     private GestureEndTarget calculateEndTarget(
1215             PointF velocityPxPerMs, float endVelocityPxPerMs, boolean isFlingY, boolean isCancel) {
1216         ActiveGestureLog.INSTANCE.addLog(
1217                 new ActiveGestureLog.CompoundString("calculateEndTarget: velocities=(x=")
1218                         .append(Float.toString(dpiFromPx(velocityPxPerMs.x)))
1219                         .append("dp/ms, y=")
1220                         .append(Float.toString(dpiFromPx(velocityPxPerMs.y)))
1221                         .append("dp/ms), angle=")
1222                         .append(Double.toString(Math.toDegrees(Math.atan2(
1223                                 -velocityPxPerMs.y, velocityPxPerMs.x)))));
1224 
1225         if (mGestureState.isHandlingAtomicEvent()) {
1226             // Button mode, this is only used to go to recents.
1227             return RECENTS;
1228         }
1229 
1230         GestureEndTarget endTarget;
1231         if (isCancel) {
1232             endTarget = LAST_TASK;
1233         } else if (isFlingY) {
1234             endTarget = calculateEndTargetForFlingY(velocityPxPerMs, endVelocityPxPerMs);
1235         } else {
1236             endTarget = calculateEndTargetForNonFling(velocityPxPerMs);
1237         }
1238 
1239         if (mDeviceState.isOverviewDisabled() && endTarget == RECENTS) {
1240             return LAST_TASK;
1241         }
1242 
1243         if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && endTarget == NEW_TASK) {
1244             // TODO(b/268075592): add support for quickswitch to/from desktop
1245             return LAST_TASK;
1246         }
1247 
1248         return endTarget;
1249     }
1250 
1251     private GestureEndTarget calculateEndTargetForFlingY(PointF velocity, float endVelocity) {
1252         // If swiping at a diagonal, base end target on the faster velocity direction.
1253         final boolean willGoToNewTask =
1254                 isScrollingToNewTask() && Math.abs(velocity.x) > Math.abs(endVelocity);
1255         final boolean isSwipeUp = endVelocity < 0;
1256         if (mIsInAllAppsRegion) {
1257             return isSwipeUp ? ALL_APPS : LAST_TASK;
1258         }
1259         if (!isSwipeUp) {
1260             final boolean isCenteredOnNewTask =
1261                     mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex();
1262             return willGoToNewTask || isCenteredOnNewTask ? NEW_TASK : LAST_TASK;
1263         }
1264 
1265         return willGoToNewTask ? NEW_TASK : HOME;
1266     }
1267 
1268     private GestureEndTarget calculateEndTargetForNonFling(PointF velocity) {
1269         final boolean isScrollingToNewTask = isScrollingToNewTask();
1270 
1271         // Fully gestural mode.
1272         final boolean isFlingX = Math.abs(velocity.x) > mContext.getResources()
1273                 .getDimension(R.dimen.quickstep_fling_threshold_speed);
1274         if (mIsInAllAppsRegion) {
1275             return ALL_APPS;
1276         } else if (isScrollingToNewTask && isFlingX) {
1277             // Flinging towards new task takes precedence over mIsMotionPaused (which only
1278             // checks y-velocity).
1279             return NEW_TASK;
1280         } else if (mIsMotionPaused) {
1281             return RECENTS;
1282         } else if (isScrollingToNewTask) {
1283             return NEW_TASK;
1284         }
1285         return velocity.y < 0 && mCanSlowSwipeGoHome ? HOME : LAST_TASK;
1286     }
1287 
1288     private boolean isScrollingToNewTask() {
1289         if (mRecentsView == null) {
1290             return false;
1291         }
1292         if (!hasTargets()) {
1293             // If there are no running tasks, then we can assume that this is a continuation of
1294             // the last gesture, but after the recents animation has finished.
1295             return true;
1296         }
1297         int runningTaskIndex = mRecentsView.getRunningTaskIndex();
1298         return runningTaskIndex >= 0 && mRecentsView.getNextPage() != runningTaskIndex;
1299     }
1300 
1301     /**
1302      * Sets whether a slow swipe can go to the HOME end target when the user lets go. A slow swipe
1303      * for this purpose must meet two criteria:
1304      *   1) y-velocity is less than quickstep_fling_threshold_speed
1305      *   AND
1306      *   2) motion pause has not been detected (possibly because
1307      *   {@link MotionPauseDetector#setDisallowPause} has been called with disallowPause == true)
1308      */
1309     public void setCanSlowSwipeGoHome(boolean canSlowSwipeGoHome) {
1310         mCanSlowSwipeGoHome = canSlowSwipeGoHome;
1311     }
1312 
1313     @UiThread
1314     private void handleNormalGestureEnd(
1315             float endVelocityPxPerMs, boolean isFling, PointF velocityPxPerMs, boolean isCancel) {
1316         long duration = MAX_SWIPE_DURATION;
1317         float currentShift = mCurrentShift.value;
1318         final GestureEndTarget endTarget = calculateEndTarget(
1319                 velocityPxPerMs, endVelocityPxPerMs, isFling, isCancel);
1320         // Set the state, but don't notify until the animation completes
1321         mGestureState.setEndTarget(endTarget, false /* isAtomic */);
1322         mAnimationFactory.setEndTarget(endTarget);
1323 
1324         float endShift = endTarget == ALL_APPS ? mDragLengthFactor
1325                 : endTarget.isLauncher ? 1 : 0;
1326         final float startShift;
1327         if (!isFling) {
1328             long expectedDuration = Math.abs(Math.round((endShift - currentShift)
1329                     * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
1330             duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
1331             startShift = currentShift;
1332         } else {
1333             startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
1334                     * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
1335             if (mTransitionDragLength > 0) {
1336                 float distanceToTravel = (endShift - currentShift) * mTransitionDragLength;
1337 
1338                 // we want the page's snap velocity to approximately match the velocity at
1339                 // which the user flings, so we scale the duration by a value near to the
1340                 // derivative of the scroll interpolator at zero, ie. 2.
1341                 long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
1342                 duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
1343             }
1344         }
1345         Interpolator interpolator;
1346         S state = mActivityInterface.stateFromGestureEndTarget(endTarget);
1347         if (state.displayOverviewTasksAsGrid(mDp)) {
1348             interpolator = ACCELERATE_DECELERATE;
1349         } else if (endTarget == RECENTS) {
1350             interpolator = OVERSHOOT_1_2;
1351         } else {
1352             interpolator = DECELERATE;
1353         }
1354 
1355         if (endTarget.isLauncher) {
1356             mInputConsumerProxy.enable();
1357         }
1358         if (endTarget == HOME) {
1359             duration = mActivity != null && mActivity.getDeviceProfile().isTaskbarPresent
1360                     ? StaggeredWorkspaceAnim.DURATION_TASKBAR_MS
1361                     : StaggeredWorkspaceAnim.DURATION_MS;
1362             // Early detach the nav bar once the endTarget is determined as HOME
1363             if (mRecentsAnimationController != null) {
1364                 mRecentsAnimationController.detachNavigationBarFromApp(true);
1365             }
1366         } else if (endTarget == RECENTS) {
1367             if (mRecentsView != null) {
1368                 int nearestPage = mRecentsView.getDestinationPage();
1369                 if (nearestPage == INVALID_PAGE) {
1370                     // Allow the snap to invalid page to catch future error cases.
1371                     Log.e(TAG,
1372                             "RecentsView destination page is invalid",
1373                             new IllegalStateException());
1374                 }
1375 
1376                 boolean isScrolling = false;
1377                 if (mRecentsView.getNextPage() != nearestPage) {
1378                     // We shouldn't really scroll to the next page when swiping up to recents.
1379                     // Only allow settling on the next page if it's nearest to the center.
1380                     mRecentsView.snapToPage(nearestPage, Math.toIntExact(duration));
1381                     isScrolling = true;
1382                 }
1383                 if (mRecentsView.getScroller().getDuration() > MAX_SWIPE_DURATION) {
1384                     mRecentsView.snapToPage(mRecentsView.getNextPage(), (int) MAX_SWIPE_DURATION);
1385                     isScrolling = true;
1386                 }
1387                 if (!mGestureState.isHandlingAtomicEvent() || isScrolling) {
1388                     duration = Math.max(duration, mRecentsView.getScroller().getDuration());
1389                 }
1390             }
1391         } else if (endTarget == LAST_TASK && mRecentsView != null
1392                 && mRecentsView.getNextPage() != mRecentsView.getRunningTaskIndex()) {
1393             mRecentsView.snapToPage(mRecentsView.getRunningTaskIndex(), Math.toIntExact(duration));
1394         }
1395 
1396         // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
1397         // or resumeLastTask().
1398         Runnable onPageTransitionEnd = () -> {
1399             mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
1400             setClampScrollOffset(false);
1401         };
1402         if (mRecentsView != null) {
1403             ActiveGestureLog.INSTANCE.trackEvent(ActiveGestureErrorDetector.GestureEvent
1404                     .SET_ON_PAGE_TRANSITION_END_CALLBACK);
1405             mRecentsView.setOnPageTransitionEndCallback(onPageTransitionEnd);
1406         } else {
1407             onPageTransitionEnd.run();
1408         }
1409 
1410         animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
1411     }
1412 
1413     private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTask) {
1414         if (mDp == null || !mDp.isGestureMode) {
1415             // We probably never received an animation controller, skip logging.
1416             return;
1417         }
1418 
1419         StatsLogManager.EventEnum event;
1420         switch (endTarget) {
1421             case HOME:
1422                 event = LAUNCHER_HOME_GESTURE;
1423                 break;
1424             case RECENTS:
1425                 event = LAUNCHER_OVERVIEW_GESTURE;
1426                 break;
1427             case LAST_TASK:
1428             case NEW_TASK:
1429                 event = mLogDirectionUpOrLeft ? LAUNCHER_QUICKSWITCH_LEFT
1430                         : LAUNCHER_QUICKSWITCH_RIGHT;
1431                 break;
1432             default:
1433                 event = IGNORE;
1434         }
1435         StatsLogger logger = StatsLogManager.newInstance(mContext).logger()
1436                 .withSrcState(LAUNCHER_STATE_BACKGROUND)
1437                 .withDstState(endTarget.containerType)
1438                 .withInputType(mGestureState.isTrackpadGesture()
1439                         ? SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TRACKPAD
1440                         : SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TOUCH);
1441         if (targetTask != null) {
1442             logger.withItemInfo(targetTask.getItemInfo());
1443         }
1444 
1445         int pageIndex = endTarget == LAST_TASK || mRecentsView == null
1446                 ? LOG_NO_OP_PAGE_INDEX
1447                 : mRecentsView.getNextPage();
1448         logger.withRank(pageIndex);
1449         logger.log(event);
1450     }
1451 
1452     /** Animates to the given progress, where 0 is the current app and 1 is overview. */
1453     @UiThread
1454     private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
1455             GestureEndTarget target, PointF velocityPxPerMs) {
1456         runOnRecentsAnimationAndLauncherBound(() -> animateToProgressInternal(start, end, duration,
1457                 interpolator, target, velocityPxPerMs));
1458     }
1459 
1460     protected abstract HomeAnimationFactory createHomeAnimationFactory(
1461             ArrayList<IBinder> launchCookies, long duration, boolean isTargetTranslucent,
1462             boolean appCanEnterPip, RemoteAnimationTarget runningTaskTarget);
1463 
1464     private final TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
1465         @Override
1466         public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
1467                 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
1468             boolean taskRunningAndNotHome = Arrays.stream(mGestureState
1469                             .getRunningTaskIds(true /*getMultipleTasks*/))
1470                     .anyMatch(taskId -> task.taskId == taskId
1471                             && task.configuration.windowConfiguration.getActivityType()
1472                             != ACTIVITY_TYPE_HOME);
1473             if (taskRunningAndNotHome) {
1474                 // Since this is an edge case, just cancel and relaunch with default activity
1475                 // options (since we don't know if there's an associated app icon to launch from)
1476                 endRunningWindowAnim(true /* cancel */);
1477                 TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
1478                         mActivityRestartListener);
1479                 ActivityManagerWrapper.getInstance().startActivityFromRecents(task.taskId, null);
1480             }
1481         }
1482     };
1483 
1484     @UiThread
1485     private void animateToProgressInternal(float start, float end, long duration,
1486             Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
1487         maybeUpdateRecentsAttachedState();
1488 
1489         // If we are transitioning to launcher, then listen for the activity to be restarted while
1490         // the transition is in progress
1491         if (mGestureState.getEndTarget().isLauncher) {
1492             // This is also called when the launcher is resumed, in order to clear the pending
1493             // widgets that have yet to be configured.
1494             DragView.removeAllViews(mActivity);
1495 
1496             TaskStackChangeListeners.getInstance().registerTaskStackListener(
1497                     mActivityRestartListener);
1498 
1499             mParallelRunningAnim = mActivityInterface.getParallelAnimationToLauncher(
1500                     mGestureState.getEndTarget(), duration,
1501                     mTaskAnimationManager.getCurrentCallbacks());
1502             if (mParallelRunningAnim != null) {
1503                 mParallelRunningAnim.addListener(new AnimatorListenerAdapter() {
1504                     @Override
1505                     public void onAnimationEnd(Animator animation) {
1506                         mParallelRunningAnim = null;
1507                     }
1508                 });
1509                 mParallelRunningAnim.start();
1510             }
1511         }
1512 
1513         if (mGestureState.getEndTarget() == HOME) {
1514             getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
1515             // Take first task ID, if there are multiple we don't have any special home
1516             // animation so doesn't matter for splitscreen.. though the "allowEnterPip" might change
1517             // depending on which task it is..
1518             final RemoteAnimationTarget runningTaskTarget = mRecentsAnimationTargets != null
1519                     ? mRecentsAnimationTargets
1520                     .findTask(mGestureState.getTopRunningTaskId())
1521                     : null;
1522             final ArrayList<IBinder> cookies = runningTaskTarget != null
1523                     ? runningTaskTarget.taskInfo.launchCookies
1524                     : new ArrayList<>();
1525             boolean isTranslucent = runningTaskTarget != null && runningTaskTarget.isTranslucent;
1526             boolean hasValidLeash = runningTaskTarget != null
1527                     && runningTaskTarget.leash != null
1528                     && runningTaskTarget.leash.isValid();
1529             boolean appCanEnterPip = !mDeviceState.isPipActive()
1530                     && hasValidLeash
1531                     && runningTaskTarget.allowEnterPip
1532                     && runningTaskTarget.taskInfo.pictureInPictureParams != null
1533                     && runningTaskTarget.taskInfo.pictureInPictureParams.isAutoEnterEnabled();
1534             HomeAnimationFactory homeAnimFactory =
1535                     createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
1536                             runningTaskTarget);
1537             mIsSwipingPipToHome = !mIsSwipeForSplit && appCanEnterPip;
1538             final RectFSpringAnim[] windowAnim;
1539             if (mIsSwipingPipToHome) {
1540                 mSwipePipToHomeAnimator = createWindowAnimationToPip(
1541                         homeAnimFactory, runningTaskTarget, start);
1542                 mSwipePipToHomeAnimators[0] = mSwipePipToHomeAnimator;
1543                 if (mSwipePipToHomeReleaseCheck != null) {
1544                     mSwipePipToHomeReleaseCheck.setCanRelease(false);
1545                 }
1546 
1547                 // grab a screenshot before the PipContentOverlay gets parented on top of the task
1548                 UI_HELPER_EXECUTOR.execute(() -> {
1549                     // Directly use top task, split to pip handled on shell side
1550                     final int taskId = mGestureState.getTopRunningTaskId();
1551                     mTaskSnapshotCache.put(taskId,
1552                             mRecentsAnimationController.screenshotTask(taskId));
1553                 });
1554 
1555                 // let SystemUi reparent the overlay leash as soon as possible
1556                 SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
1557                         mSwipePipToHomeAnimator.getTaskId(),
1558                         mSwipePipToHomeAnimator.getComponentName(),
1559                         mSwipePipToHomeAnimator.getDestinationBounds(),
1560                         mSwipePipToHomeAnimator.getContentOverlay());
1561 
1562                 windowAnim = mSwipePipToHomeAnimators;
1563             } else {
1564                 mSwipePipToHomeAnimator = null;
1565                 if (mSwipePipToHomeReleaseCheck != null) {
1566                     mSwipePipToHomeReleaseCheck.setCanRelease(true);
1567                     mSwipePipToHomeReleaseCheck = null;
1568                 }
1569                 windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
1570 
1571                 windowAnim[0].addAnimatorListener(new AnimationSuccessListener() {
1572                     @Override
1573                     public void onAnimationSuccess(Animator animator) {
1574                         if (mRecentsAnimationController == null) {
1575                             // If the recents animation is interrupted, we still end the running
1576                             // animation (not canceled) so this is still called. In that case,
1577                             // we can skip doing any future work here for the current gesture.
1578                             return;
1579                         }
1580                         // Finalize the state and notify of the change
1581                         mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
1582                     }
1583                 });
1584             }
1585             mRunningWindowAnim = new RunningWindowAnim[windowAnim.length];
1586             for (int i = 0, windowAnimLength = windowAnim.length; i < windowAnimLength; i++) {
1587                 RectFSpringAnim windowAnimation = windowAnim[i];
1588                 if (windowAnimation == null) {
1589                     continue;
1590                 }
1591                 DeviceProfile dp = mActivity == null ? null : mActivity.getDeviceProfile();
1592                 windowAnimation.start(mContext, dp, velocityPxPerMs);
1593                 mRunningWindowAnim[i] = RunningWindowAnim.wrap(windowAnimation);
1594             }
1595             homeAnimFactory.setSwipeVelocity(velocityPxPerMs.y);
1596             homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
1597             mLauncherTransitionController = null;
1598 
1599             if (mRecentsView != null) {
1600                 mRecentsView.onPrepareGestureEndAnimation(null, mGestureState.getEndTarget(),
1601                         getRemoteTaskViewSimulators());
1602             }
1603         } else {
1604             AnimatorSet animatorSet = new AnimatorSet();
1605             ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);
1606             windowAnim.addUpdateListener(valueAnimator -> {
1607                 computeRecentsScrollIfInvisible();
1608             });
1609             windowAnim.addListener(new AnimationSuccessListener() {
1610                 @Override
1611                 public void onAnimationSuccess(Animator animator) {
1612                     if (mRecentsAnimationController == null) {
1613                         // If the recents animation is interrupted, we still end the running
1614                         // animation (not canceled) so this is still called. In that case, we can
1615                         // skip doing any future work here for the current gesture.
1616                         return;
1617                     }
1618                     if (mRecentsView != null) {
1619                         int taskToLaunch = mRecentsView.getNextPage();
1620                         int runningTask = getLastAppearedTaskIndex();
1621                         boolean hasStartedNewTask = hasStartedNewTask();
1622                         if (target == NEW_TASK && taskToLaunch == runningTask
1623                                 && !hasStartedNewTask) {
1624                             // We are about to launch the current running task, so use LAST_TASK
1625                             // state instead of NEW_TASK. This could happen, for example, if our
1626                             // scroll is aborted after we determined the target to be NEW_TASK.
1627                             mGestureState.setEndTarget(LAST_TASK);
1628                         } else if (target == LAST_TASK && hasStartedNewTask) {
1629                             // We are about to re-launch the previously running task, but we can't
1630                             // just finish the controller like we normally would because that would
1631                             // instead resume the last task that appeared, and not ensure that this
1632                             // task is restored to the top. To address this, re-launch the task as
1633                             // if it were a new task.
1634                             mGestureState.setEndTarget(NEW_TASK);
1635                         }
1636                     }
1637                     mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
1638                 }
1639             });
1640             animatorSet.play(windowAnim);
1641             if (mRecentsView != null) {
1642                 mRecentsView.onPrepareGestureEndAnimation(
1643                         animatorSet, mGestureState.getEndTarget(),
1644                         getRemoteTaskViewSimulators());
1645             }
1646             animatorSet.setDuration(duration).setInterpolator(interpolator);
1647             animatorSet.start();
1648             mRunningWindowAnim = new RunningWindowAnim[]{RunningWindowAnim.wrap(animatorSet)};
1649         }
1650     }
1651 
1652     private int calculateWindowRotation(RemoteAnimationTarget runningTaskTarget,
1653             RecentsOrientedState orientationState) {
1654         if (runningTaskTarget.rotationChange != 0
1655                 && TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
1656             return Math.abs(runningTaskTarget.rotationChange) == ROTATION_90
1657                     ? ROTATION_270 : ROTATION_90;
1658         } else {
1659             return orientationState.getDisplayRotation();
1660         }
1661     }
1662 
1663     @Nullable
1664     private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
1665             RemoteAnimationTarget runningTaskTarget, float startProgress) {
1666         // Directly animate the app to PiP (picture-in-picture) mode
1667         final ActivityManager.RunningTaskInfo taskInfo = runningTaskTarget.taskInfo;
1668         final RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
1669                 .getOrientationState();
1670         final int windowRotation = calculateWindowRotation(runningTaskTarget, orientationState);
1671         final int homeRotation = orientationState.getRecentsActivityRotation();
1672 
1673         final Matrix[] homeToWindowPositionMaps = new Matrix[mRemoteTargetHandles.length];
1674         final RectF startRect = updateProgressForStartRect(homeToWindowPositionMaps,
1675                 startProgress)[0];
1676         final Matrix homeToWindowPositionMap = homeToWindowPositionMaps[0];
1677         // Move the startRect to Launcher space as floatingIconView runs in Launcher
1678         final Matrix windowToHomePositionMap = new Matrix();
1679         homeToWindowPositionMap.invert(windowToHomePositionMap);
1680         windowToHomePositionMap.mapRect(startRect);
1681 
1682         final Rect hotseatKeepClearArea = getKeepClearAreaForHotseat();
1683         final Rect destinationBounds = SystemUiProxy.INSTANCE.get(mContext)
1684                 .startSwipePipToHome(taskInfo.topActivity,
1685                         taskInfo.topActivityInfo,
1686                         runningTaskTarget.taskInfo.pictureInPictureParams,
1687                         homeRotation,
1688                         hotseatKeepClearArea);
1689         if (destinationBounds == null) {
1690             // No destination bounds returned from SystemUI, bail early.
1691             return null;
1692         }
1693         final Rect appBounds = new Rect();
1694         final WindowConfiguration winConfig = taskInfo.configuration.windowConfiguration;
1695         // Adjust the appBounds for TaskBar by using the calculated window crop Rect
1696         // from TaskViewSimulator and fallback to the bounds in TaskInfo when it's originated
1697         // from windowing modes other than full-screen.
1698         if (winConfig.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FULLSCREEN) {
1699             mRemoteTargetHandles[0].getTaskViewSimulator().getCurrentCropRect().round(appBounds);
1700         } else {
1701             appBounds.set(winConfig.getBounds());
1702         }
1703         final SwipePipToHomeAnimator.Builder builder = new SwipePipToHomeAnimator.Builder()
1704                 .setContext(mContext)
1705                 .setTaskId(runningTaskTarget.taskId)
1706                 .setActivityInfo(taskInfo.topActivityInfo)
1707                 .setAppIconSizePx(mDp.iconSizePx)
1708                 .setLeash(runningTaskTarget.leash)
1709                 .setSourceRectHint(
1710                         runningTaskTarget.taskInfo.pictureInPictureParams.getSourceRectHint())
1711                 .setAppBounds(appBounds)
1712                 .setHomeToWindowPositionMap(homeToWindowPositionMap)
1713                 .setStartBounds(startRect)
1714                 .setDestinationBounds(destinationBounds)
1715                 .setCornerRadius(mRecentsView.getPipCornerRadius())
1716                 .setShadowRadius(mRecentsView.getPipShadowRadius())
1717                 .setAttachedView(mRecentsView);
1718         // We would assume home and app window always in the same rotation While homeRotation
1719         // is not ROTATION_0 (which implies the rotation is turned on in launcher settings).
1720         if (homeRotation == ROTATION_0
1721                 && (windowRotation == ROTATION_90 || windowRotation == ROTATION_270)) {
1722             builder.setFromRotation(mRemoteTargetHandles[0].getTaskViewSimulator(), windowRotation,
1723                     taskInfo.displayCutoutInsets);
1724         }
1725         final SwipePipToHomeAnimator swipePipToHomeAnimator = builder.build();
1726         AnimatorPlaybackController activityAnimationToHome =
1727                 homeAnimFactory.createActivityAnimationToHome();
1728         swipePipToHomeAnimator.addAnimatorListener(new AnimatorListenerAdapter() {
1729             private boolean mHasAnimationEnded;
1730             @Override
1731             public void onAnimationStart(Animator animation) {
1732                 if (mHasAnimationEnded) return;
1733                 // Ensure Launcher ends in NORMAL state
1734                 activityAnimationToHome.dispatchOnStart();
1735             }
1736 
1737             @Override
1738             public void onAnimationEnd(Animator animation) {
1739                 if (mHasAnimationEnded) return;
1740                 mHasAnimationEnded = true;
1741                 activityAnimationToHome.getAnimationPlayer().end();
1742                 if (mRecentsAnimationController == null) {
1743                     // If the recents animation is interrupted, we still end the running
1744                     // animation (not canceled) so this is still called. In that case, we can
1745                     // skip doing any future work here for the current gesture.
1746                     return;
1747                 }
1748                 // Finalize the state and notify of the change
1749                 mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
1750             }
1751         });
1752         setupWindowAnimation(new RectFSpringAnim[]{swipePipToHomeAnimator});
1753         return swipePipToHomeAnimator;
1754     }
1755 
1756     private Rect getKeepClearAreaForHotseat() {
1757         Rect keepClearArea;
1758         // the keep clear area in global screen coordinates, in pixels
1759         if (mDp.isPhone) {
1760             if (mDp.isSeascape()) {
1761                 // in seascape the Hotseat is on the left edge of the screen
1762                 keepClearArea = new Rect(0, 0, mDp.hotseatBarSizePx, mDp.heightPx);
1763             } else if (mDp.isLandscape) {
1764                 // in landscape the Hotseat is on the right edge of the screen
1765                 keepClearArea = new Rect(mDp.widthPx - mDp.hotseatBarSizePx, 0,
1766                         mDp.widthPx, mDp.heightPx);
1767             } else {
1768                 // in portrait mode the Hotseat is at the bottom of the screen
1769                 keepClearArea = new Rect(0, mDp.heightPx - mDp.hotseatBarSizePx,
1770                         mDp.widthPx, mDp.heightPx);
1771             }
1772         } else {
1773             // large screens have Hotseat always at the bottom of the screen
1774             keepClearArea = new Rect(0, mDp.heightPx - mDp.hotseatBarSizePx,
1775                     mDp.widthPx, mDp.heightPx);
1776         }
1777         return keepClearArea;
1778     }
1779 
1780     /**
1781      * Notifies to start intercepting touches in the app window and hide the divider bar if needed.
1782      * @see RecentsAnimationController#enableInputConsumer()
1783      */
1784     private void startInterceptingTouchesForGesture() {
1785         if (mRecentsAnimationController == null || !mStartMovingTasks) {
1786             return;
1787         }
1788 
1789         mRecentsAnimationController.enableInputConsumer();
1790 
1791         // Hide the divider as it starts intercepting touches in the app window.
1792         setDividerShown(false);
1793     }
1794 
1795     private void computeRecentsScrollIfInvisible() {
1796         if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
1797             // Views typically don't compute scroll when invisible as an optimization,
1798             // but in our case we need to since the window offset depends on the scroll.
1799             mRecentsView.computeScroll();
1800         }
1801     }
1802 
1803     private void continueComputingRecentsScrollIfNecessary() {
1804         if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)
1805                 && !mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)
1806                 && !mCanceled) {
1807             computeRecentsScrollIfInvisible();
1808             mRecentsView.postOnAnimation(this::continueComputingRecentsScrollIfNecessary);
1809         }
1810     }
1811 
1812     /**
1813      * Creates an animation that transforms the current app window into the home app.
1814      * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
1815      * @param homeAnimationFactory The home animation factory.
1816      */
1817     @Override
1818     protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress,
1819             HomeAnimationFactory homeAnimationFactory) {
1820         RectFSpringAnim[] anim =
1821                 super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
1822         setupWindowAnimation(anim);
1823         return anim;
1824     }
1825 
1826     private void setupWindowAnimation(RectFSpringAnim[] anims) {
1827         anims[0].addOnUpdateListener((r, p) -> {
1828             updateSysUiFlags(Math.max(p, mCurrentShift.value));
1829         });
1830         anims[0].addAnimatorListener(new AnimationSuccessListener() {
1831             @Override
1832             public void onAnimationSuccess(Animator animator) {
1833                 if (mRecentsView != null) {
1834                     mRecentsView.post(mRecentsView::resetTaskVisuals);
1835                 }
1836                 // Make sure recents is in its final state
1837                 maybeUpdateRecentsAttachedState(false);
1838                 mActivityInterface.onSwipeUpToHomeComplete(mDeviceState);
1839             }
1840         });
1841         if (mRecentsAnimationTargets != null) {
1842             mRecentsAnimationTargets.addReleaseCheck(anims[0]);
1843         }
1844     }
1845 
1846     public void onConsumerAboutToBeSwitched() {
1847         if (mActivity != null) {
1848             // In the off chance that the gesture ends before Launcher is started, we should clear
1849             // the callback here so that it doesn't update with the wrong state
1850             mActivity.clearRunOnceOnStartCallback();
1851             resetLauncherListeners();
1852         }
1853         if (mGestureState.isRecentsAnimationRunning() && mGestureState.getEndTarget() != null
1854                 && !mGestureState.getEndTarget().isLauncher) {
1855             // Continued quick switch.
1856             cancelCurrentAnimation();
1857         } else {
1858             mStateCallback.setStateOnUiThread(STATE_FINISH_WITH_NO_END);
1859             reset();
1860         }
1861     }
1862 
1863     public boolean isCanceled() {
1864         return mCanceled;
1865     }
1866 
1867     @UiThread
1868     private void resumeLastTask() {
1869         if (mRecentsAnimationController != null) {
1870             mRecentsAnimationController.finish(false /* toRecents */, null);
1871         }
1872         doLogGesture(LAST_TASK, null);
1873         reset();
1874     }
1875 
1876     @UiThread
1877     private void startNewTask() {
1878         TaskView taskToLaunch = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
1879         startNewTask(success -> {
1880             if (!success) {
1881                 reset();
1882                 // We couldn't launch the task, so take user to overview so they can
1883                 // decide what to do instead of staying in this broken state.
1884                 endLauncherTransitionController();
1885                 updateSysUiFlags(1 /* windowProgress == overview */);
1886             }
1887             doLogGesture(NEW_TASK, taskToLaunch);
1888         });
1889     }
1890 
1891     /**
1892      * Called when we successfully startNewTask() on the task that was previously running. Normally
1893      * we call resumeLastTask() when returning to the previously running task, but this handles a
1894      * specific edge case: if we switch from A to B, and back to A before B appears, we need to
1895      * start A again to ensure it stays on top.
1896      */
1897     @androidx.annotation.CallSuper
1898     protected void onRestartPreviouslyAppearedTask() {
1899         // Finish the controller here, since we won't get onTaskAppeared() for a task that already
1900         // appeared.
1901         if (mRecentsAnimationController != null) {
1902             mRecentsAnimationController.finish(false, null);
1903         }
1904         reset();
1905     }
1906 
1907     @UiThread
1908     private void finishCurrentTransitionToAllApps() {
1909         finishCurrentTransitionToHome();
1910         reset();
1911     }
1912 
1913     private void reset() {
1914         mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
1915         if (mActivity != null) {
1916             mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
1917         }
1918     }
1919 
1920     /**
1921      * Cancels any running animation so that the active target can be overriden by a new swipe
1922      * handler (in case of quick switch).
1923      */
1924     private void cancelCurrentAnimation() {
1925         ActiveGestureLog.INSTANCE.addLog(
1926                 "AbsSwipeUpHandler.cancelCurrentAnimation",
1927                 ActiveGestureErrorDetector.GestureEvent.CANCEL_CURRENT_ANIMATION);
1928         mCanceled = true;
1929         mCurrentShift.cancelAnimation();
1930 
1931         // Cleanup when switching handlers
1932         mInputConsumerProxy.unregisterCallback();
1933         mActivityInitListener.unregister();
1934         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
1935                 mActivityRestartListener);
1936         mTaskSnapshotCache.clear();
1937     }
1938 
1939     private void invalidateHandler() {
1940         if (!mActivityInterface.isInLiveTileMode() || mGestureState.getEndTarget() != RECENTS) {
1941             mInputConsumerProxy.destroy();
1942             mTaskAnimationManager.setLiveTileCleanUpHandler(null);
1943         }
1944         mInputConsumerProxy.unregisterCallback();
1945         endRunningWindowAnim(false /* cancel */);
1946 
1947         if (mGestureEndCallback != null) {
1948             mGestureEndCallback.run();
1949         }
1950 
1951         mActivityInitListener.unregister();
1952         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
1953                 mActivityRestartListener);
1954         mTaskSnapshotCache.clear();
1955     }
1956 
1957     private void invalidateHandlerWithLauncher() {
1958         endLauncherTransitionController();
1959 
1960         if (mRecentsView != null) {
1961             mRecentsView.onGestureAnimationEnd();
1962         }
1963         resetLauncherListeners();
1964     }
1965 
1966     private void endLauncherTransitionController() {
1967         mHasEndedLauncherTransition = true;
1968 
1969         if (mLauncherTransitionController != null) {
1970             // End the animation, but stay at the same visual progress.
1971             mLauncherTransitionController.getNormalController().dispatchSetInterpolator(
1972                     t -> Utilities.boundToRange(mCurrentShift.value, 0, 1));
1973             mLauncherTransitionController.getNormalController().getAnimationPlayer().end();
1974             mLauncherTransitionController = null;
1975         }
1976 
1977         if (mRecentsView != null) {
1978             mRecentsView.abortScrollerAnimation();
1979         }
1980     }
1981 
1982     /**
1983      * Unlike invalidateHandlerWithLauncher, this is called even when switching consumers, e.g. on
1984      * continued quick switch gesture, which cancels the previous handler but doesn't invalidate it.
1985      */
1986     private void resetLauncherListeners() {
1987         mActivity.getRootView().setOnApplyWindowInsetsListener(null);
1988 
1989         if (mRecentsView != null) {
1990             mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
1991         }
1992     }
1993 
1994     private void resetStateForAnimationCancel() {
1995         boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
1996         mActivityInterface.onTransitionCancelled(wasVisible, mGestureState.getEndTarget());
1997 
1998         // Leave the pending invisible flag, as it may be used by wallpaper open animation.
1999         if (mActivity != null) {
2000             mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
2001         }
2002     }
2003 
2004     protected void switchToScreenshot() {
2005         if (!hasTargets()) {
2006             // If there are no targets, then we don't need to capture anything
2007             mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
2008         } else {
2009             boolean finishTransitionPosted = false;
2010             // If we already have cached screenshot(s) from running tasks, skip update
2011             boolean shouldUpdate = false;
2012             int[] runningTaskIds = mGestureState.getRunningTaskIds(mIsSwipeForSplit);
2013             for (int id : runningTaskIds) {
2014                 if (!mTaskSnapshotCache.containsKey(id)) {
2015                     shouldUpdate = true;
2016                     break;
2017                 }
2018             }
2019 
2020             if (mRecentsAnimationController != null) {
2021                 // Update the screenshot of the task
2022                 if (shouldUpdate) {
2023                     UI_HELPER_EXECUTOR.execute(() -> {
2024                         if (mRecentsAnimationController == null) return;
2025                         for (int id : runningTaskIds) {
2026                             mTaskSnapshotCache.put(
2027                                     id, mRecentsAnimationController.screenshotTask(id));
2028                         }
2029 
2030                         MAIN_EXECUTOR.execute(() -> {
2031                             if (!updateThumbnail(false /* refreshView */)) {
2032                                 setScreenshotCapturedState();
2033                             }
2034                         });
2035                     });
2036                     return;
2037                 }
2038 
2039                 finishTransitionPosted = updateThumbnail(false /* refreshView */);
2040             }
2041 
2042             if (!finishTransitionPosted) {
2043                 setScreenshotCapturedState();
2044             }
2045         }
2046     }
2047 
2048     // Returns whether finish transition was posted.
2049     private boolean updateThumbnail(boolean refreshView) {
2050         if (mGestureState.getEndTarget() == HOME
2051                 || mGestureState.getEndTarget() == NEW_TASK
2052                 || mGestureState.getEndTarget() == ALL_APPS
2053                 || mRecentsView == null) {
2054             // Capture the screenshot before finishing the transition to home or quickswitching to
2055             // ensure it's taken in the correct orientation, but no need to update the thumbnail.
2056             return false;
2057         }
2058 
2059         boolean finishTransitionPosted = false;
2060         TaskView updatedTaskView = mRecentsView.updateThumbnail(mTaskSnapshotCache, refreshView);
2061         if (updatedTaskView != null && refreshView && !mCanceled) {
2062             // Defer finishing the animation until the next launcher frame with the
2063             // new thumbnail
2064             finishTransitionPosted = ViewUtils.postFrameDrawn(updatedTaskView,
2065                     () -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
2066                     this::isCanceled);
2067         }
2068 
2069         return finishTransitionPosted;
2070     }
2071 
2072     private void setScreenshotCapturedState() {
2073         // If we haven't posted a draw callback, set the state immediately.
2074         TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT);
2075         mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
2076         TraceHelper.INSTANCE.endSection();
2077     }
2078 
2079     private void finishCurrentTransitionToRecents() {
2080         if (mRecentsView != null
2081                 && mActivityInterface.getDesktopVisibilityController() != null
2082                 && mActivityInterface.getDesktopVisibilityController().areFreeformTasksVisible()) {
2083             mRecentsView.switchToScreenshot(() -> {
2084                 mRecentsView.finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
2085                         () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
2086             });
2087         } else {
2088             mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
2089             if (mRecentsAnimationController != null) {
2090                 mRecentsAnimationController.detachNavigationBarFromApp(true);
2091             }
2092         }
2093     }
2094 
2095     private void finishCurrentTransitionToHome() {
2096         if (!hasTargets() || mRecentsAnimationController == null) {
2097             // If there are no targets or the animation not started, then there is nothing to finish
2098             mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
2099             maybeAbortSwipePipToHome();
2100         } else {
2101             maybeFinishSwipePipToHome();
2102             finishRecentsControllerToHome(
2103                     () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
2104         }
2105         if (mSwipePipToHomeReleaseCheck != null) {
2106             mSwipePipToHomeReleaseCheck.setCanRelease(true);
2107             mSwipePipToHomeReleaseCheck = null;
2108         }
2109         doLogGesture(HOME, mRecentsView == null ? null : mRecentsView.getCurrentPageTaskView());
2110     }
2111 
2112     /**
2113      * Notifies SysUI that transition is aborted if applicable and also pass leash transactions
2114      * from Launcher to WM.
2115      */
2116     private void maybeAbortSwipePipToHome() {
2117         if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
2118             SystemUiProxy.INSTANCE.get(mContext).abortSwipePipToHome(
2119                     mSwipePipToHomeAnimator.getTaskId(),
2120                     mSwipePipToHomeAnimator.getComponentName());
2121             mIsSwipingPipToHome = false;
2122         }
2123     }
2124 
2125     /**
2126      * Notifies SysUI that transition is finished if applicable and also pass leash transactions
2127      * from Launcher to WM.
2128      * This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
2129      */
2130     private void maybeFinishSwipePipToHome() {
2131         if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
2132             mRecentsAnimationController.setFinishTaskTransaction(
2133                     mSwipePipToHomeAnimator.getTaskId(),
2134                     mSwipePipToHomeAnimator.getFinishTransaction(),
2135                     mSwipePipToHomeAnimator.getContentOverlay());
2136             mIsSwipingPipToHome = false;
2137         } else if (mIsSwipeForSplit) {
2138             // Transaction to hide the task to avoid flicker for entering PiP from split-screen.
2139             PictureInPictureSurfaceTransaction tx =
2140                     new PictureInPictureSurfaceTransaction.Builder()
2141                             .setAlpha(0f)
2142                             .build();
2143             tx.setShouldDisableCanAffectSystemUiFlags(false);
2144             int[] taskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds();
2145             for (int taskId : taskIds) {
2146                 mRecentsAnimationController.setFinishTaskTransaction(taskId,
2147                         tx, null /* overlay */);
2148             }
2149         }
2150     }
2151 
2152     protected abstract void finishRecentsControllerToHome(Runnable callback);
2153 
2154     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
2155         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
2156             return;
2157         }
2158         endLauncherTransitionController();
2159         mRecentsView.onSwipeUpAnimationSuccess();
2160         mTaskAnimationManager.setLiveTileCleanUpHandler(() -> {
2161             mRecentsView.cleanupRemoteTargets();
2162             mInputConsumerProxy.destroy();
2163         });
2164         mTaskAnimationManager.enableLiveTileRestartListener();
2165 
2166         SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
2167         doLogGesture(RECENTS, mRecentsView.getCurrentPageTaskView());
2168         reset();
2169     }
2170 
2171     private static boolean isNotInRecents(RemoteAnimationTarget app) {
2172         return app.isNotInRecents
2173                 || app.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME;
2174     }
2175 
2176     protected void performHapticFeedback() {
2177         VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
2178     }
2179 
2180     public Consumer<MotionEvent> getRecentsViewDispatcher(float navbarRotation) {
2181         return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null;
2182     }
2183 
2184     public void setGestureEndCallback(Runnable gestureEndCallback) {
2185         mGestureEndCallback = gestureEndCallback;
2186     }
2187 
2188     protected void linkRecentsViewScroll() {
2189         SurfaceTransactionApplier applier = new SurfaceTransactionApplier(mRecentsView);
2190         runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
2191                         .setSyncTransactionApplier(applier));
2192         runOnRecentsAnimationAndLauncherBound(() ->
2193                 mRecentsAnimationTargets.addReleaseCheck(applier));
2194 
2195         mRecentsView.addOnScrollChangedListener(mOnRecentsScrollListener);
2196         runOnRecentsAnimationAndLauncherBound(() ->
2197                 mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
2198                         mRecentsAnimationTargets));
2199 
2200         // Disable scrolling in RecentsView for trackpad 3-finger swipe up gesture.
2201         if (!mGestureState.isThreeFingerTrackpadGesture()) {
2202             mRecentsViewScrollLinked = true;
2203         }
2204     }
2205 
2206     private void onRecentsViewScroll() {
2207         if (moveWindowWithRecentsScroll()) {
2208             onCurrentShiftUpdated();
2209         }
2210     }
2211 
2212     protected void startNewTask(Consumer<Boolean> resultCallback) {
2213         // Launch the task user scrolled to (mRecentsView.getNextPage()).
2214         if (!mCanceled) {
2215             TaskView nextTask = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
2216             if (nextTask != null) {
2217                 int[] taskIds = nextTask.getTaskIds();
2218                 StringBuilder nextTaskLog = new StringBuilder();
2219                 for (TaskIdAttributeContainer c : nextTask.getTaskIdAttributeContainers()) {
2220                     if (c == null) {
2221                         continue;
2222                     }
2223                     nextTaskLog
2224                             .append("[id: ")
2225                             .append(c.getTask().key.id)
2226                             .append(", pkg: ")
2227                             .append(c.getTask().key.getPackageName())
2228                             .append("] | ");
2229                 }
2230                 mGestureState.updateLastStartedTaskIds(taskIds);
2231                 boolean hasTaskPreviouslyAppeared = Arrays.stream(taskIds).anyMatch(
2232                                 taskId -> mGestureState.getPreviouslyAppearedTaskIds()
2233                                         .contains(taskId));
2234                 if (!hasTaskPreviouslyAppeared) {
2235                     ActiveGestureLog.INSTANCE.trackEvent(EXPECTING_TASK_APPEARED);
2236                 }
2237                 ActiveGestureLog.INSTANCE.addLog("Launching task: " + nextTaskLog);
2238                 nextTask.launchTask(success -> {
2239                     resultCallback.accept(success);
2240                     if (success) {
2241                         if (hasTaskPreviouslyAppeared) {
2242                             onRestartPreviouslyAppearedTask();
2243                         }
2244                     } else {
2245                         mActivityInterface.onLaunchTaskFailed();
2246                         if (mRecentsAnimationController != null) {
2247                             mRecentsAnimationController.finish(true /* toRecents */, null);
2248                         }
2249                     }
2250                 }, true /* freezeTaskList */);
2251             } else {
2252                 mActivityInterface.onLaunchTaskFailed();
2253                 Toast.makeText(mContext, R.string.activity_not_available, LENGTH_SHORT).show();
2254                 if (mRecentsAnimationController != null) {
2255                     mRecentsAnimationController.finish(true /* toRecents */, null);
2256                 }
2257             }
2258         }
2259         mCanceled = false;
2260     }
2261 
2262     /**
2263      * Runs the given {@param action} if the recents animation has already started and Launcher has
2264      * been created and bound to the TouchInteractionService, or queues it to be run when it this
2265      * next happens.
2266      */
2267     private void runOnRecentsAnimationAndLauncherBound(Runnable action) {
2268         mRecentsAnimationStartCallbacks.add(action);
2269         flushOnRecentsAnimationAndLauncherBound();
2270     }
2271 
2272     private void flushOnRecentsAnimationAndLauncherBound() {
2273         if (mRecentsAnimationTargets == null ||
2274                 !mStateCallback.hasStates(STATE_LAUNCHER_BIND_TO_SERVICE)) {
2275             return;
2276         }
2277 
2278         if (!mRecentsAnimationStartCallbacks.isEmpty()) {
2279             for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
2280                 action.run();
2281             }
2282             mRecentsAnimationStartCallbacks.clear();
2283         }
2284     }
2285 
2286     /**
2287      * TODO can we remove this now that we don't finish the controller until onTaskAppeared()?
2288      * @return whether the recents animation has started and there are valid app targets.
2289      */
2290     protected boolean hasTargets() {
2291         return mRecentsAnimationTargets != null && mRecentsAnimationTargets.hasTargets();
2292     }
2293 
2294     @Override
2295     public void onRecentsAnimationFinished(RecentsAnimationController controller) {
2296         mRecentsAnimationController = null;
2297         mRecentsAnimationTargets = null;
2298         if (mRecentsView != null) {
2299             mRecentsView.setRecentsAnimationTargets(null, null);
2300         }
2301     }
2302 
2303     @Override
2304     public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
2305         if (mRecentsAnimationController != null) {
2306             boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTargets).anyMatch(
2307                     mGestureState.mLastStartedTaskIdPredicate);
2308             if (!mStateCallback.hasStates(STATE_GESTURE_COMPLETED) && !hasStartedTaskBefore) {
2309                 // This is a special case, if a task is started mid-gesture that wasn't a part of a
2310                 // previous quickswitch task launch, then cancel the animation back to the app
2311                 RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
2312                 TaskInfo taskInfo = appearedTaskTarget.taskInfo;
2313                 ActiveGestureLog.INSTANCE.addLog("Unexpected task appeared"
2314                         + " id=" + taskInfo.taskId
2315                         + " pkg=" + taskInfo.baseIntent.getComponent().getPackageName());
2316                 finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
2317             } else if (handleTaskAppeared(appearedTaskTargets)) {
2318                 Optional<RemoteAnimationTarget> taskTargetOptional =
2319                         Arrays.stream(appearedTaskTargets)
2320                                 .filter(mGestureState.mLastStartedTaskIdPredicate)
2321                                 .findFirst();
2322                 if (!taskTargetOptional.isPresent()) {
2323                     ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id");
2324                     finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
2325                     return;
2326                 }
2327                 RemoteAnimationTarget taskTarget = taskTargetOptional.get();
2328                 TaskView taskView = mRecentsView == null
2329                         ? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
2330                 if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) {
2331                     ActiveGestureLog.INSTANCE.addLog("Invalid task view splash state");
2332                     finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
2333                     return;
2334                 }
2335 
2336                 ViewGroup splashView = mActivity.getDragLayer();
2337                 final QuickstepLauncher quickstepLauncher = mActivity instanceof QuickstepLauncher
2338                         ? (QuickstepLauncher) mActivity : null;
2339                 if (quickstepLauncher != null) {
2340                     quickstepLauncher.getDepthController().pauseBlursOnWindows(true);
2341                 }
2342 
2343                 // When revealing the app with launcher splash screen, make the app visible
2344                 // and behind the splash view before the splash is animated away.
2345                 SurfaceTransactionApplier surfaceApplier =
2346                         new SurfaceTransactionApplier(splashView);
2347                 SurfaceTransaction transaction = new SurfaceTransaction();
2348                 for (RemoteAnimationTarget target : appearedTaskTargets) {
2349                     transaction.forSurface(target.leash).setAlpha(1).setLayer(-1).setShow();
2350                 }
2351                 surfaceApplier.scheduleApply(transaction);
2352 
2353                 SplashScreenExitAnimationUtils.startAnimations(splashView, taskTarget.leash,
2354                         mSplashMainWindowShiftLength, new TransactionPool(), new Rect(),
2355                         SPLASH_ANIMATION_DURATION, SPLASH_FADE_OUT_DURATION,
2356                         /* iconStartAlpha= */ 0, /* brandingStartAlpha= */ 0,
2357                         SPLASH_APP_REVEAL_DELAY, SPLASH_APP_REVEAL_DURATION,
2358                         new AnimatorListenerAdapter() {
2359                             @Override
2360                             public void onAnimationEnd(Animator animation) {
2361                                 // Hiding launcher which shows the app surface behind, then
2362                                 // finishing recents to the app. After transition finish, showing
2363                                 // the views on launcher again, so it can be visible when next
2364                                 // animation starts.
2365                                 splashView.setAlpha(0);
2366                                 if (quickstepLauncher != null) {
2367                                     quickstepLauncher.getDepthController()
2368                                             .pauseBlursOnWindows(false);
2369                                 }
2370                                 finishRecentsAnimationOnTasksAppeared(() -> splashView.setAlpha(1));
2371                             }
2372                         });
2373             }
2374         }
2375     }
2376 
2377     private void finishRecentsAnimationOnTasksAppeared(Runnable onFinishComplete) {
2378         if (mRecentsAnimationController != null) {
2379             mRecentsAnimationController.finish(false /* toRecents */, onFinishComplete);
2380         }
2381         ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimationOnTasksAppeared");
2382     }
2383 
2384     /**
2385      * @return The index of the TaskView in RecentsView whose taskId matches the task that will
2386      * resume if we finish the controller.
2387      */
2388     protected int getLastAppearedTaskIndex() {
2389         if (mRecentsView == null) {
2390             return -1;
2391         }
2392 
2393         OptionalInt firstValidTaskId = Arrays.stream(mGestureState.getLastAppearedTaskIds())
2394                 .filter(i -> i != -1)
2395                 .findFirst();
2396         return firstValidTaskId.isPresent()
2397                 ? mRecentsView.getTaskIndexForId(firstValidTaskId.getAsInt())
2398                 : mRecentsView.getRunningTaskIndex();
2399     }
2400 
2401     /**
2402      * @return Whether we are continuing a gesture that already landed on a new task,
2403      * but before that task appeared.
2404      */
2405     protected boolean hasStartedNewTask() {
2406         return mGestureState.getLastStartedTaskIds()[0] != -1;
2407     }
2408 
2409     /**
2410      * Registers a callback to run when the activity is ready.
2411      */
2412     public void initWhenReady() {
2413         // Preload the plan
2414         RecentsModel.INSTANCE.get(mContext).getTasks(null);
2415 
2416         mActivityInitListener.register();
2417     }
2418 
2419     /**
2420      * Applies the transform on the recents animation
2421      */
2422     protected void applyScrollAndTransform() {
2423         // No need to apply any transform if there is ongoing swipe-to-home animator
2424         //    swipe-to-pip handles the leash solely
2425         //    swipe-to-icon animation is handled by RectFSpringAnim anim
2426         boolean notSwipingToHome = mRecentsAnimationTargets != null
2427                 && mGestureState.getEndTarget() != HOME;
2428         boolean setRecentsScroll = mRecentsViewScrollLinked && mRecentsView != null;
2429         float progress = Math.max(mCurrentShift.value, getScaleProgressDueToScroll());
2430         int scrollOffset = setRecentsScroll ? mRecentsView.getScrollOffset() : 0;
2431         if (!mStartMovingTasks && (progress > 0 || scrollOffset != 0)) {
2432             mStartMovingTasks = true;
2433             startInterceptingTouchesForGesture();
2434         }
2435         for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
2436             AnimatorControllerWithResistance playbackController =
2437                     remoteHandle.getPlaybackController();
2438             if (playbackController != null) {
2439                 playbackController.setProgress(progress, mDragLengthFactor);
2440             }
2441 
2442             if (notSwipingToHome) {
2443                 TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator();
2444                 if (setRecentsScroll) {
2445                     taskViewSimulator.setScroll(scrollOffset);
2446                 }
2447                 taskViewSimulator.apply(remoteHandle.getTransformParams());
2448             }
2449         }
2450     }
2451 
2452     // Scaling of RecentsView during quick switch based on amount of recents scroll
2453     private float getScaleProgressDueToScroll() {
2454         if (mActivity == null || !mActivity.getDeviceProfile().isTablet || mRecentsView == null
2455                 || !mRecentsViewScrollLinked) {
2456             return 0;
2457         }
2458 
2459         float scrollOffset = Math.abs(mRecentsView.getScrollOffset(mRecentsView.getCurrentPage()));
2460         int maxScrollOffset = mRecentsView.getPagedOrientationHandler().getPrimaryValue(
2461                 mRecentsView.getLastComputedTaskSize().width(),
2462                 mRecentsView.getLastComputedTaskSize().height());
2463         maxScrollOffset += mRecentsView.getPageSpacing();
2464 
2465         float maxScaleProgress =
2466                 MAX_QUICK_SWITCH_RECENTS_SCALE_PROGRESS * mRecentsView.getMaxScaleForFullScreen();
2467         float scaleProgress = maxScaleProgress;
2468 
2469         if (scrollOffset < mQuickSwitchScaleScrollThreshold) {
2470             scaleProgress = Utilities.mapToRange(scrollOffset, 0, mQuickSwitchScaleScrollThreshold,
2471                     0, maxScaleProgress, ACCELERATE_DECELERATE);
2472         } else if (scrollOffset > (maxScrollOffset - mQuickSwitchScaleScrollThreshold)) {
2473             scaleProgress = Utilities.mapToRange(scrollOffset,
2474                     (maxScrollOffset - mQuickSwitchScaleScrollThreshold), maxScrollOffset,
2475                     maxScaleProgress, 0, ACCELERATE_DECELERATE);
2476         }
2477 
2478         return scaleProgress;
2479     }
2480 
2481     /**
2482      * Overrides the gesture displacement to keep the app window at the bottom of the screen while
2483      * the transient taskbar is being swiped in.
2484      *
2485      * There is also a catch up period so that the window can start moving 1:1 with the swipe.
2486      */
2487     @Override
2488     protected float overrideDisplacementForTransientTaskbar(float displacement) {
2489         if (!mIsTransientTaskbar) {
2490             return displacement;
2491         }
2492 
2493         if (mTaskbarAlreadyOpen || mIsTaskbarAllAppsOpen || mGestureState.isTrackpadGesture()) {
2494             return displacement;
2495         }
2496 
2497         if (displacement < mTaskbarAppWindowThreshold) {
2498             return 0;
2499         }
2500 
2501         // "Catch up" with the displacement at mTaskbarCatchUpThreshold.
2502         if (displacement < mTaskbarCatchUpThreshold) {
2503             return Utilities.mapToRange(displacement, mTaskbarAppWindowThreshold,
2504                     mTaskbarCatchUpThreshold, 0, mTaskbarCatchUpThreshold, ACCELERATE_DECELERATE);
2505         }
2506 
2507         return displacement;
2508     }
2509 
2510     private void setDividerShown(boolean shown) {
2511         if (mRecentsAnimationTargets == null || mIsDividerShown == shown) {
2512             return;
2513         }
2514         mIsDividerShown = shown;
2515         TaskViewUtils.createSplitAuxiliarySurfacesAnimator(
2516                 mRecentsAnimationTargets.nonApps, shown, null /* animatorHandler */);
2517     }
2518 
2519     public interface Factory {
2520         AbsSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs);
2521     }
2522 }
2523