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