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