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