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 com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
19 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
20 import static com.android.launcher3.anim.Interpolators.DEACCEL;
21 import static com.android.launcher3.anim.Interpolators.LINEAR;
22 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
23 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
24 import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
25 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
26 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
27 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
28 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
29 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
30 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
31 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
32 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
33 import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
34 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
35 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
36 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
37 import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
38 import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
39 import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
40 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
41 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
42 import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
43 import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
44 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
45 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
46 
47 import android.animation.Animator;
48 import android.animation.TimeInterpolator;
49 import android.animation.ValueAnimator;
50 import android.annotation.TargetApi;
51 import android.app.ActivityManager;
52 import android.content.Context;
53 import android.content.Intent;
54 import android.graphics.PointF;
55 import android.os.Build;
56 import android.os.SystemClock;
57 import android.view.View;
58 import android.view.View.OnApplyWindowInsetsListener;
59 import android.view.ViewTreeObserver.OnDrawListener;
60 import android.view.WindowInsets;
61 import android.view.animation.Interpolator;
62 
63 import androidx.annotation.UiThread;
64 
65 import com.android.launcher3.AbstractFloatingView;
66 import com.android.launcher3.DeviceProfile;
67 import com.android.launcher3.R;
68 import com.android.launcher3.Utilities;
69 import com.android.launcher3.anim.AnimationSuccessListener;
70 import com.android.launcher3.anim.AnimatorPlaybackController;
71 import com.android.launcher3.anim.Interpolators;
72 import com.android.launcher3.logging.StatsLogManager;
73 import com.android.launcher3.logging.UserEventDispatcher;
74 import com.android.launcher3.statemanager.StatefulActivity;
75 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
76 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
77 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
78 import com.android.launcher3.util.TraceHelper;
79 import com.android.quickstep.BaseActivityInterface.AnimationFactory;
80 import com.android.quickstep.GestureState.GestureEndTarget;
81 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
82 import com.android.quickstep.util.ActiveGestureLog;
83 import com.android.quickstep.util.RectFSpringAnim;
84 import com.android.quickstep.util.ShelfPeekAnim;
85 import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
86 import com.android.quickstep.views.LiveTileOverlay;
87 import com.android.quickstep.views.RecentsView;
88 import com.android.quickstep.views.TaskView;
89 import com.android.systemui.shared.recents.model.ThumbnailData;
90 import com.android.systemui.shared.system.ActivityManagerWrapper;
91 import com.android.systemui.shared.system.InputConsumerController;
92 import com.android.systemui.shared.system.LatencyTrackerCompat;
93 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
94 import com.android.systemui.shared.system.TaskInfoCompat;
95 import com.android.systemui.shared.system.TaskStackChangeListener;
96 
97 /**
98  * Handles the navigation gestures when Launcher is the default home activity.
99  * TODO: Merge this with BaseSwipeUpHandler
100  */
101 @TargetApi(Build.VERSION_CODES.O)
102 public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q extends RecentsView>
103         extends BaseSwipeUpHandler<T, Q> implements OnApplyWindowInsetsListener {
104     private static final String TAG = BaseSwipeUpHandlerV2.class.getSimpleName();
105 
106     private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
107 
getFlagForIndex(int index, String name)108     private static int getFlagForIndex(int index, String name) {
109         if (DEBUG_STATES) {
110             STATE_NAMES[index] = name;
111         }
112         return 1 << index;
113     }
114 
115     // Launcher UI related states
116     protected static final int STATE_LAUNCHER_PRESENT =
117             getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
118     protected static final int STATE_LAUNCHER_STARTED =
119             getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
120     protected static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
121 
122     // Internal initialization states
123     private static final int STATE_APP_CONTROLLER_RECEIVED =
124             getFlagForIndex(3, "STATE_APP_CONTROLLER_RECEIVED");
125 
126     // Interaction finish states
127     private static final int STATE_SCALED_CONTROLLER_HOME =
128             getFlagForIndex(4, "STATE_SCALED_CONTROLLER_HOME");
129     private static final int STATE_SCALED_CONTROLLER_RECENTS =
130             getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
131 
132     protected static final int STATE_HANDLER_INVALIDATED =
133             getFlagForIndex(6, "STATE_HANDLER_INVALIDATED");
134     private static final int STATE_GESTURE_STARTED =
135             getFlagForIndex(7, "STATE_GESTURE_STARTED");
136     private static final int STATE_GESTURE_CANCELLED =
137             getFlagForIndex(8, "STATE_GESTURE_CANCELLED");
138     private static final int STATE_GESTURE_COMPLETED =
139             getFlagForIndex(9, "STATE_GESTURE_COMPLETED");
140 
141     private static final int STATE_CAPTURE_SCREENSHOT =
142             getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT");
143     protected static final int STATE_SCREENSHOT_CAPTURED =
144             getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED");
145     private static final int STATE_SCREENSHOT_VIEW_SHOWN =
146             getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN");
147 
148     private static final int STATE_RESUME_LAST_TASK =
149             getFlagForIndex(13, "STATE_RESUME_LAST_TASK");
150     private static final int STATE_START_NEW_TASK =
151             getFlagForIndex(14, "STATE_START_NEW_TASK");
152     private static final int STATE_CURRENT_TASK_FINISHED =
153             getFlagForIndex(15, "STATE_CURRENT_TASK_FINISHED");
154 
155     private static final int LAUNCHER_UI_STATES =
156             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
157 
158     public static final long MAX_SWIPE_DURATION = 350;
159     public static final long MIN_OVERSHOOT_DURATION = 120;
160 
161     public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
162     private static final float SWIPE_DURATION_MULTIPLIER =
163             Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
164     private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured";
165 
166     public static final long RECENTS_ATTACH_DURATION = 300;
167 
168     /**
169      * Used as the page index for logging when we return to the last task at the end of the gesture.
170      */
171     private static final int LOG_NO_OP_PAGE_INDEX = -1;
172 
173     protected final TaskAnimationManager mTaskAnimationManager;
174 
175     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
176     private RunningWindowAnim mRunningWindowAnim;
177     private boolean mIsShelfPeeking;
178 
179     private boolean mContinuingLastGesture;
180 
181     private ThumbnailData mTaskSnapshot;
182 
183     // Used to control launcher components throughout the swipe gesture.
184     private AnimatorPlaybackController mLauncherTransitionController;
185     private boolean mHasLauncherTransitionControllerStarted;
186 
187     private AnimationFactory mAnimationFactory = (t) -> { };
188 
189     private boolean mWasLauncherAlreadyVisible;
190 
191     private boolean mPassedOverviewThreshold;
192     private boolean mGestureStarted;
193     private int mLogAction = Touch.SWIPE;
194     private int mLogDirection = Direction.UP;
195     private PointF mDownPos;
196     private boolean mIsLikelyToStartNewTask;
197 
198     private final long mTouchTimeMs;
199     private long mLauncherFrameDrawnTime;
200 
201     private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
202 
BaseSwipeUpHandlerV2(Context context, RecentsAnimationDeviceState deviceState, TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs, boolean continuingLastGesture, InputConsumerController inputConsumer)203     public BaseSwipeUpHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
204             TaskAnimationManager taskAnimationManager, GestureState gestureState,
205             long touchTimeMs, boolean continuingLastGesture,
206             InputConsumerController inputConsumer) {
207         super(context, deviceState, gestureState, inputConsumer);
208         mTaskAnimationManager = taskAnimationManager;
209         mTouchTimeMs = touchTimeMs;
210         mContinuingLastGesture = continuingLastGesture;
211 
212         initAfterSubclassConstructor();
213         initStateCallbacks();
214     }
215 
initStateCallbacks()216     private void initStateCallbacks() {
217         mStateCallback = new MultiStateCallback(STATE_NAMES);
218 
219         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
220                 this::onLauncherPresentAndGestureStarted);
221 
222         mStateCallback.runOnceAtState(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
223                 this::initializeLauncherAnimationController);
224 
225         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
226                 this::launcherFrameDrawn);
227 
228         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
229                         | STATE_GESTURE_CANCELLED,
230                 this::resetStateForAnimationCancel);
231 
232         mStateCallback.runOnceAtState(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
233                 this::resumeLastTask);
234         mStateCallback.runOnceAtState(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
235                 this::startNewTask);
236 
237         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
238                         | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT,
239                 this::switchToScreenshot);
240 
241         mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
242                         | STATE_SCALED_CONTROLLER_RECENTS,
243                 this::finishCurrentTransitionToRecents);
244 
245         mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
246                         | STATE_SCALED_CONTROLLER_HOME,
247                 this::finishCurrentTransitionToHome);
248         mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
249                 this::reset);
250 
251         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
252                         | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
253                         | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
254                         | STATE_GESTURE_STARTED,
255                 this::setupLauncherUiAfterSwipeUpToRecentsAnimation);
256 
257         mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED,
258                 this::continueComputingRecentsScrollIfNecessary);
259         mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED
260                         | STATE_RECENTS_SCROLLING_FINISHED,
261                 this::onSettledOnEndTarget);
262 
263         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
264         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
265                 this::invalidateHandlerWithLauncher);
266         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
267                 this::notifyTransitionCancelled);
268 
269         mGestureState.runOnceAtState(STATE_END_TARGET_SET,
270                 () -> mDeviceState.onEndTargetCalculated(mGestureState.getEndTarget(),
271                         mActivityInterface));
272 
273         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
274             mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
275                             | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
276                     (b) -> mRecentsView.setRunningTaskHidden(!b));
277         }
278     }
279 
280     @Override
onActivityInit(Boolean alreadyOnHome)281     protected boolean onActivityInit(Boolean alreadyOnHome) {
282         super.onActivityInit(alreadyOnHome);
283         final T activity = mActivityInterface.getCreatedActivity();
284         if (mActivity == activity) {
285             return true;
286         }
287 
288         if (mActivity != null) {
289             // The launcher may have been recreated as a result of device rotation.
290             int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
291             initStateCallbacks();
292             mStateCallback.setState(oldState);
293         }
294         mWasLauncherAlreadyVisible = alreadyOnHome;
295         mActivity = activity;
296         // Override the visibility of the activity until the gesture actually starts and we swipe
297         // up, or until we transition home and the home animation is composed
298         if (alreadyOnHome) {
299             mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
300         } else {
301             mActivity.addForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
302         }
303 
304         mRecentsView = activity.getOverviewPanel();
305         mRecentsView.setOnPageTransitionEndCallback(null);
306         addLiveTileOverlay();
307 
308         mStateCallback.setState(STATE_LAUNCHER_PRESENT);
309         if (alreadyOnHome) {
310             onLauncherStart();
311         } else {
312             activity.runOnceOnStart(this::onLauncherStart);
313         }
314 
315         setupRecentsViewUi();
316 
317         if (mDeviceState.getNavMode() == TWO_BUTTONS) {
318             // If the device is in two button mode, swiping up will show overview with predictions
319             // so we need to kick off switching to the overview predictions as soon as possible
320             mActivityInterface.updateOverviewPredictionState();
321         }
322         linkRecentsViewScroll();
323 
324         return true;
325     }
326 
327     @Override
moveWindowWithRecentsScroll()328     protected boolean moveWindowWithRecentsScroll() {
329         return mGestureState.getEndTarget() != HOME;
330     }
331 
onLauncherStart()332     private void onLauncherStart() {
333         final T activity = mActivityInterface.getCreatedActivity();
334         if (mActivity != activity) {
335             return;
336         }
337         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
338             return;
339         }
340         mTaskViewSimulator.setRecentsConfiguration(mActivity.getResources().getConfiguration());
341 
342         // If we've already ended the gesture and are going home, don't prepare recents UI,
343         // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
344         if (mGestureState.getEndTarget() != HOME) {
345             Runnable initAnimFactory = () -> {
346                 mAnimationFactory = mActivityInterface.prepareRecentsUI(mDeviceState,
347                         mWasLauncherAlreadyVisible, this::onAnimatorPlaybackControllerCreated);
348                 maybeUpdateRecentsAttachedState(false /* animate */);
349             };
350             if (mWasLauncherAlreadyVisible) {
351                 // Launcher is visible, but might be about to stop. Thus, if we prepare recents
352                 // now, it might get overridden by moveToRestState() in onStop(). To avoid this,
353                 // wait until the next gesture (and possibly launcher) starts.
354                 mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, initAnimFactory);
355             } else {
356                 initAnimFactory.run();
357             }
358         }
359         AbstractFloatingView.closeAllOpenViewsExcept(activity, mWasLauncherAlreadyVisible,
360                 AbstractFloatingView.TYPE_LISTENER);
361 
362         if (mWasLauncherAlreadyVisible) {
363             mStateCallback.setState(STATE_LAUNCHER_DRAWN);
364         } else {
365             Object traceToken = TraceHelper.INSTANCE.beginSection("WTS-init");
366             View dragLayer = activity.getDragLayer();
367             dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
368                 boolean mHandled = false;
369 
370                 @Override
371                 public void onDraw() {
372                     if (mHandled) {
373                         return;
374                     }
375                     mHandled = true;
376 
377                     TraceHelper.INSTANCE.endSection(traceToken);
378                     dragLayer.post(() ->
379                             dragLayer.getViewTreeObserver().removeOnDrawListener(this));
380                     if (activity != mActivity) {
381                         return;
382                     }
383 
384                     mStateCallback.setState(STATE_LAUNCHER_DRAWN);
385                 }
386             });
387         }
388 
389         activity.getRootView().setOnApplyWindowInsetsListener(this);
390         mStateCallback.setState(STATE_LAUNCHER_STARTED);
391     }
392 
onLauncherPresentAndGestureStarted()393     private void onLauncherPresentAndGestureStarted() {
394         // Re-setup the recents UI when gesture starts, as the state could have been changed during
395         // that time by a previous window transition.
396         setupRecentsViewUi();
397 
398         // For the duration of the gesture, in cases where an activity is launched while the
399         // activity is not yet resumed, finish the animation to ensure we get resumed
400         mGestureState.getActivityInterface().setOnDeferredActivityLaunchCallback(
401                 mOnDeferredActivityLaunch);
402 
403         notifyGestureStartedAsync();
404     }
405 
onDeferredActivityLaunch()406     private void onDeferredActivityLaunch() {
407         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
408             mActivityInterface.switchRunningTaskViewToScreenshot(
409                     null, () -> {
410                         mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
411                     });
412         } else {
413             mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
414         }
415     }
416 
setupRecentsViewUi()417     private void setupRecentsViewUi() {
418         if (mContinuingLastGesture) {
419             updateSysUiFlags(mCurrentShift.value);
420             return;
421         }
422         notifyGestureAnimationStartToRecents();
423     }
424 
notifyGestureAnimationStartToRecents()425     protected void notifyGestureAnimationStartToRecents() {
426         mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId());
427     }
428 
launcherFrameDrawn()429     private void launcherFrameDrawn() {
430         mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
431     }
432 
initializeLauncherAnimationController()433     private void initializeLauncherAnimationController() {
434         buildAnimationController();
435 
436         Object traceToken = TraceHelper.INSTANCE.beginSection("logToggleRecents",
437                 TraceHelper.FLAG_IGNORE_BINDERS);
438         // Only used in debug builds
439         if (LatencyTrackerCompat.isEnabled(mContext)) {
440             LatencyTrackerCompat.logToggleRecents(
441                     (int) (mLauncherFrameDrawnTime - mTouchTimeMs));
442         }
443         TraceHelper.INSTANCE.endSection(traceToken);
444 
445         // This method is only called when STATE_GESTURE_STARTED is set, so we can enable the
446         // high-res thumbnail loader here once we are sure that we will end up in an overview state
447         RecentsModel.INSTANCE.get(mContext).getThumbnailCache()
448                 .getHighResLoadingState().setVisible(true);
449     }
450 
451     @Override
onMotionPauseChanged(boolean isPaused)452     public void onMotionPauseChanged(boolean isPaused) {
453         setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION);
454 
455         if (mDeviceState.isFullyGesturalNavMode() && isPaused) {
456             // In fully gestural nav mode, switch to overview predictions once the user has paused
457             // (this is a no-op if the predictions are already in that state)
458             mActivityInterface.updateOverviewPredictionState();
459         }
460     }
461 
maybeUpdateRecentsAttachedState()462     public void maybeUpdateRecentsAttachedState() {
463         maybeUpdateRecentsAttachedState(true /* animate */);
464     }
465 
466     /**
467      * Determines whether to show or hide RecentsView. The window is always
468      * synchronized with its corresponding TaskView in RecentsView, so if
469      * RecentsView is shown, it will appear to be attached to the window.
470      *
471      * Note this method has no effect unless the navigation mode is NO_BUTTON.
472      */
maybeUpdateRecentsAttachedState(boolean animate)473     private void maybeUpdateRecentsAttachedState(boolean animate) {
474         if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
475             return;
476         }
477         RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null
478                 ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
479                 : null;
480         final boolean recentsAttachedToAppWindow;
481         if (mGestureState.getEndTarget() != null) {
482             recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow;
483         } else if (mContinuingLastGesture
484                 && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
485             recentsAttachedToAppWindow = true;
486         } else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) {
487             // The window is going away so make sure recents is always visible in this case.
488             recentsAttachedToAppWindow = true;
489         } else {
490             recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
491         }
492         mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
493     }
494 
495     @Override
setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask)496     public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
497         setIsLikelyToStartNewTask(isLikelyToStartNewTask, true /* animate */);
498     }
499 
setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask, boolean animate)500     private void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask, boolean animate) {
501         if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
502             mIsLikelyToStartNewTask = isLikelyToStartNewTask;
503             maybeUpdateRecentsAttachedState(animate);
504         }
505     }
506 
507     @UiThread
setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration)508     public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) {
509         mAnimationFactory.setShelfState(shelfState, interpolator, duration);
510         boolean wasShelfPeeking = mIsShelfPeeking;
511         mIsShelfPeeking = shelfState == PEEK;
512         if (mIsShelfPeeking != wasShelfPeeking) {
513             maybeUpdateRecentsAttachedState();
514         }
515         if (shelfState.shouldPreformHaptic) {
516             performHapticFeedback();
517         }
518     }
519 
buildAnimationController()520     private void buildAnimationController() {
521         if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
522             return;
523         }
524         initTransitionEndpoints(mActivity.getDeviceProfile());
525         mAnimationFactory.createActivityInterface(mTransitionDragLength);
526     }
527 
528     /**
529      * We don't want to change mLauncherTransitionController if mGestureState.getEndTarget() == HOME
530      * (it has its own animation) or if we're already animating the current controller.
531      * @return Whether we can create the launcher controller or update its progress.
532      */
canCreateNewOrUpdateExistingLauncherTransitionController()533     private boolean canCreateNewOrUpdateExistingLauncherTransitionController() {
534         return mGestureState.getEndTarget() != HOME && !mHasLauncherTransitionControllerStarted;
535     }
536 
537     @Override
onApplyWindowInsets(View view, WindowInsets windowInsets)538     public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
539         WindowInsets result = view.onApplyWindowInsets(windowInsets);
540         buildAnimationController();
541         return result;
542     }
543 
onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim)544     private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) {
545         mLauncherTransitionController = anim;
546         mLauncherTransitionController.dispatchSetInterpolator(t -> t * mDragLengthFactor);
547         mLauncherTransitionController.dispatchOnStart();
548         updateLauncherTransitionProgress();
549     }
550 
551     @Override
getLaunchIntent()552     public Intent getLaunchIntent() {
553         return mGestureState.getOverviewIntent();
554     }
555 
556     @Override
updateFinalShift()557     public void updateFinalShift() {
558         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
559             if (mRecentsAnimationTargets != null) {
560                 LiveTileOverlay.INSTANCE.update(
561                         mTaskViewSimulator.getCurrentCropRect(),
562                         mTaskViewSimulator.getCurrentCornerRadius());
563             }
564         }
565 
566         final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
567         if (passed != mPassedOverviewThreshold) {
568             mPassedOverviewThreshold = passed;
569             if (!mDeviceState.isFullyGesturalNavMode()) {
570                 performHapticFeedback();
571             }
572         }
573 
574         updateSysUiFlags(mCurrentShift.value);
575         applyWindowTransform();
576         updateLauncherTransitionProgress();
577     }
578 
updateLauncherTransitionProgress()579     private void updateLauncherTransitionProgress() {
580         if (mLauncherTransitionController == null
581                 || !canCreateNewOrUpdateExistingLauncherTransitionController()) {
582             return;
583         }
584         // Normalize the progress to 0 to 1, as the animation controller will clamp it to that
585         // anyway. The controller mimics the drag length factor by applying it to its interpolators.
586         float progress = mCurrentShift.value / mDragLengthFactor;
587         mLauncherTransitionController.setPlayFraction(progress);
588     }
589 
590     /**
591      * @param windowProgress 0 == app, 1 == overview
592      */
updateSysUiFlags(float windowProgress)593     private void updateSysUiFlags(float windowProgress) {
594         if (mRecentsAnimationController != null && mRecentsView != null) {
595             TaskView runningTask = mRecentsView.getRunningTaskView();
596             TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen();
597             int centermostTaskFlags = centermostTask == null ? 0
598                     : centermostTask.getThumbnail().getSysUiStatusNavFlags();
599             boolean swipeUpThresholdPassed = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
600             boolean quickswitchThresholdPassed = centermostTask != runningTask;
601 
602             // We will handle the sysui flags based on the centermost task view.
603             mRecentsAnimationController.setUseLauncherSystemBarFlags(swipeUpThresholdPassed
604                     ||  (quickswitchThresholdPassed && centermostTaskFlags != 0));
605             mRecentsAnimationController.setSplitScreenMinimized(swipeUpThresholdPassed);
606 
607             int sysuiFlags = swipeUpThresholdPassed ? 0 : centermostTaskFlags;
608             mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
609         }
610     }
611 
612     @Override
onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets)613     public void onRecentsAnimationStart(RecentsAnimationController controller,
614             RecentsAnimationTargets targets) {
615         ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
616         super.onRecentsAnimationStart(controller, targets);
617 
618         // Only add the callback to enable the input consumer after we actually have the controller
619         mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
620                 mRecentsAnimationController::enableInputConsumer);
621         mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
622 
623         mPassedOverviewThreshold = false;
624     }
625 
626     @Override
onRecentsAnimationCanceled(ThumbnailData thumbnailData)627     public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
628         ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
629         mActivityInitListener.unregister();
630         mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
631 
632         // Defer clearing the controller and the targets until after we've updated the state
633         super.onRecentsAnimationCanceled(thumbnailData);
634     }
635 
636     @Override
onGestureStarted(boolean isLikelyToStartNewTask)637     public void onGestureStarted(boolean isLikelyToStartNewTask) {
638         notifyGestureStartedAsync();
639         setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
640         mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
641         mGestureStarted = true;
642     }
643 
644     /**
645      * Notifies the launcher that the swipe gesture has started. This can be called multiple times.
646      */
647     @UiThread
notifyGestureStartedAsync()648     private void notifyGestureStartedAsync() {
649         final T curActivity = mActivity;
650         if (curActivity != null) {
651             // Once the gesture starts, we can no longer transition home through the button, so
652             // reset the force override of the activity visibility
653             mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
654         }
655     }
656 
657     /**
658      * Called as a result on ACTION_CANCEL to return the UI to the start state.
659      */
660     @Override
onGestureCancelled()661     public void onGestureCancelled() {
662         updateDisplacement(0);
663         mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
664         mLogAction = Touch.SWIPE_NOOP;
665         handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */);
666     }
667 
668     /**
669      * @param endVelocity The velocity in the direction of the nav bar to the middle of the screen.
670      * @param velocity The x and y components of the velocity when the gesture ends.
671      * @param downPos The x and y value of where the gesture started.
672      */
673     @Override
onGestureEnded(float endVelocity, PointF velocity, PointF downPos)674     public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
675         float flingThreshold = mContext.getResources()
676                 .getDimension(R.dimen.quickstep_fling_threshold_velocity);
677         boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
678         mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
679 
680         mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
681         boolean isVelocityVertical = Math.abs(velocity.y) > Math.abs(velocity.x);
682         if (isVelocityVertical) {
683             mLogDirection = velocity.y < 0 ? Direction.UP : Direction.DOWN;
684         } else {
685             mLogDirection = velocity.x < 0 ? Direction.LEFT : Direction.RIGHT;
686         }
687         mDownPos = downPos;
688         handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
689     }
690 
691     @Override
692     protected InputConsumer createNewInputProxyHandler() {
693         endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
694         endLauncherTransitionController();
695 
696         StatefulActivity activity = mActivityInterface.getCreatedActivity();
697         return activity == null ? InputConsumer.NO_OP
698                 : new OverviewInputConsumer(mGestureState, activity, null, true);
699     }
700 
701     private void endRunningWindowAnim(boolean cancel) {
702         if (mRunningWindowAnim != null) {
703             if (cancel) {
704                 mRunningWindowAnim.cancel();
705             } else {
706                 mRunningWindowAnim.end();
707             }
708         }
709     }
710 
711     private void onSettledOnEndTarget() {
712         switch (mGestureState.getEndTarget()) {
713             case HOME:
714                 mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
715                 // Notify swipe-to-home (recents animation) is finished
716                 SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
717                 break;
718             case RECENTS:
719                 mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
720                         | STATE_SCREENSHOT_VIEW_SHOWN);
721                 break;
722             case NEW_TASK:
723                 mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT);
724                 break;
725             case LAST_TASK:
726                 mStateCallback.setState(STATE_RESUME_LAST_TASK);
727                 break;
728         }
729         ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + mGestureState.getEndTarget());
730     }
731 
732     @Override
733     protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
734         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
735             return false;
736         }
737         if (mStateCallback.hasStates(STATE_START_NEW_TASK)
738                 && appearedTaskTarget.taskId == mGestureState.getLastStartedTaskId()) {
739             reset();
740             return true;
741         }
742         return false;
743     }
744 
745     private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
746             boolean isCancel) {
747         final GestureEndTarget endTarget;
748         final boolean goingToNewTask;
749         if (mRecentsView != null) {
750             if (!hasTargets()) {
751                 // If there are no running tasks, then we can assume that this is a continuation of
752                 // the last gesture, but after the recents animation has finished
753                 goingToNewTask = true;
754             } else {
755                 final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
756                 final int taskToLaunch = mRecentsView.getNextPage();
757                 goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
758             }
759         } else {
760             goingToNewTask = false;
761         }
762         final boolean reachedOverviewThreshold = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
763         if (!isFling) {
764             if (isCancel) {
765                 endTarget = LAST_TASK;
766             } else if (mDeviceState.isFullyGesturalNavMode()) {
767                 if (mIsShelfPeeking) {
768                     endTarget = RECENTS;
769                 } else if (goingToNewTask) {
770                     endTarget = NEW_TASK;
771                 } else {
772                     endTarget = !reachedOverviewThreshold ? LAST_TASK : HOME;
773                 }
774             } else {
775                 endTarget = reachedOverviewThreshold && mGestureStarted
776                         ? RECENTS
777                         : goingToNewTask
778                                 ? NEW_TASK
779                                 : LAST_TASK;
780             }
781         } else {
782             // If swiping at a diagonal, base end target on the faster velocity.
783             boolean isSwipeUp = endVelocity < 0;
784             boolean willGoToNewTaskOnSwipeUp =
785                     goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity);
786 
787             if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
788                 endTarget = HOME;
789             } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !mIsShelfPeeking) {
790                 // If swiping at a diagonal, base end target on the faster velocity.
791                 endTarget = NEW_TASK;
792             } else if (isSwipeUp) {
793                 endTarget = !reachedOverviewThreshold && willGoToNewTaskOnSwipeUp
794                         ? NEW_TASK : RECENTS;
795             } else {
796                 endTarget = goingToNewTask ? NEW_TASK : LAST_TASK;
797             }
798         }
799 
800         if (mDeviceState.isOverviewDisabled() && (endTarget == RECENTS || endTarget == LAST_TASK)) {
801             return LAST_TASK;
802         }
803         return endTarget;
804     }
805 
806     @UiThread
handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity, boolean isCancel)807     private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity,
808             boolean isCancel) {
809         PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000);
810         long duration = MAX_SWIPE_DURATION;
811         float currentShift = mCurrentShift.value;
812         final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
813                 isFling, isCancel);
814         float endShift = endTarget.isLauncher ? 1 : 0;
815         final float startShift;
816         Interpolator interpolator = DEACCEL;
817         if (!isFling) {
818             long expectedDuration = Math.abs(Math.round((endShift - currentShift)
819                     * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
820             duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
821             startShift = currentShift;
822             interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL;
823         } else {
824             startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
825                     * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
826             float minFlingVelocity = mContext.getResources()
827                     .getDimension(R.dimen.quickstep_fling_min_velocity);
828             if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
829                 if (endTarget == RECENTS && !mDeviceState.isFullyGesturalNavMode()) {
830                     Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
831                             startShift, endShift, endShift, endVelocity / 1000,
832                             mTransitionDragLength, mContext);
833                     endShift = overshoot.end;
834                     interpolator = overshoot.interpolator;
835                     duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
836                             MAX_SWIPE_DURATION);
837                 } else {
838                     float distanceToTravel = (endShift - currentShift) * mTransitionDragLength;
839 
840                     // we want the page's snap velocity to approximately match the velocity at
841                     // which the user flings, so we scale the duration by a value near to the
842                     // derivative of the scroll interpolator at zero, ie. 2.
843                     long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
844                     duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
845 
846                     if (endTarget == RECENTS) {
847                         interpolator = OVERSHOOT_1_2;
848                     }
849                 }
850             }
851         }
852 
853         if (endTarget.isLauncher && mRecentsAnimationController != null) {
854             mRecentsAnimationController.enableInputProxy(mInputConsumer,
855                     this::createNewInputProxyHandler);
856         }
857 
858         if (endTarget == HOME) {
859             setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
860             duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
861         } else if (endTarget == RECENTS) {
862             LiveTileOverlay.INSTANCE.startIconAnimation();
863             if (mRecentsView != null) {
864                 int nearestPage = mRecentsView.getPageNearestToCenterOfScreen();
865                 if (mRecentsView.getNextPage() != nearestPage) {
866                     // We shouldn't really scroll to the next page when swiping up to recents.
867                     // Only allow settling on the next page if it's nearest to the center.
868                     mRecentsView.snapToPage(nearestPage, Math.toIntExact(duration));
869                 }
870                 if (mRecentsView.getScroller().getDuration() > MAX_SWIPE_DURATION) {
871                     mRecentsView.snapToPage(mRecentsView.getNextPage(), (int) MAX_SWIPE_DURATION);
872                 }
873                 duration = Math.max(duration, mRecentsView.getScroller().getDuration());
874             }
875             if (mDeviceState.isFullyGesturalNavMode()) {
876                 setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration);
877             }
878         }
879 
880         // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
881         // or resumeLastTask().
882         if (mRecentsView != null) {
883             mRecentsView.setOnPageTransitionEndCallback(
884                     () -> mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED));
885         } else {
886             mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
887         }
888 
889         animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
890     }
891 
doLogGesture(GestureEndTarget endTarget)892     private void doLogGesture(GestureEndTarget endTarget) {
893         DeviceProfile dp = mDp;
894         if (dp == null || mDownPos == null) {
895             // We probably never received an animation controller, skip logging.
896             return;
897         }
898 
899         int pageIndex = endTarget == LAST_TASK
900                 ? LOG_NO_OP_PAGE_INDEX
901                 : mRecentsView.getNextPage();
902         UserEventDispatcher.newInstance(mContext).logStateChangeAction(
903                 mLogAction, mLogDirection,
904                 (int) mDownPos.x, (int) mDownPos.y,
905                 ContainerType.NAVBAR, ContainerType.APP,
906                 endTarget.containerType,
907                 pageIndex);
908         StatsLogManager.EventEnum event;
909         switch (endTarget) {
910             case HOME:
911                 event = LAUNCHER_HOME_GESTURE;
912                 break;
913             case RECENTS:
914                 event = LAUNCHER_OVERVIEW_GESTURE;
915                 break;
916             case LAST_TASK:
917             case NEW_TASK:
918                 event = (mLogDirection == Direction.LEFT)
919                         ? LAUNCHER_QUICKSWITCH_LEFT
920                         : LAUNCHER_QUICKSWITCH_RIGHT;
921                 break;
922             default:
923                 event = IGNORE;
924         }
925         StatsLogManager.newInstance(mContext).logger()
926                 .withSrcState(LAUNCHER_STATE_BACKGROUND)
927                 .withDstState(StatsLogManager.containerTypeToAtomState(endTarget.containerType))
928                 .log(event);
929     }
930 
931     /** Animates to the given progress, where 0 is the current app and 1 is overview. */
932     @UiThread
animateToProgress(float start, float end, long duration, Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs)933     private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
934             GestureEndTarget target, PointF velocityPxPerMs) {
935         runOnRecentsAnimationStart(() -> animateToProgressInternal(start, end, duration,
936                 interpolator, target, velocityPxPerMs));
937     }
938 
939     protected abstract HomeAnimationFactory createHomeAnimationFactory(long duration);
940 
941     private TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
942         @Override
943         public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
944                 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
945             if (task.taskId == mGestureState.getRunningTaskId()
946                     && TaskInfoCompat.getActivityType(task) != ACTIVITY_TYPE_HOME) {
947                 // Since this is an edge case, just cancel and relaunch with default activity
948                 // options (since we don't know if there's an associated app icon to launch from)
949                 endRunningWindowAnim(true /* cancel */);
950                 ActivityManagerWrapper.getInstance().unregisterTaskStackListener(
951                         mActivityRestartListener);
952                 ActivityManagerWrapper.getInstance().startActivityFromRecents(task.taskId, null);
953             }
954         }
955     };
956 
957     @UiThread
animateToProgressInternal(float start, float end, long duration, Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs)958     private void animateToProgressInternal(float start, float end, long duration,
959             Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
960         // Set the state, but don't notify until the animation completes
961         mGestureState.setEndTarget(target, false /* isAtomic */);
962         maybeUpdateRecentsAttachedState();
963 
964         // If we are transitioning to launcher, then listen for the activity to be restarted while
965         // the transition is in progress
966         if (mGestureState.getEndTarget().isLauncher) {
967             ActivityManagerWrapper.getInstance().registerTaskStackListener(
968                     mActivityRestartListener);
969         }
970 
971         if (mGestureState.getEndTarget() == HOME) {
972             HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
973             RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
974             windowAnim.addAnimatorListener(new AnimationSuccessListener() {
975                 @Override
976                 public void onAnimationSuccess(Animator animator) {
977                     if (mRecentsAnimationController == null) {
978                         // If the recents animation is interrupted, we still end the running
979                         // animation (not canceled) so this is still called. In that case, we can
980                         // skip doing any future work here for the current gesture.
981                         return;
982                     }
983                     // Finalize the state and notify of the change
984                     mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
985                 }
986             });
987             getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
988             windowAnim.start(mContext, velocityPxPerMs);
989             homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
990             mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
991             mLauncherTransitionController = null;
992         } else {
993             ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);
994             windowAnim.setDuration(duration).setInterpolator(interpolator);
995             windowAnim.addUpdateListener(valueAnimator -> {
996                 computeRecentsScrollIfInvisible();
997             });
998             windowAnim.addListener(new AnimationSuccessListener() {
999                 @Override
1000                 public void onAnimationSuccess(Animator animator) {
1001                     if (mRecentsAnimationController == null) {
1002                         // If the recents animation is interrupted, we still end the running
1003                         // animation (not canceled) so this is still called. In that case, we can
1004                         // skip doing any future work here for the current gesture.
1005                         return;
1006                     }
1007                     if (mRecentsView != null) {
1008                         int taskToLaunch = mRecentsView.getNextPage();
1009                         int runningTask = getLastAppearedTaskIndex();
1010                         boolean hasStartedNewTask = hasStartedNewTask();
1011                         if (target == NEW_TASK && taskToLaunch == runningTask
1012                                 && !hasStartedNewTask) {
1013                             // We are about to launch the current running task, so use LAST_TASK
1014                             // state instead of NEW_TASK. This could happen, for example, if our
1015                             // scroll is aborted after we determined the target to be NEW_TASK.
1016                             mGestureState.setEndTarget(LAST_TASK);
1017                         } else if (target == LAST_TASK && hasStartedNewTask) {
1018                             // We are about to re-launch the previously running task, but we can't
1019                             // just finish the controller like we normally would because that would
1020                             // instead resume the last task that appeared, and not ensure that this
1021                             // task is restored to the top. To address this, re-launch the task as
1022                             // if it were a new task.
1023                             mGestureState.setEndTarget(NEW_TASK);
1024                         }
1025                     }
1026                     mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
1027                 }
1028             });
1029             windowAnim.start();
1030             mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
1031         }
1032         // Always play the entire launcher animation when going home, since it is separate from
1033         // the animation that has been controlled thus far.
1034         if (mGestureState.getEndTarget() == HOME) {
1035             start = 0;
1036         }
1037 
1038         // We want to use the same interpolator as the window, but need to adjust it to
1039         // interpolate over the remaining progress (end - start).
1040         TimeInterpolator adjustedInterpolator = Interpolators.mapToProgress(
1041                 interpolator, start, end);
1042         if (mLauncherTransitionController == null) {
1043             return;
1044         }
1045         if (start == end || duration <= 0) {
1046             mLauncherTransitionController.dispatchSetInterpolator(t -> end);
1047         } else {
1048             mLauncherTransitionController.dispatchSetInterpolator(adjustedInterpolator);
1049         }
1050         mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration));
1051 
1052         if (UNSTABLE_SPRINGS.get()) {
1053             mLauncherTransitionController.dispatchOnStart();
1054         }
1055         mLauncherTransitionController.getAnimationPlayer().start();
1056         mHasLauncherTransitionControllerStarted = true;
1057     }
1058 
computeRecentsScrollIfInvisible()1059     private void computeRecentsScrollIfInvisible() {
1060         if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
1061             // Views typically don't compute scroll when invisible as an optimization,
1062             // but in our case we need to since the window offset depends on the scroll.
1063             mRecentsView.computeScroll();
1064         }
1065     }
1066 
continueComputingRecentsScrollIfNecessary()1067     private void continueComputingRecentsScrollIfNecessary() {
1068         if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)
1069                 && !mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)
1070                 && !mCanceled) {
1071             computeRecentsScrollIfInvisible();
1072             mRecentsView.postOnAnimation(this::continueComputingRecentsScrollIfNecessary);
1073         }
1074     }
1075 
1076     /**
1077      * Creates an animation that transforms the current app window into the home app.
1078      * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
1079      * @param homeAnimationFactory The home animation factory.
1080      */
1081     @Override
createWindowAnimationToHome(float startProgress, HomeAnimationFactory homeAnimationFactory)1082     protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
1083             HomeAnimationFactory homeAnimationFactory) {
1084         RectFSpringAnim anim =
1085                 super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
1086         anim.addOnUpdateListener((r, p) -> {
1087             updateSysUiFlags(Math.max(p, mCurrentShift.value));
1088         });
1089         anim.addAnimatorListener(new AnimationSuccessListener() {
1090             @Override
1091             public void onAnimationStart(Animator animation) {
1092                 if (mActivity != null) {
1093                     removeLiveTileOverlay();
1094                 }
1095             }
1096 
1097             @Override
1098             public void onAnimationSuccess(Animator animator) {
1099                 if (mRecentsView != null) {
1100                     mRecentsView.post(mRecentsView::resetTaskVisuals);
1101                 }
1102                 // Make sure recents is in its final state
1103                 maybeUpdateRecentsAttachedState(false);
1104                 mActivityInterface.onSwipeUpToHomeComplete(mDeviceState);
1105             }
1106         });
1107         return anim;
1108     }
1109 
1110     @Override
onConsumerAboutToBeSwitched()1111     public void onConsumerAboutToBeSwitched() {
1112         if (mActivity != null) {
1113             // In the off chance that the gesture ends before Launcher is started, we should clear
1114             // the callback here so that it doesn't update with the wrong state
1115             mActivity.clearRunOnceOnStartCallback();
1116             resetLauncherListenersAndOverlays();
1117         }
1118         if (mGestureState.getEndTarget() != null && !mGestureState.isRunningAnimationToLauncher()) {
1119             cancelCurrentAnimation();
1120         } else {
1121             reset();
1122         }
1123     }
1124 
isCanceled()1125     public boolean isCanceled() {
1126         return mCanceled;
1127     }
1128 
1129     @UiThread
resumeLastTask()1130     private void resumeLastTask() {
1131         mRecentsAnimationController.finish(false /* toRecents */, null);
1132         ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
1133         doLogGesture(LAST_TASK);
1134         reset();
1135     }
1136 
1137     @UiThread
startNewTask()1138     private void startNewTask() {
1139         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
1140             mRecentsAnimationController.finish(true /* toRecents */, this::startNewTaskInternal);
1141         } else {
1142             startNewTaskInternal();
1143         }
1144     }
1145 
1146     @UiThread
startNewTaskInternal()1147     private void startNewTaskInternal() {
1148         startNewTask(success -> {
1149             if (!success) {
1150                 reset();
1151                 // We couldn't launch the task, so take user to overview so they can
1152                 // decide what to do instead of staying in this broken state.
1153                 endLauncherTransitionController();
1154                 updateSysUiFlags(1 /* windowProgress == overview */);
1155             }
1156             doLogGesture(NEW_TASK);
1157         });
1158     }
1159 
1160     @Override
onRestartPreviouslyAppearedTask()1161     protected void onRestartPreviouslyAppearedTask() {
1162         super.onRestartPreviouslyAppearedTask();
1163         reset();
1164     }
1165 
reset()1166     private void reset() {
1167         mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
1168     }
1169 
1170     /**
1171      * Cancels any running animation so that the active target can be overriden by a new swipe
1172      * handle (in case of quick switch).
1173      */
cancelCurrentAnimation()1174     private void cancelCurrentAnimation() {
1175         mCanceled = true;
1176         mCurrentShift.cancelAnimation();
1177         if (mLauncherTransitionController != null && mLauncherTransitionController
1178                 .getAnimationPlayer().isStarted()) {
1179             mLauncherTransitionController.getAnimationPlayer().cancel();
1180         }
1181     }
1182 
invalidateHandler()1183     private void invalidateHandler() {
1184         endRunningWindowAnim(false /* cancel */);
1185 
1186         if (mGestureEndCallback != null) {
1187             mGestureEndCallback.run();
1188         }
1189 
1190         mActivityInitListener.unregister();
1191         ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mActivityRestartListener);
1192         mTaskSnapshot = null;
1193     }
1194 
invalidateHandlerWithLauncher()1195     private void invalidateHandlerWithLauncher() {
1196         endLauncherTransitionController();
1197 
1198         mRecentsView.onGestureAnimationEnd();
1199         resetLauncherListenersAndOverlays();
1200     }
1201 
endLauncherTransitionController()1202     private void endLauncherTransitionController() {
1203         setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
1204         if (mLauncherTransitionController != null) {
1205             mLauncherTransitionController.getAnimationPlayer().end();
1206             mLauncherTransitionController = null;
1207         }
1208     }
1209 
resetLauncherListenersAndOverlays()1210     private void resetLauncherListenersAndOverlays() {
1211         // Reset the callback for deferred activity launches
1212         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
1213             mActivityInterface.setOnDeferredActivityLaunchCallback(null);
1214         }
1215         mActivity.getRootView().setOnApplyWindowInsetsListener(null);
1216         removeLiveTileOverlay();
1217     }
1218 
notifyTransitionCancelled()1219     private void notifyTransitionCancelled() {
1220         mAnimationFactory.onTransitionCancelled();
1221     }
1222 
resetStateForAnimationCancel()1223     private void resetStateForAnimationCancel() {
1224         boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
1225         mActivityInterface.onTransitionCancelled(wasVisible);
1226 
1227         // Leave the pending invisible flag, as it may be used by wallpaper open animation.
1228         mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
1229     }
1230 
switchToScreenshot()1231     protected void switchToScreenshot() {
1232         final int runningTaskId = mGestureState.getRunningTaskId();
1233         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
1234             if (mRecentsAnimationController != null) {
1235                 mRecentsAnimationController.getController().setWillFinishToHome(true);
1236                 // Update the screenshot of the task
1237                 if (mTaskSnapshot == null) {
1238                     mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
1239                 }
1240                 mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot, false /* refreshNow */);
1241             }
1242             mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
1243         } else if (!hasTargets()) {
1244             // If there are no targets, then we don't need to capture anything
1245             mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
1246         } else {
1247             boolean finishTransitionPosted = false;
1248             if (mRecentsAnimationController != null) {
1249                 // Update the screenshot of the task
1250                 if (mTaskSnapshot == null) {
1251                     mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
1252                 }
1253                 final TaskView taskView;
1254                 if (mGestureState.getEndTarget() == HOME) {
1255                     // Capture the screenshot before finishing the transition to home to ensure it's
1256                     // taken in the correct orientation, but no need to update the thumbnail.
1257                     taskView = null;
1258                 } else {
1259                     taskView = mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot);
1260                 }
1261                 if (taskView != null && !mCanceled) {
1262                     // Defer finishing the animation until the next launcher frame with the
1263                     // new thumbnail
1264                     finishTransitionPosted = ViewUtils.postDraw(taskView,
1265                             () -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
1266                                     this::isCanceled);
1267                 }
1268             }
1269             if (!finishTransitionPosted) {
1270                 // If we haven't posted a draw callback, set the state immediately.
1271                 Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT,
1272                         TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS);
1273                 mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
1274                 TraceHelper.INSTANCE.endSection(traceToken);
1275             }
1276         }
1277     }
1278 
finishCurrentTransitionToRecents()1279     private void finishCurrentTransitionToRecents() {
1280         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
1281             mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
1282         } else if (!hasTargets() || mRecentsAnimationController == null) {
1283             // If there are no targets or the animation not started, then there is nothing to finish
1284             mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
1285         } else {
1286             mRecentsAnimationController.finish(true /* toRecents */,
1287                     () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
1288         }
1289         ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
1290     }
1291 
finishCurrentTransitionToHome()1292     private void finishCurrentTransitionToHome() {
1293         if (!hasTargets() || mRecentsAnimationController == null) {
1294             // If there are no targets or the animation not started, then there is nothing to finish
1295             mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
1296         } else {
1297             finishRecentsControllerToHome(
1298                     () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
1299         }
1300         ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
1301         doLogGesture(HOME);
1302     }
1303 
1304     protected abstract void finishRecentsControllerToHome(Runnable callback);
1305 
setupLauncherUiAfterSwipeUpToRecentsAnimation()1306     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
1307         endLauncherTransitionController();
1308         mActivityInterface.onSwipeUpToRecentsComplete();
1309         if (mRecentsAnimationController != null) {
1310             mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
1311                     true /* screenshot */);
1312         }
1313         mRecentsView.onSwipeUpAnimationSuccess();
1314 
1315         SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
1316         doLogGesture(RECENTS);
1317         reset();
1318     }
1319 
addLiveTileOverlay()1320     private void addLiveTileOverlay() {
1321         if (LiveTileOverlay.INSTANCE.attach(mActivity.getRootView().getOverlay())) {
1322             mRecentsView.setLiveTileOverlayAttached(true);
1323         }
1324     }
1325 
removeLiveTileOverlay()1326     private void removeLiveTileOverlay() {
1327         LiveTileOverlay.INSTANCE.detach(mActivity.getRootView().getOverlay());
1328         mRecentsView.setLiveTileOverlayAttached(false);
1329     }
1330 
isNotInRecents(RemoteAnimationTargetCompat app)1331     private static boolean isNotInRecents(RemoteAnimationTargetCompat app) {
1332         return app.isNotInRecents
1333                 || app.activityType == ACTIVITY_TYPE_HOME;
1334     }
1335 }
1336