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