• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.quickstep;
17 
18 import static 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.Utilities.SINGLE_FRAME_MS;
21 import static com.android.launcher3.Utilities.postAsyncCallback;
22 import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
23 import static com.android.launcher3.anim.Interpolators.DEACCEL;
24 import static com.android.launcher3.anim.Interpolators.LINEAR;
25 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
26 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
27 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
28 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
29 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
30 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
31 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
32 import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.HIDE;
33 import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.PEEK;
34 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
35 import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
36 import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
37 import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.HOME;
38 import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.LAST_TASK;
39 import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.NEW_TASK;
40 import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.RECENTS;
41 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
42 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
43 
44 import android.animation.Animator;
45 import android.animation.AnimatorSet;
46 import android.animation.ObjectAnimator;
47 import android.animation.TimeInterpolator;
48 import android.animation.ValueAnimator;
49 import android.annotation.TargetApi;
50 import android.app.ActivityManager.RunningTaskInfo;
51 import android.content.Context;
52 import android.graphics.Canvas;
53 import android.graphics.Point;
54 import android.graphics.PointF;
55 import android.graphics.Rect;
56 import android.graphics.RectF;
57 import android.os.Build;
58 import android.os.Handler;
59 import android.os.Looper;
60 import android.os.SystemClock;
61 import android.util.Log;
62 import android.view.HapticFeedbackConstants;
63 import android.view.MotionEvent;
64 import android.view.View;
65 import android.view.View.OnApplyWindowInsetsListener;
66 import android.view.ViewTreeObserver.OnDrawListener;
67 import android.view.WindowInsets;
68 import android.view.WindowManager;
69 import android.view.animation.Interpolator;
70 
71 import androidx.annotation.NonNull;
72 import androidx.annotation.Nullable;
73 import androidx.annotation.UiThread;
74 
75 import com.android.launcher3.AbstractFloatingView;
76 import com.android.launcher3.BaseDraggingActivity;
77 import com.android.launcher3.DeviceProfile;
78 import com.android.launcher3.InvariantDeviceProfile;
79 import com.android.launcher3.R;
80 import com.android.launcher3.Utilities;
81 import com.android.launcher3.anim.AnimationSuccessListener;
82 import com.android.launcher3.anim.AnimatorPlaybackController;
83 import com.android.launcher3.anim.Interpolators;
84 import com.android.launcher3.graphics.RotationMode;
85 import com.android.launcher3.logging.UserEventDispatcher;
86 import com.android.launcher3.testing.TestProtocol;
87 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
88 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
89 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
90 import com.android.launcher3.util.RaceConditionTracker;
91 import com.android.launcher3.util.TraceHelper;
92 import com.android.launcher3.views.FloatingIconView;
93 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
94 import com.android.quickstep.ActivityControlHelper.AnimationFactory;
95 import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
96 import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
97 import com.android.quickstep.SysUINavigationMode.Mode;
98 import com.android.quickstep.inputconsumers.InputConsumer;
99 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
100 import com.android.quickstep.util.ClipAnimationHelper;
101 import com.android.quickstep.util.RectFSpringAnim;
102 import com.android.quickstep.util.RemoteAnimationTargetSet;
103 import com.android.quickstep.util.SwipeAnimationTargetSet;
104 import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
105 import com.android.quickstep.views.LiveTileOverlay;
106 import com.android.quickstep.views.RecentsView;
107 import com.android.quickstep.views.TaskView;
108 import com.android.systemui.shared.recents.model.ThumbnailData;
109 import com.android.systemui.shared.system.InputConsumerController;
110 import com.android.systemui.shared.system.LatencyTrackerCompat;
111 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
112 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
113 import com.android.systemui.shared.system.WindowCallbacksCompat;
114 
115 import java.util.function.BiFunction;
116 import java.util.function.Consumer;
117 
118 @TargetApi(Build.VERSION_CODES.O)
119 public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
120         implements SwipeAnimationListener, OnApplyWindowInsetsListener {
121     private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
122 
123     private static final Rect TEMP_RECT = new Rect();
124 
125     private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
126 
getFlagForIndex(int index, String name)127     private static int getFlagForIndex(int index, String name) {
128         if (DEBUG_STATES) {
129             STATE_NAMES[index] = name;
130         }
131         return 1 << index;
132     }
133 
134     // Launcher UI related states
135     private static final int STATE_LAUNCHER_PRESENT = getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
136     private static final int STATE_LAUNCHER_STARTED = getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
137     private static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
138 
139     // Internal initialization states
140     private static final int STATE_APP_CONTROLLER_RECEIVED =
141             getFlagForIndex(3, "STATE_APP_CONTROLLER_RECEIVED");
142 
143     // Interaction finish states
144     private static final int STATE_SCALED_CONTROLLER_HOME =
145             getFlagForIndex(4, "STATE_SCALED_CONTROLLER_HOME");
146     private static final int STATE_SCALED_CONTROLLER_RECENTS =
147             getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
148 
149     private static final int STATE_HANDLER_INVALIDATED =
150             getFlagForIndex(6, "STATE_HANDLER_INVALIDATED");
151     private static final int STATE_GESTURE_STARTED =
152             getFlagForIndex(7, "STATE_GESTURE_STARTED");
153     private static final int STATE_GESTURE_CANCELLED =
154             getFlagForIndex(8, "STATE_GESTURE_CANCELLED");
155     private static final int STATE_GESTURE_COMPLETED =
156             getFlagForIndex(9, "STATE_GESTURE_COMPLETED");
157 
158     private static final int STATE_CAPTURE_SCREENSHOT =
159             getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT");
160     private static final int STATE_SCREENSHOT_CAPTURED =
161             getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED");
162     private static final int STATE_SCREENSHOT_VIEW_SHOWN =
163             getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN");
164 
165     private static final int STATE_RESUME_LAST_TASK =
166             getFlagForIndex(13, "STATE_RESUME_LAST_TASK");
167     private static final int STATE_START_NEW_TASK =
168             getFlagForIndex(14, "STATE_START_NEW_TASK");
169     private static final int STATE_CURRENT_TASK_FINISHED =
170             getFlagForIndex(15, "STATE_CURRENT_TASK_FINISHED");
171 
172     private static final int LAUNCHER_UI_STATES =
173             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
174 
175     public enum GestureEndTarget {
176         HOME(1, STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT, true, false,
177                 ContainerType.WORKSPACE, false),
178 
179         RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
180                 | STATE_SCREENSHOT_VIEW_SHOWN, true, false, ContainerType.TASKSWITCHER, true),
181 
182         NEW_TASK(0, STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT, false, true,
183                 ContainerType.APP, true),
184 
185         LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP, false);
186 
GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued, int containerType, boolean recentsAttachedToAppWindow)187         GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued,
188                 int containerType, boolean recentsAttachedToAppWindow) {
189             this.endShift = endShift;
190             this.endState = endState;
191             this.isLauncher = isLauncher;
192             this.canBeContinued = canBeContinued;
193             this.containerType = containerType;
194             this.recentsAttachedToAppWindow = recentsAttachedToAppWindow;
195         }
196 
197         /** 0 is app, 1 is overview */
198         public final float endShift;
199         /** The state to apply when we reach this final target */
200         public final int endState;
201         /** Whether the target is in the launcher activity */
202         public final boolean isLauncher;
203         /** Whether the user can start a new gesture while this one is finishing */
204         public final boolean canBeContinued;
205         /** Used to log where the user ended up after the gesture ends */
206         public final int containerType;
207         /** Whether RecentsView should be attached to the window as we animate to this target */
208         public final boolean recentsAttachedToAppWindow;
209     }
210 
211     public static final long MAX_SWIPE_DURATION = 350;
212     public static final long MIN_SWIPE_DURATION = 80;
213     public static final long MIN_OVERSHOOT_DURATION = 120;
214 
215     public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
216     private static final float SWIPE_DURATION_MULTIPLIER =
217             Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
218     private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured";
219 
220     private static final long SHELF_ANIM_DURATION = 240;
221     public static final long RECENTS_ATTACH_DURATION = 300;
222 
223     // Start resisting when swiping past this factor of mTransitionDragLength.
224     private static final float DRAG_LENGTH_FACTOR_START_PULLBACK = 1.4f;
225     // This is how far down we can scale down, where 0f is full screen and 1f is recents.
226     private static final float DRAG_LENGTH_FACTOR_MAX_PULLBACK = 1.8f;
227     private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
228 
229     /**
230      * Used as the page index for logging when we return to the last task at the end of the gesture.
231      */
232     private static final int LOG_NO_OP_PAGE_INDEX = -1;
233 
234     private final ClipAnimationHelper mClipAnimationHelper;
235     private final ClipAnimationHelper.TransformParams mTransformParams;
236 
237     private Runnable mGestureEndCallback;
238     private GestureEndTarget mGestureEndTarget;
239     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
240     private RunningWindowAnim mRunningWindowAnim;
241     private boolean mIsShelfPeeking;
242     private DeviceProfile mDp;
243     // The distance needed to drag to reach the task size in recents.
244     private int mTransitionDragLength;
245     // How much further we can drag past recents, as a factor of mTransitionDragLength.
246     private float mDragLengthFactor = 1;
247 
248     // Shift in the range of [0, 1].
249     // 0 => preview snapShot is completely visible, and hotseat is completely translated down
250     // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
251     // visible.
252     private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
253     private boolean mContinuingLastGesture;
254     // To avoid UI jump when gesture is started, we offset the animation by the threshold.
255     private float mShiftAtGestureStart = 0;
256 
257     private final Handler mMainThreadHandler = MAIN_THREAD_EXECUTOR.getHandler();
258 
259     private final Context mContext;
260     private final ActivityControlHelper<T> mActivityControlHelper;
261     private final ActivityInitListener mActivityInitListener;
262 
263     private final SysUINavigationMode.Mode mMode;
264 
265     private final int mRunningTaskId;
266     private ThumbnailData mTaskSnapshot;
267 
268     private MultiStateCallback mStateCallback;
269     // Used to control launcher components throughout the swipe gesture.
270     private AnimatorPlaybackController mLauncherTransitionController;
271     private boolean mHasLauncherTransitionControllerStarted;
272 
273     private T mActivity;
274     private RecentsView mRecentsView;
275     private AnimationFactory mAnimationFactory = (t) -> { };
276     private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay();
277 
278     private boolean mCanceled;
279     private boolean mWasLauncherAlreadyVisible;
280     private int mFinishingRecentsAnimationForNewTaskId = -1;
281 
282     private boolean mPassedOverviewThreshold;
283     private boolean mGestureStarted;
284     private int mLogAction = Touch.SWIPE;
285     private int mLogDirection = Direction.UP;
286     private PointF mDownPos;
287     private boolean mIsLikelyToStartNewTask;
288 
289     private final RecentsAnimationWrapper mRecentsAnimationWrapper;
290 
291     private final long mTouchTimeMs;
292     private long mLauncherFrameDrawnTime;
293 
WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture, InputConsumerController inputConsumer)294     public WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
295             long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
296             InputConsumerController inputConsumer) {
297         mContext = context;
298         mRunningTaskId = runningTaskInfo.id;
299         mTouchTimeMs = touchTimeMs;
300         mActivityControlHelper = controller;
301         mActivityInitListener = mActivityControlHelper
302                 .createActivityInitListener(this::onActivityInit);
303         mContinuingLastGesture = continuingLastGesture;
304         mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
305                 this::createNewInputProxyHandler);
306         mClipAnimationHelper = new ClipAnimationHelper(context);
307         mTransformParams = new ClipAnimationHelper.TransformParams();
308 
309         mMode = SysUINavigationMode.getMode(context);
310         initStateCallbacks();
311 
312         DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
313         initTransitionEndpoints(dp);
314     }
315 
initStateCallbacks()316     private void initStateCallbacks() {
317         mStateCallback = new MultiStateCallback(STATE_NAMES);
318 
319         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
320                 this::onLauncherPresentAndGestureStarted);
321 
322         mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
323                 this::initializeLauncherAnimationController);
324 
325         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
326                 this::launcherFrameDrawn);
327 
328         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
329                         | STATE_GESTURE_CANCELLED,
330                 this::resetStateForAnimationCancel);
331 
332         mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
333                 this::sendRemoteAnimationsToAnimationFactory);
334 
335         mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
336                 this::resumeLastTask);
337         mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
338                 this::startNewTask);
339 
340         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
341                         | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT,
342                 this::switchToScreenshot);
343 
344         mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
345                         | STATE_SCALED_CONTROLLER_RECENTS,
346                 this::finishCurrentTransitionToRecents);
347 
348         mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
349                         | STATE_SCALED_CONTROLLER_HOME,
350                 this::finishCurrentTransitionToHome);
351         mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
352                 this::reset);
353 
354         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
355                         | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
356                         | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
357                         | STATE_GESTURE_STARTED,
358                 this::setupLauncherUiAfterSwipeUpToRecentsAnimation);
359 
360         mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
361         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
362                 this::invalidateHandlerWithLauncher);
363         mStateCallback.addCallback(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
364                 this::notifyTransitionCancelled);
365 
366         mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
367                 mRecentsAnimationWrapper::enableInputConsumer);
368 
369         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
370             mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
371                             | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
372                     (b) -> mRecentsView.setRunningTaskHidden(!b));
373         }
374     }
375 
setStateOnUiThread(int stateFlag)376     private void setStateOnUiThread(int stateFlag) {
377         if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
378             mStateCallback.setState(stateFlag);
379         } else {
380             postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag));
381         }
382     }
383 
initTransitionEndpoints(DeviceProfile dp)384     private void initTransitionEndpoints(DeviceProfile dp) {
385         mDp = dp;
386 
387         Rect tempRect = new Rect();
388         mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
389                 dp, mContext, tempRect);
390         mClipAnimationHelper.updateTargetRect(tempRect);
391         if (mMode == Mode.NO_BUTTON) {
392             // We can drag all the way to the top of the screen.
393             mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
394         }
395     }
396 
getFadeInDuration()397     private long getFadeInDuration() {
398         if (mCurrentShift.getCurrentAnimation() != null) {
399             ObjectAnimator anim = mCurrentShift.getCurrentAnimation();
400             long theirDuration = anim.getDuration() - anim.getCurrentPlayTime();
401 
402             // TODO: Find a better heuristic
403             return Math.min(MAX_SWIPE_DURATION, Math.max(theirDuration, MIN_SWIPE_DURATION));
404         } else {
405             return MAX_SWIPE_DURATION;
406         }
407     }
408 
initWhenReady()409     public void initWhenReady() {
410         mActivityInitListener.register();
411     }
412 
onActivityInit(final T activity, Boolean alreadyOnHome)413     private boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
414         if (mActivity == activity) {
415             return true;
416         }
417         if (mActivity != null) {
418             // The launcher may have been recreated as a result of device rotation.
419             int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
420             initStateCallbacks();
421             mStateCallback.setState(oldState);
422         }
423         mWasLauncherAlreadyVisible = alreadyOnHome;
424         mActivity = activity;
425         // Override the visibility of the activity until the gesture actually starts and we swipe
426         // up, or until we transition home and the home animation is composed
427         if (alreadyOnHome) {
428             mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
429         } else {
430             mActivity.addForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
431         }
432 
433         mRecentsView = activity.getOverviewPanel();
434         SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> {
435             mTransformParams.setSyncTransactionApplier(applier);
436             mRecentsAnimationWrapper.runOnInit(() ->
437                     mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
438             });
439 
440         mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
441             if (mGestureEndTarget != HOME) {
442                 updateFinalShift();
443             }
444         });
445         mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
446         mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
447         mRecentsView.setLiveTileOverlay(mLiveTileOverlay);
448         mActivity.getRootView().getOverlay().add(mLiveTileOverlay);
449 
450         mStateCallback.setState(STATE_LAUNCHER_PRESENT);
451         if (alreadyOnHome) {
452             onLauncherStart(activity);
453         } else {
454             activity.setOnStartCallback(this::onLauncherStart);
455         }
456 
457         setupRecentsViewUi();
458         return true;
459     }
460 
onLauncherStart(final T activity)461     private void onLauncherStart(final T activity) {
462         if (TestProtocol.sDebugTracing) {
463             Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart");
464         }
465         if (mActivity != activity) {
466             return;
467         }
468         if (TestProtocol.sDebugTracing) {
469             Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 1");
470         }
471         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
472             return;
473         }
474         if (TestProtocol.sDebugTracing) {
475             Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 2");
476         }
477 
478         // If we've already ended the gesture and are going home, don't prepare recents UI,
479         // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
480         if (mGestureEndTarget != HOME) {
481             if (TestProtocol.sDebugTracing) {
482                 Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 3");
483             }
484             Runnable initAnimFactory = () -> {
485                 if (TestProtocol.sDebugTracing) {
486                     Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 4");
487                 }
488                 mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
489                         mWasLauncherAlreadyVisible, true,
490                         this::onAnimatorPlaybackControllerCreated);
491                 maybeUpdateRecentsAttachedState(false /* animate */);
492             };
493             if (mWasLauncherAlreadyVisible) {
494                 // Launcher is visible, but might be about to stop. Thus, if we prepare recents
495                 // now, it might get overridden by moveToRestState() in onStop(). To avoid this,
496                 // wait until the next gesture (and possibly launcher) starts.
497                 if (TestProtocol.sDebugTracing) {
498                     Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 5");
499                 }
500                 mStateCallback.addCallback(STATE_GESTURE_STARTED, initAnimFactory);
501             } else {
502                 if (TestProtocol.sDebugTracing) {
503                     Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 6");
504                 }
505                 initAnimFactory.run();
506             }
507         }
508         AbstractFloatingView.closeAllOpenViewsExcept(activity, mWasLauncherAlreadyVisible,
509                 AbstractFloatingView.TYPE_LISTENER);
510 
511         if (mWasLauncherAlreadyVisible) {
512             mStateCallback.setState(STATE_LAUNCHER_DRAWN);
513         } else {
514             TraceHelper.beginSection("WTS-init");
515             View dragLayer = activity.getDragLayer();
516             dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
517 
518                 @Override
519                 public void onDraw() {
520                     TraceHelper.endSection("WTS-init", "Launcher frame is drawn");
521                     dragLayer.post(() ->
522                             dragLayer.getViewTreeObserver().removeOnDrawListener(this));
523                     if (activity != mActivity) {
524                         return;
525                     }
526 
527                     mStateCallback.setState(STATE_LAUNCHER_DRAWN);
528                 }
529             });
530         }
531 
532         activity.getRootView().setOnApplyWindowInsetsListener(this);
533         mStateCallback.setState(STATE_LAUNCHER_STARTED);
534     }
535 
onLauncherPresentAndGestureStarted()536     private void onLauncherPresentAndGestureStarted() {
537         // Re-setup the recents UI when gesture starts, as the state could have been changed during
538         // that time by a previous window transition.
539         setupRecentsViewUi();
540 
541         notifyGestureStartedAsync();
542     }
543 
setupRecentsViewUi()544     private void setupRecentsViewUi() {
545         if (mContinuingLastGesture) {
546             updateSysUiFlags(mCurrentShift.value);
547             return;
548         }
549         mRecentsView.onGestureAnimationStart(mRunningTaskId);
550     }
551 
launcherFrameDrawn()552     private void launcherFrameDrawn() {
553         mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
554     }
555 
sendRemoteAnimationsToAnimationFactory()556     private void sendRemoteAnimationsToAnimationFactory() {
557         mAnimationFactory.onRemoteAnimationReceived(mRecentsAnimationWrapper.targetSet);
558     }
559 
initializeLauncherAnimationController()560     private void initializeLauncherAnimationController() {
561         buildAnimationController();
562 
563         if (LatencyTrackerCompat.isEnabled(mContext)) {
564             LatencyTrackerCompat.logToggleRecents((int) (mLauncherFrameDrawnTime - mTouchTimeMs));
565         }
566 
567         // This method is only called when STATE_GESTURE_STARTED is set, so we can enable the
568         // high-res thumbnail loader here once we are sure that we will end up in an overview state
569         RecentsModel.INSTANCE.get(mContext).getThumbnailCache()
570                 .getHighResLoadingState().setVisible(true);
571     }
572 
getTaskCurveScaleForOffsetX(float offsetX, float taskWidth)573     private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) {
574         float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 +
575                 mContext.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
576         float interpolation = Math.min(1, offsetX / distanceToReachEdge);
577         return TaskView.getCurveScaleForInterpolation(interpolation);
578     }
579 
getRecentsViewDispatcher(RotationMode rotationMode)580     public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode rotationMode) {
581         return mRecentsView != null ? mRecentsView.getEventDispatcher(rotationMode) : null;
582     }
583 
584     @UiThread
updateDisplacement(float displacement)585     public void updateDisplacement(float displacement) {
586         // We are moving in the negative x/y direction
587         displacement = -displacement;
588         if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
589             mCurrentShift.updateValue(mDragLengthFactor);
590         } else {
591             float translation = Math.max(displacement, 0);
592             float shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
593             if (shift > DRAG_LENGTH_FACTOR_START_PULLBACK) {
594                 float pullbackProgress = Utilities.getProgress(shift,
595                         DRAG_LENGTH_FACTOR_START_PULLBACK, mDragLengthFactor);
596                 pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
597                 shift = DRAG_LENGTH_FACTOR_START_PULLBACK + pullbackProgress
598                         * (DRAG_LENGTH_FACTOR_MAX_PULLBACK - DRAG_LENGTH_FACTOR_START_PULLBACK);
599             }
600             mCurrentShift.updateValue(shift);
601         }
602     }
603 
onMotionPauseChanged(boolean isPaused)604     public void onMotionPauseChanged(boolean isPaused) {
605         setShelfState(isPaused ? PEEK : HIDE, OVERSHOOT_1_2, SHELF_ANIM_DURATION);
606     }
607 
maybeUpdateRecentsAttachedState()608     public void maybeUpdateRecentsAttachedState() {
609         maybeUpdateRecentsAttachedState(true /* animate */);
610     }
611 
612     /**
613      * Determines whether to show or hide RecentsView. The window is always
614      * synchronized with its corresponding TaskView in RecentsView, so if
615      * RecentsView is shown, it will appear to be attached to the window.
616      *
617      * Note this method has no effect unless the navigation mode is NO_BUTTON.
618      */
maybeUpdateRecentsAttachedState(boolean animate)619     private void maybeUpdateRecentsAttachedState(boolean animate) {
620         if (mMode != Mode.NO_BUTTON || mRecentsView == null) {
621             return;
622         }
623         RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationWrapper.targetSet == null
624                 ? null
625                 : mRecentsAnimationWrapper.targetSet.findTask(mRunningTaskId);
626         final boolean recentsAttachedToAppWindow;
627         int runningTaskIndex = mRecentsView.getRunningTaskIndex();
628         if (mGestureEndTarget != null) {
629             recentsAttachedToAppWindow = mGestureEndTarget.recentsAttachedToAppWindow;
630         } else if (mContinuingLastGesture
631                 && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
632             recentsAttachedToAppWindow = true;
633             animate = false;
634         } else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) {
635             // The window is going away so make sure recents is always visible in this case.
636             recentsAttachedToAppWindow = true;
637             animate = false;
638         } else {
639             recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
640             if (animate) {
641                 // Only animate if an adjacent task view is visible on screen.
642                 TaskView adjacentTask1 = mRecentsView.getTaskViewAt(runningTaskIndex + 1);
643                 TaskView adjacentTask2 = mRecentsView.getTaskViewAt(runningTaskIndex - 1);
644                 float prevTranslationX = mRecentsView.getTranslationX();
645                 mRecentsView.setTranslationX(0);
646                 animate = (adjacentTask1 != null && adjacentTask1.getGlobalVisibleRect(TEMP_RECT))
647                         || (adjacentTask2 != null && adjacentTask2.getGlobalVisibleRect(TEMP_RECT));
648                 mRecentsView.setTranslationX(prevTranslationX);
649             }
650         }
651         mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
652     }
653 
setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask)654     public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
655         if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
656             mIsLikelyToStartNewTask = isLikelyToStartNewTask;
657             maybeUpdateRecentsAttachedState();
658         }
659     }
660 
661     @UiThread
setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration)662     public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) {
663         mAnimationFactory.setShelfState(shelfState, interpolator, duration);
664         boolean wasShelfPeeking = mIsShelfPeeking;
665         mIsShelfPeeking = shelfState == PEEK;
666         if (mIsShelfPeeking != wasShelfPeeking) {
667             maybeUpdateRecentsAttachedState();
668         }
669         if (mRecentsView != null && shelfState.shouldPreformHaptic) {
670             mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
671                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
672         }
673     }
674 
buildAnimationController()675     private void buildAnimationController() {
676         if (mGestureEndTarget == HOME || mHasLauncherTransitionControllerStarted) {
677             // We don't want a new mLauncherTransitionController if mGestureEndTarget == HOME (it
678             // has its own animation) or if we're already animating the current controller.
679             return;
680         }
681         initTransitionEndpoints(mActivity.getDeviceProfile());
682         mAnimationFactory.createActivityController(mTransitionDragLength);
683     }
684 
685     @Override
onApplyWindowInsets(View view, WindowInsets windowInsets)686     public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
687         WindowInsets result = view.onApplyWindowInsets(windowInsets);
688         buildAnimationController();
689         return result;
690     }
691 
onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim)692     private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) {
693         mLauncherTransitionController = anim;
694         mLauncherTransitionController.dispatchSetInterpolator(t -> t * mDragLengthFactor);
695         mAnimationFactory.adjustActivityControllerInterpolators();
696         mLauncherTransitionController.dispatchOnStart();
697         updateLauncherTransitionProgress();
698     }
699 
700     @UiThread
updateFinalShift()701     private void updateFinalShift() {
702         float shift = mCurrentShift.value;
703 
704         SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
705         if (controller != null) {
706             float offsetX = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
707             float offsetScale = getTaskCurveScaleForOffsetX(offsetX,
708                     mClipAnimationHelper.getTargetRect().width());
709             mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale);
710             mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
711                     mTransformParams);
712             updateSysUiFlags(shift);
713         }
714 
715         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
716             if (mRecentsAnimationWrapper.getController() != null) {
717                 mLiveTileOverlay.update(mClipAnimationHelper.getCurrentRectWithInsets(),
718                         mClipAnimationHelper.getCurrentCornerRadius());
719             }
720         }
721 
722         final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
723         if (passed != mPassedOverviewThreshold) {
724             mPassedOverviewThreshold = passed;
725             if (mRecentsView != null && mMode != Mode.NO_BUTTON) {
726                 mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
727                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
728             }
729         }
730 
731         if (mLauncherTransitionController == null || mLauncherTransitionController
732                 .getAnimationPlayer().isStarted()) {
733             return;
734         }
735         updateLauncherTransitionProgress();
736     }
737 
updateLauncherTransitionProgress()738     private void updateLauncherTransitionProgress() {
739         if (mGestureEndTarget == HOME) {
740             return;
741         }
742         // Normalize the progress to 0 to 1, as the animation controller will clamp it to that
743         // anyway. The controller mimics the drag length factor by applying it to its interpolators.
744         float progress = mCurrentShift.value / mDragLengthFactor;
745         mLauncherTransitionController.setPlayFraction(
746                 progress <= mShiftAtGestureStart || mShiftAtGestureStart >= 1
747                         ? 0 : (progress - mShiftAtGestureStart) / (1 - mShiftAtGestureStart));
748     }
749 
750     /**
751      * @param windowProgress 0 == app, 1 == overview
752      */
updateSysUiFlags(float windowProgress)753     private void updateSysUiFlags(float windowProgress) {
754         if (mRecentsView != null) {
755             TaskView centermostTask = mRecentsView.getTaskViewAt(mRecentsView
756                     .getPageNearestToCenterOfScreen());
757             int centermostTaskFlags = centermostTask == null ? 0
758                     : centermostTask.getThumbnail().getSysUiStatusNavFlags();
759             boolean useHomeScreenFlags = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
760             // We will handle the sysui flags based on the centermost task view.
761             mRecentsAnimationWrapper.setWindowThresholdCrossed(centermostTaskFlags != 0
762                     || useHomeScreenFlags);
763             int sysuiFlags = useHomeScreenFlags ? 0 : centermostTaskFlags;
764             mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
765         }
766     }
767 
768     @Override
onRecentsAnimationStart(SwipeAnimationTargetSet targetSet)769     public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
770         DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
771         final Rect overviewStackBounds;
772         RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
773 
774         if (targetSet.minimizedHomeBounds != null && runningTaskTarget != null) {
775             overviewStackBounds = mActivityControlHelper
776                     .getOverviewWindowBounds(targetSet.minimizedHomeBounds, runningTaskTarget);
777             dp = dp.getMultiWindowProfile(mContext, new Point(
778                     targetSet.minimizedHomeBounds.width(), targetSet.minimizedHomeBounds.height()));
779             dp.updateInsets(targetSet.homeContentInsets);
780         } else {
781             if (mActivity != null) {
782                 int loc[] = new int[2];
783                 View rootView = mActivity.getRootView();
784                 rootView.getLocationOnScreen(loc);
785                 overviewStackBounds = new Rect(loc[0], loc[1], loc[0] + rootView.getWidth(),
786                         loc[1] + rootView.getHeight());
787             } else {
788                 overviewStackBounds = new Rect(0, 0, dp.widthPx, dp.heightPx);
789             }
790             // If we are not in multi-window mode, home insets should be same as system insets.
791             dp = dp.copy(mContext);
792             dp.updateInsets(targetSet.homeContentInsets);
793         }
794         dp.updateIsSeascape(mContext.getSystemService(WindowManager.class));
795 
796         if (runningTaskTarget != null) {
797             mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
798         }
799         mClipAnimationHelper.prepareAnimation(dp, false /* isOpening */);
800         initTransitionEndpoints(dp);
801 
802         mRecentsAnimationWrapper.setController(targetSet);
803         TOUCH_INTERACTION_LOG.addLog("startRecentsAnimationCallback", targetSet.apps.length);
804         setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
805 
806         mPassedOverviewThreshold = false;
807     }
808 
809     @Override
onRecentsAnimationCanceled()810     public void onRecentsAnimationCanceled() {
811         mRecentsAnimationWrapper.setController(null);
812         mActivityInitListener.unregister();
813         setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
814         TOUCH_INTERACTION_LOG.addLog("cancelRecentsAnimation");
815     }
816 
817     @UiThread
onGestureStarted()818     public void onGestureStarted() {
819         notifyGestureStartedAsync();
820         mShiftAtGestureStart = mCurrentShift.value;
821         setStateOnUiThread(STATE_GESTURE_STARTED);
822         mGestureStarted = true;
823     }
824 
825     /**
826      * Notifies the launcher that the swipe gesture has started. This can be called multiple times.
827      */
828     @UiThread
notifyGestureStartedAsync()829     private void notifyGestureStartedAsync() {
830         final T curActivity = mActivity;
831         if (curActivity != null) {
832             // Once the gesture starts, we can no longer transition home through the button, so
833             // reset the force override of the activity visibility
834             mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
835         }
836     }
837 
838     /**
839      * Called as a result on ACTION_CANCEL to return the UI to the start state.
840      */
841     @UiThread
onGestureCancelled()842     public void onGestureCancelled() {
843         updateDisplacement(0);
844         setStateOnUiThread(STATE_GESTURE_COMPLETED);
845         mLogAction = Touch.SWIPE_NOOP;
846         handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */);
847     }
848 
849     /**
850      * @param endVelocity The velocity in the direction of the nav bar to the middle of the screen.
851      * @param velocity The x and y components of the velocity when the gesture ends.
852      * @param downPos The x and y value of where the gesture started.
853      */
854     @UiThread
onGestureEnded(float endVelocity, PointF velocity, PointF downPos)855     public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
856         float flingThreshold = mContext.getResources()
857                 .getDimension(R.dimen.quickstep_fling_threshold_velocity);
858         boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
859         setStateOnUiThread(STATE_GESTURE_COMPLETED);
860 
861         mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
862         boolean isVelocityVertical = Math.abs(velocity.y) > Math.abs(velocity.x);
863         if (isVelocityVertical) {
864             mLogDirection = velocity.y < 0 ? Direction.UP : Direction.DOWN;
865         } else {
866             mLogDirection = velocity.x < 0 ? Direction.LEFT : Direction.RIGHT;
867         }
868         mDownPos = downPos;
869         handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
870     }
871 
872     @UiThread
873     private InputConsumer createNewInputProxyHandler() {
874         endRunningWindowAnim();
875         endLauncherTransitionController();
876         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
877             // Hide the task view, if not already hidden
878             setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
879         }
880 
881         BaseDraggingActivity activity = mActivityControlHelper.getCreatedActivity();
882         return activity == null
883                 ? InputConsumer.NO_OP : new OverviewInputConsumer(activity, null, true);
884     }
885 
886     private void endRunningWindowAnim() {
887         if (mRunningWindowAnim != null) {
888             mRunningWindowAnim.end();
889         }
890     }
891 
892     private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
893             boolean isCancel) {
894         final GestureEndTarget endTarget;
895         final boolean goingToNewTask;
896         if (mRecentsView != null) {
897             if (!mRecentsAnimationWrapper.hasTargets()) {
898                 // If there are no running tasks, then we can assume that this is a continuation of
899                 // the last gesture, but after the recents animation has finished
900                 goingToNewTask = true;
901             } else {
902                 final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
903                 final int taskToLaunch = mRecentsView.getNextPage();
904                 goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
905             }
906         } else {
907             goingToNewTask = false;
908         }
909         final boolean reachedOverviewThreshold = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
910         if (!isFling) {
911             if (isCancel) {
912                 endTarget = LAST_TASK;
913             } else if (mMode == Mode.NO_BUTTON) {
914                 if (mIsShelfPeeking) {
915                     endTarget = RECENTS;
916                 } else if (goingToNewTask) {
917                     endTarget = NEW_TASK;
918                 } else {
919                     endTarget = !reachedOverviewThreshold ? LAST_TASK : HOME;
920                 }
921             } else {
922                 endTarget = reachedOverviewThreshold && mGestureStarted
923                         ? RECENTS
924                         : goingToNewTask
925                                 ? NEW_TASK
926                                 : LAST_TASK;
927             }
928         } else {
929             if (mMode == Mode.NO_BUTTON && endVelocity < 0 && !mIsShelfPeeking) {
930                 // If swiping at a diagonal, base end target on the faster velocity.
931                 endTarget = goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity)
932                         ? NEW_TASK : HOME;
933             } else if (endVelocity < 0) {
934                 if (reachedOverviewThreshold) {
935                     endTarget = RECENTS;
936                 } else {
937                     // If swiping at a diagonal, base end target on the faster velocity.
938                     endTarget = goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity)
939                             ? NEW_TASK : RECENTS;
940                 }
941             } else {
942                 endTarget = goingToNewTask ? NEW_TASK : LAST_TASK;
943             }
944         }
945 
946         int stateFlags = OverviewInteractionState.INSTANCE.get(mActivity).getSystemUiStateFlags();
947         if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0
948                 && (endTarget == RECENTS || endTarget == LAST_TASK)) {
949             return LAST_TASK;
950         }
951         return endTarget;
952     }
953 
954     @UiThread
handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity, boolean isCancel)955     private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity,
956             boolean isCancel) {
957         PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000);
958         long duration = MAX_SWIPE_DURATION;
959         float currentShift = mCurrentShift.value;
960         final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
961                 isFling, isCancel);
962         float endShift = endTarget.endShift;
963         final float startShift;
964         Interpolator interpolator = DEACCEL;
965         if (!isFling) {
966             long expectedDuration = Math.abs(Math.round((endShift - currentShift)
967                     * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
968             duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
969             startShift = currentShift;
970             interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL;
971         } else {
972             startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
973                     * SINGLE_FRAME_MS / mTransitionDragLength, 0, mDragLengthFactor);
974             float minFlingVelocity = mContext.getResources()
975                     .getDimension(R.dimen.quickstep_fling_min_velocity);
976             if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
977                 if (endTarget == RECENTS && mMode != Mode.NO_BUTTON) {
978                     Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
979                             startShift, endShift, endShift, velocityPxPerMs.y,
980                             mTransitionDragLength);
981                     endShift = overshoot.end;
982                     interpolator = overshoot.interpolator;
983                     duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
984                             MAX_SWIPE_DURATION);
985                 } else {
986                     float distanceToTravel = (endShift - currentShift) * mTransitionDragLength;
987 
988                     // we want the page's snap velocity to approximately match the velocity at
989                     // which the user flings, so we scale the duration by a value near to the
990                     // derivative of the scroll interpolator at zero, ie. 2.
991                     long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
992                     duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
993 
994                     if (endTarget == RECENTS) {
995                         interpolator = OVERSHOOT_1_2;
996                     }
997                 }
998             }
999         }
1000 
1001         if (endTarget.isLauncher) {
1002             mRecentsAnimationWrapper.enableInputProxy();
1003         }
1004 
1005         if (endTarget == HOME) {
1006             setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
1007             duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
1008         } else if (endTarget == RECENTS) {
1009             mLiveTileOverlay.startIconAnimation();
1010             if (mRecentsView != null) {
1011                 int nearestPage = mRecentsView.getPageNearestToCenterOfScreen();
1012                 if (mRecentsView.getNextPage() != nearestPage) {
1013                     // We shouldn't really scroll to the next page when swiping up to recents.
1014                     // Only allow settling on the next page if it's nearest to the center.
1015                     mRecentsView.snapToPage(nearestPage, Math.toIntExact(duration));
1016                 }
1017                 if (mRecentsView.getScroller().getDuration() > MAX_SWIPE_DURATION) {
1018                     mRecentsView.snapToPage(mRecentsView.getNextPage(), (int) MAX_SWIPE_DURATION);
1019                 }
1020                 duration = Math.max(duration, mRecentsView.getScroller().getDuration());
1021             }
1022             if (mMode == Mode.NO_BUTTON) {
1023                 setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration);
1024             }
1025         } else if (endTarget == NEW_TASK || endTarget == LAST_TASK) {
1026             // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
1027             // or resumeLastTask().
1028             if (mRecentsView != null) {
1029                 duration = Math.max(duration, mRecentsView.getScroller().getDuration());
1030             }
1031         }
1032         animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
1033     }
1034 
doLogGesture(GestureEndTarget endTarget)1035     private void doLogGesture(GestureEndTarget endTarget) {
1036         DeviceProfile dp = mDp;
1037         if (dp == null || mDownPos == null) {
1038             // We probably never received an animation controller, skip logging.
1039             return;
1040         }
1041 
1042         int pageIndex = endTarget == LAST_TASK
1043                 ? LOG_NO_OP_PAGE_INDEX
1044                 : mRecentsView.getNextPage();
1045         UserEventDispatcher.newInstance(mContext).logStateChangeAction(
1046                 mLogAction, mLogDirection,
1047                 (int) mDownPos.x, (int) mDownPos.y,
1048                 ContainerType.NAVBAR, ContainerType.APP,
1049                 endTarget.containerType,
1050                 pageIndex);
1051     }
1052 
1053     /** Animates to the given progress, where 0 is the current app and 1 is overview. */
1054     @UiThread
animateToProgress(float start, float end, long duration, Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs)1055     private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
1056             GestureEndTarget target, PointF velocityPxPerMs) {
1057         mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration,
1058                 interpolator, target, velocityPxPerMs));
1059     }
1060 
1061     @UiThread
animateToProgressInternal(float start, float end, long duration, Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs)1062     private void animateToProgressInternal(float start, float end, long duration,
1063             Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
1064         mGestureEndTarget = target;
1065 
1066         maybeUpdateRecentsAttachedState();
1067 
1068         if (mGestureEndTarget == HOME) {
1069             HomeAnimationFactory homeAnimFactory;
1070             if (mActivity != null) {
1071                 homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity);
1072             } else {
1073                 homeAnimFactory = new HomeAnimationFactory() {
1074                     @NonNull
1075                     @Override
1076                     public RectF getWindowTargetRect() {
1077                         RectF fallbackTarget = new RectF(mClipAnimationHelper.getTargetRect());
1078                         Utilities.scaleRectFAboutCenter(fallbackTarget, 0.25f);
1079                         return fallbackTarget;
1080                     }
1081 
1082                     @NonNull
1083                     @Override
1084                     public AnimatorPlaybackController createActivityAnimationToHome() {
1085                         return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
1086                     }
1087                 };
1088                 mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
1089                         isPresent -> mRecentsView.startHome());
1090             }
1091             RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
1092             windowAnim.addAnimatorListener(new AnimationSuccessListener() {
1093                 @Override
1094                 public void onAnimationSuccess(Animator animator) {
1095                     setStateOnUiThread(target.endState);
1096                 }
1097             });
1098             windowAnim.start(velocityPxPerMs);
1099             homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
1100             mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
1101             mLauncherTransitionController = null;
1102         } else {
1103             ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);
1104             windowAnim.setDuration(duration).setInterpolator(interpolator);
1105             windowAnim.addUpdateListener(valueAnimator -> {
1106                 if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
1107                     // Views typically don't compute scroll when invisible as an optimization,
1108                     // but in our case we need to since the window offset depends on the scroll.
1109                     mRecentsView.computeScroll();
1110                 }
1111             });
1112             windowAnim.addListener(new AnimationSuccessListener() {
1113                 @Override
1114                 public void onAnimationSuccess(Animator animator) {
1115                     setStateOnUiThread(target.endState);
1116                 }
1117             });
1118             windowAnim.start();
1119             mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
1120         }
1121         // Always play the entire launcher animation when going home, since it is separate from
1122         // the animation that has been controlled thus far.
1123         if (mGestureEndTarget == HOME) {
1124             start = 0;
1125         }
1126 
1127         // We want to use the same interpolator as the window, but need to adjust it to
1128         // interpolate over the remaining progress (end - start).
1129         TimeInterpolator adjustedInterpolator = Interpolators.mapToProgress(
1130                 interpolator, start, end);
1131         if (mLauncherTransitionController == null) {
1132             return;
1133         }
1134         if (start == end || duration <= 0) {
1135             mLauncherTransitionController.dispatchSetInterpolator(t -> end);
1136         } else {
1137             mLauncherTransitionController.dispatchSetInterpolator(adjustedInterpolator);
1138             mAnimationFactory.adjustActivityControllerInterpolators();
1139         }
1140         mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration));
1141 
1142         if (QUICKSTEP_SPRINGS.get()) {
1143             mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs.y);
1144         }
1145         mLauncherTransitionController.getAnimationPlayer().start();
1146         mHasLauncherTransitionControllerStarted = true;
1147     }
1148 
1149     /**
1150      * Creates an animation that transforms the current app window into the home app.
1151      * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
1152      * @param homeAnimationFactory The home animation factory.
1153      */
createWindowAnimationToHome(float startProgress, HomeAnimationFactory homeAnimationFactory)1154     private RectFSpringAnim createWindowAnimationToHome(float startProgress,
1155             HomeAnimationFactory homeAnimationFactory) {
1156         final RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet;
1157         final RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet,
1158                 mTransformParams.setProgress(startProgress), false /* launcherOnTop */));
1159         final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
1160 
1161         final View floatingView = homeAnimationFactory.getFloatingView();
1162         final boolean isFloatingIconView = floatingView instanceof FloatingIconView;
1163         RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mActivity.getResources());
1164         if (isFloatingIconView) {
1165             FloatingIconView fiv = (FloatingIconView) floatingView;
1166             anim.addAnimatorListener(fiv);
1167             fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
1168         }
1169 
1170         AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome();
1171 
1172         // End on a "round-enough" radius so that the shape reveal doesn't have to do too much
1173         // rounding at the end of the animation.
1174         float startRadius = mClipAnimationHelper.getCurrentCornerRadius();
1175         float endRadius = startRect.width() / 6f;
1176         // We want the window alpha to be 0 once this threshold is met, so that the
1177         // FolderIconView can be seen morphing into the icon shape.
1178         final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f;
1179         anim.addOnUpdateListener((currentRect, progress) -> {
1180             homeAnim.setPlayFraction(progress);
1181 
1182             float alphaProgress = ACCEL_1_5.getInterpolation(progress);
1183             float windowAlpha = Utilities.boundToRange(Utilities.mapToRange(alphaProgress, 0,
1184                     windowAlphaThreshold, 1.5f, 0f, Interpolators.LINEAR), 0, 1);
1185             mTransformParams.setProgress(progress)
1186                     .setCurrentRectAndTargetAlpha(currentRect, windowAlpha);
1187             if (isFloatingIconView) {
1188                 mTransformParams.setCornerRadius(endRadius * progress + startRadius
1189                         * (1f - progress));
1190             }
1191             mClipAnimationHelper.applyTransform(targetSet, mTransformParams,
1192                     false /* launcherOnTop */);
1193 
1194             if (isFloatingIconView) {
1195                 ((FloatingIconView) floatingView).update(currentRect, 1f, progress,
1196                         windowAlphaThreshold, mClipAnimationHelper.getCurrentCornerRadius(), false);
1197             }
1198 
1199             updateSysUiFlags(Math.max(progress, mCurrentShift.value));
1200         });
1201         anim.addAnimatorListener(new AnimationSuccessListener() {
1202             @Override
1203             public void onAnimationStart(Animator animation) {
1204                 homeAnim.dispatchOnStart();
1205                 if (mActivity != null) {
1206                     mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
1207                 }
1208             }
1209 
1210             @Override
1211             public void onAnimationSuccess(Animator animator) {
1212                 homeAnim.getAnimationPlayer().end();
1213                 if (mRecentsView != null) {
1214                     mRecentsView.post(mRecentsView::resetTaskVisuals);
1215                 }
1216                 // Make sure recents is in its final state
1217                 maybeUpdateRecentsAttachedState(false);
1218                 mActivityControlHelper.onSwipeUpToHomeComplete(mActivity);
1219             }
1220         });
1221         return anim;
1222     }
1223 
1224     /**
1225      * @return The GestureEndTarget if the gesture has ended, else null.
1226      */
getGestureEndTarget()1227     public @Nullable GestureEndTarget getGestureEndTarget() {
1228         return mGestureEndTarget;
1229     }
1230 
1231     @UiThread
resumeLastTask()1232     private void resumeLastTask() {
1233         mRecentsAnimationWrapper.finish(false /* toRecents */, null);
1234         TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
1235         doLogGesture(LAST_TASK);
1236         reset();
1237     }
1238 
1239     @UiThread
startNewTask()1240     private void startNewTask() {
1241         // Launch the task user scrolled to (mRecentsView.getNextPage()).
1242         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
1243             // We finish recents animation inside launchTask() when live tile is enabled.
1244             mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false /* animate */,
1245                     true /* freezeTaskList */);
1246         } else {
1247             int taskId = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).getTask().key.id;
1248             mFinishingRecentsAnimationForNewTaskId = taskId;
1249             mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
1250                 if (!mCanceled) {
1251                     TaskView nextTask = mRecentsView.getTaskView(taskId);
1252                     if (nextTask != null) {
1253                         nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
1254                                 success -> {
1255                             if (!success) {
1256                                 // We couldn't launch the task, so take user to overview so they can
1257                                 // decide what to do instead of staying in this broken state.
1258                                 endLauncherTransitionController();
1259                                 mActivityControlHelper.onLaunchTaskFailed(mActivity);
1260                                 nextTask.notifyTaskLaunchFailed(TAG);
1261                                 updateSysUiFlags(1 /* windowProgress == overview */);
1262                             } else {
1263                                 mActivityControlHelper.onLaunchTaskSuccess(mActivity);
1264                             }
1265                         }, mMainThreadHandler);
1266                         doLogGesture(NEW_TASK);
1267                     }
1268                     reset();
1269                 }
1270                 mCanceled = false;
1271                 mFinishingRecentsAnimationForNewTaskId = -1;
1272             });
1273         }
1274         TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
1275     }
1276 
reset()1277     public void reset() {
1278         setStateOnUiThread(STATE_HANDLER_INVALIDATED);
1279     }
1280 
1281     /**
1282      * Cancels any running animation so that the active target can be overriden by a new swipe
1283      * handle (in case of quick switch).
1284      */
cancelCurrentAnimation(SwipeSharedState sharedState)1285     public void cancelCurrentAnimation(SwipeSharedState sharedState) {
1286         mCanceled = true;
1287         mCurrentShift.cancelAnimation();
1288         if (mLauncherTransitionController != null && mLauncherTransitionController
1289                 .getAnimationPlayer().isStarted()) {
1290             mLauncherTransitionController.getAnimationPlayer().cancel();
1291         }
1292 
1293         if (mFinishingRecentsAnimationForNewTaskId != -1) {
1294             // If we are canceling mid-starting a new task, switch to the screenshot since the
1295             // recents animation has finished
1296             switchToScreenshot();
1297             TaskView newRunningTaskView = mRecentsView.getTaskView(
1298                     mFinishingRecentsAnimationForNewTaskId);
1299             int newRunningTaskId = newRunningTaskView != null
1300                     ? newRunningTaskView.getTask().key.id
1301                     : -1;
1302             mRecentsView.setCurrentTask(newRunningTaskId);
1303             sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId);
1304         }
1305     }
1306 
invalidateHandler()1307     private void invalidateHandler() {
1308         endRunningWindowAnim();
1309 
1310         if (mGestureEndCallback != null) {
1311             mGestureEndCallback.run();
1312         }
1313 
1314         mActivityInitListener.unregister();
1315         mTaskSnapshot = null;
1316     }
1317 
invalidateHandlerWithLauncher()1318     private void invalidateHandlerWithLauncher() {
1319         endLauncherTransitionController();
1320 
1321         mRecentsView.onGestureAnimationEnd();
1322 
1323         mActivity.getRootView().setOnApplyWindowInsetsListener(null);
1324         mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
1325     }
1326 
endLauncherTransitionController()1327     private void endLauncherTransitionController() {
1328         setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
1329         if (mLauncherTransitionController != null) {
1330             mLauncherTransitionController.getAnimationPlayer().end();
1331             mLauncherTransitionController = null;
1332         }
1333     }
1334 
notifyTransitionCancelled()1335     private void notifyTransitionCancelled() {
1336         mAnimationFactory.onTransitionCancelled();
1337     }
1338 
resetStateForAnimationCancel()1339     private void resetStateForAnimationCancel() {
1340         boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
1341         mActivityControlHelper.onTransitionCancelled(mActivity, wasVisible);
1342 
1343         // Leave the pending invisible flag, as it may be used by wallpaper open animation.
1344         mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
1345     }
1346 
switchToScreenshot()1347     private void switchToScreenshot() {
1348         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
1349             setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
1350         } else if (!mRecentsAnimationWrapper.hasTargets()) {
1351             // If there are no targets, then we don't need to capture anything
1352             setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
1353         } else {
1354             boolean finishTransitionPosted = false;
1355             SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
1356             if (controller != null) {
1357                 // Update the screenshot of the task
1358                 if (mTaskSnapshot == null) {
1359                     mTaskSnapshot = controller.screenshotTask(mRunningTaskId);
1360                 }
1361                 final TaskView taskView;
1362                 if (mGestureEndTarget == HOME) {
1363                     // Capture the screenshot before finishing the transition to home to ensure it's
1364                     // taken in the correct orientation, but no need to update the thumbnail.
1365                     taskView = null;
1366                 } else {
1367                     taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot);
1368                 }
1369                 if (taskView != null && !mCanceled) {
1370                     // Defer finishing the animation until the next launcher frame with the
1371                     // new thumbnail
1372                     finishTransitionPosted = new WindowCallbacksCompat(taskView) {
1373 
1374                         // The number of frames to defer until we actually finish the animation
1375                         private int mDeferFrameCount = 2;
1376 
1377                         @Override
1378                         public void onPostDraw(Canvas canvas) {
1379                             // If we were cancelled after this was attached, do not update
1380                             // the state.
1381                             if (mCanceled) {
1382                                 detach();
1383                                 return;
1384                             }
1385 
1386                             if (mDeferFrameCount > 0) {
1387                                 mDeferFrameCount--;
1388                                 // Workaround, detach and reattach to invalidate the root node for
1389                                 // another draw
1390                                 detach();
1391                                 attach();
1392                                 taskView.invalidate();
1393                                 return;
1394                             }
1395 
1396                             setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
1397                             detach();
1398                         }
1399                     }.attach();
1400                 }
1401             }
1402             if (!finishTransitionPosted) {
1403                 // If we haven't posted a draw callback, set the state immediately.
1404                 RaceConditionTracker.onEvent(SCREENSHOT_CAPTURED_EVT, ENTER);
1405                 setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
1406                 RaceConditionTracker.onEvent(SCREENSHOT_CAPTURED_EVT, EXIT);
1407             }
1408         }
1409     }
1410 
finishCurrentTransitionToRecents()1411     private void finishCurrentTransitionToRecents() {
1412         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
1413             setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
1414         } else if (!mRecentsAnimationWrapper.hasTargets()) {
1415             // If there are no targets, then there is nothing to finish
1416             setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
1417         } else {
1418             synchronized (mRecentsAnimationWrapper) {
1419                 mRecentsAnimationWrapper.finish(true /* toRecents */,
1420                         () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
1421             }
1422         }
1423         TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
1424     }
1425 
finishCurrentTransitionToHome()1426     private void finishCurrentTransitionToHome() {
1427         synchronized (mRecentsAnimationWrapper) {
1428             mRecentsAnimationWrapper.finish(true /* toRecents */,
1429                     () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED),
1430                     true /* sendUserLeaveHint */);
1431         }
1432         TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
1433         doLogGesture(HOME);
1434     }
1435 
setupLauncherUiAfterSwipeUpToRecentsAnimation()1436     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
1437         endLauncherTransitionController();
1438         mActivityControlHelper.onSwipeUpToRecentsComplete(mActivity);
1439         mRecentsAnimationWrapper.setCancelWithDeferredScreenshot(true);
1440         mRecentsView.onSwipeUpAnimationSuccess();
1441 
1442         RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
1443 
1444         doLogGesture(RECENTS);
1445         reset();
1446     }
1447 
setGestureEndCallback(Runnable gestureEndCallback)1448     public void setGestureEndCallback(Runnable gestureEndCallback) {
1449         mGestureEndCallback = gestureEndCallback;
1450     }
1451 
setTargetAlphaProvider( BiFunction<RemoteAnimationTargetCompat, Float, Float> provider)1452     private void setTargetAlphaProvider(
1453             BiFunction<RemoteAnimationTargetCompat, Float, Float> provider) {
1454         mClipAnimationHelper.setTaskAlphaCallback(provider);
1455         updateFinalShift();
1456     }
1457 
getHiddenTargetAlpha(RemoteAnimationTargetCompat app, Float expectedAlpha)1458     public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, Float expectedAlpha) {
1459         if (!isNotInRecents(app)) {
1460             return 0;
1461         }
1462         return expectedAlpha;
1463     }
1464 
isNotInRecents(RemoteAnimationTargetCompat app)1465     private static boolean isNotInRecents(RemoteAnimationTargetCompat app) {
1466         return app.isNotInRecents
1467                 || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
1468     }
1469 
1470     private interface RunningWindowAnim {
1471         void end();
1472 
wrap(Animator animator)1473         static RunningWindowAnim wrap(Animator animator) {
1474             return animator::end;
1475         }
1476 
wrap(RectFSpringAnim rectFSpringAnim)1477         static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) {
1478             return rectFSpringAnim::end;
1479         }
1480     }
1481 }
1482