Merging  ub-launcher3-qt-dev, build 5656472
am: 0e57817adc

Change-Id: I13a7214d0a1d3ee3eda994555aa0d72e3ad4702e
diff --git a/go/quickstep/src/com/android/quickstep/GoActivityControlHelper.java b/go/quickstep/src/com/android/quickstep/GoActivityControlHelper.java
index 2db8b39..274a347 100644
--- a/go/quickstep/src/com/android/quickstep/GoActivityControlHelper.java
+++ b/go/quickstep/src/com/android/quickstep/GoActivityControlHelper.java
@@ -61,4 +61,9 @@
         // Go does not support live tiles.
         return false;
     }
+
+    @Override
+    public void onLaunchTaskFailed(T activity) {
+        // Go does not support gestures from one task to another.
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index f1d6450..1c66968 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -64,10 +64,12 @@
     public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
         // Initialize the recents view scale to what it would be when starting swipe up
         RecentsView recentsView = launcher.getOverviewPanel();
-        if (recentsView.getTaskViewCount() == 0) {
+        int taskCount = recentsView.getTaskViewCount();
+        if (taskCount == 0) {
             return super.getOverviewScaleAndTranslation(launcher);
         }
-        TaskView dummyTask = recentsView.getTaskViewAt(recentsView.getCurrentPage());
+        TaskView dummyTask = recentsView.getTaskViewAt(Math.max(taskCount - 1,
+                recentsView.getCurrentPage()));
         return recentsView.getTempClipAnimationHelper().updateForFullscreenOverview(dummyTask)
                 .getScaleAndTranslation();
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index ed511f5..cdc271f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.uioverrides.states;
 
+import android.os.Handler;
+import android.os.Looper;
+
 import com.android.launcher3.Launcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.views.RecentsView;
@@ -27,6 +30,8 @@
  */
 public class QuickSwitchState extends BackgroundAppState {
 
+    private static final String TAG = "QuickSwitchState";
+
     public QuickSwitchState(int id) {
         super(id, LauncherLogProto.ContainerType.APP);
     }
@@ -48,7 +53,12 @@
     public void onStateTransitionEnd(Launcher launcher) {
         TaskView tasktolaunch = launcher.<RecentsView>getOverviewPanel().getTaskViewAt(0);
         if (tasktolaunch != null) {
-            tasktolaunch.launchTask(false);
+            tasktolaunch.launchTask(false, success -> {
+                if (!success) {
+                    launcher.getStateManager().goToState(OVERVIEW);
+                    tasktolaunch.notifyTaskLaunchFailed(TAG);
+                }
+            }, new Handler(Looper.getMainLooper()));
         } else {
             launcher.getStateManager().goToState(NORMAL);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
index 2c42fd6..4ae6d87 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -224,4 +224,10 @@
     public boolean isInLiveTileMode() {
         return false;
     }
+
+    @Override
+    public void onLaunchTaskFailed(RecentsActivity activity) {
+        // TODO: probably go back to overview instead.
+        activity.<RecentsView>getOverviewPanel().startHome();
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index 1b82bcb..0d06c19 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -16,13 +16,17 @@
 package com.android.quickstep;
 
 import static android.view.View.TRANSLATION_Y;
+
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
 import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_DAMPING_RATIO;
 import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_STIFFNESS;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.WindowTransformSwipeHandler.RECENTS_ATTACH_DURATION;
 
@@ -44,16 +48,19 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherInitListenerEx;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.SpringObjectAnimator;
-import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.views.FloatingIconView;
@@ -100,6 +107,13 @@
     }
 
     @Override
+    public void onSwipeUpToHomeComplete(Launcher activity) {
+        // Ensure recents is at the correct position for NORMAL state. For example, when we detach
+        // recents, we assume the first task is invisible, making translation off by one task.
+        activity.getStateManager().reapplyState();
+    }
+
+    @Override
     public void onAssistantVisibilityChanged(float visibility) {
         Launcher launcher = getCreatedActivity();
         if (launcher != null) {
@@ -156,18 +170,23 @@
             public AnimatorPlaybackController createActivityAnimationToHome() {
                 // Return an empty APC here since we have an non-user controlled animation to home.
                 long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
-                AnimatorSet as = new AnimatorSet();
-                as.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        activity.getStateManager().goToState(NORMAL, false);
-                    }
-                });
-                return AnimatorPlaybackController.wrap(as, accuracy);
+                return activity.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy,
+                        0 /* animComponents */);
             }
 
             @Override
             public void playAtomicAnimation(float velocity) {
+                // Setup workspace with 0 duration to prepare for our staggered animation.
+                LauncherStateManager stateManager = activity.getStateManager();
+                AnimatorSetBuilder builder = new AnimatorSetBuilder();
+                // setRecentsAttachedToAppWindow() will animate recents out.
+                builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
+                stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0);
+                builder.build().start();
+
+                // Stop scrolling so that it doesn't interfere with the translation offscreen.
+                recentsView.getScroller().forceFinished(true);
+
                 new StaggeredWorkspaceAnim(activity, workspaceView, velocity).start();
             }
         };
@@ -196,12 +215,11 @@
         // Optimization, hide the all apps view to prevent layout while initializing
         activity.getAppsView().getContentView().setVisibility(View.GONE);
 
-        AccessibilityManagerCompat.sendStateEventToTest(activity, fromState.ordinal);
-
         return new AnimationFactory() {
             private Animator mShelfAnim;
             private ShelfAnimState mShelfState;
-            private Animator mAttachToWindowAnim;
+            private Animator mAttachToWindowFadeAnim;
+            private SpringAnimation mAttachToWindowTranslationXAnim;
             private boolean mIsAttachedToWindow;
 
             @Override
@@ -267,20 +285,60 @@
                     return;
                 }
                 mIsAttachedToWindow = attached;
-                if (mAttachToWindowAnim != null) {
-                    mAttachToWindowAnim.cancel();
+                if (mAttachToWindowFadeAnim != null) {
+                    mAttachToWindowFadeAnim.cancel();
                 }
-                mAttachToWindowAnim = ObjectAnimator.ofFloat(activity.getOverviewPanel(),
+                RecentsView recentsView = activity.getOverviewPanel();
+                mAttachToWindowFadeAnim = ObjectAnimator.ofFloat(recentsView,
                         RecentsView.CONTENT_ALPHA, attached ? 1 : 0);
-                mAttachToWindowAnim.addListener(new AnimatorListenerAdapter() {
+
+                int runningTaskIndex = recentsView.getRunningTaskIndex();
+                if (runningTaskIndex == 0) {
+                    // If we are on the first task (we haven't quick switched), translate recents in
+                    // from the side. Calculate the start translation based on current scale/scroll.
+                    float currScale = recentsView.getScaleX();
+                    float scrollOffsetX = recentsView.getScrollOffset();
+
+                    float offscreenX = NORMAL.getOverviewScaleAndTranslation(activity).translationX;
+                    // The first task is hidden, so offset by its width.
+                    int firstTaskWidth = recentsView.getTaskViewAt(0).getWidth();
+                    offscreenX -= (firstTaskWidth + recentsView.getPageSpacing()) * currScale;
+                    // Offset since scale pushes tasks outwards.
+                    offscreenX += firstTaskWidth * (currScale - 1) / 2;
+                    offscreenX = Math.max(0, offscreenX);
+                    if (recentsView.isRtl()) {
+                        offscreenX = -offscreenX;
+                    }
+
+                    float fromTranslationX = attached ? offscreenX - scrollOffsetX : 0;
+                    float toTranslationX = attached ? 0 : offscreenX - scrollOffsetX;
+                    if (mAttachToWindowTranslationXAnim == null) {
+                        mAttachToWindowTranslationXAnim = new SpringAnimation(recentsView,
+                                SpringAnimation.TRANSLATION_X).setSpring(new SpringForce()
+                                .setDampingRatio(0.8f)
+                                .setStiffness(250));
+                    }
+                    if (!recentsView.isShown() && animate) {
+                        recentsView.setTranslationX(fromTranslationX);
+                        mAttachToWindowTranslationXAnim.setStartValue(fromTranslationX);
+                    }
+                    mAttachToWindowTranslationXAnim.animateToFinalPosition(toTranslationX);
+                    if (!animate && mAttachToWindowTranslationXAnim.canSkipToEnd()) {
+                        mAttachToWindowTranslationXAnim.skipToEnd();
+                    }
+
+                    mAttachToWindowFadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
+                } else {
+                    mAttachToWindowFadeAnim.setInterpolator(ACCEL_DEACCEL);
+                }
+                mAttachToWindowFadeAnim.addListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
-                        mAttachToWindowAnim = null;
+                        mAttachToWindowFadeAnim = null;
                     }
                 });
-                mAttachToWindowAnim.setInterpolator(ACCEL_DEACCEL);
-                mAttachToWindowAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0);
-                mAttachToWindowAnim.start();
+                mAttachToWindowFadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0);
+                mAttachToWindowFadeAnim.start();
             }
         };
     }
@@ -448,4 +506,9 @@
         return launcher != null && launcher.getStateManager().getState() == OVERVIEW &&
                 launcher.isStarted();
     }
+
+    @Override
+    public void onLaunchTaskFailed(Launcher launcher) {
+        launcher.getStateManager().goToState(OVERVIEW);
+    }
 }
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
index 5a039cd..6689ce3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
@@ -15,8 +15,11 @@
  */
 package com.android.quickstep;
 
+import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
+
 import android.util.Log;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.Preconditions;
 import com.android.quickstep.util.RecentsAnimationListenerSet;
@@ -29,7 +32,7 @@
  */
 public class SwipeSharedState implements SwipeAnimationListener {
 
-    private final OverviewComponentObserver mOverviewComponentObserver;
+    private OverviewComponentObserver mOverviewComponentObserver;
 
     private RecentsAnimationListenerSet mRecentsAnimationListener;
     private SwipeAnimationTargetSet mLastAnimationTarget;
@@ -42,8 +45,8 @@
     public boolean recentsAnimationFinishInterrupted;
     public int nextRunningTaskId = -1;
 
-    public SwipeSharedState(OverviewComponentObserver overviewComponentObserver) {
-        mOverviewComponentObserver = overviewComponentObserver;
+    public void setOverviewComponentObserver(OverviewComponentObserver observer) {
+        mOverviewComponentObserver = observer;
     }
 
     @Override
@@ -72,6 +75,12 @@
     private void clearListenerState() {
         if (mRecentsAnimationListener != null) {
             mRecentsAnimationListener.removeListener(this);
+            mRecentsAnimationListener.cancelListener();
+            if (mLastAnimationRunning && mLastAnimationTarget != null) {
+                Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(),
+                        mLastAnimationTarget::cancelAnimation);
+                mLastAnimationTarget = null;
+            }
         }
         mRecentsAnimationListener = null;
         clearAnimationTarget();
@@ -98,9 +107,10 @@
         }
 
         clearListenerState();
-        mRecentsAnimationListener = new RecentsAnimationListenerSet(mOverviewComponentObserver
-                .getActivityControlHelper().shouldMinimizeSplitScreen(),
-                this::onSwipeAnimationFinished);
+        boolean shouldMinimiseSplitScreen = mOverviewComponentObserver == null ? false
+                : mOverviewComponentObserver.getActivityControlHelper().shouldMinimizeSplitScreen();
+        mRecentsAnimationListener = new RecentsAnimationListenerSet(
+                shouldMinimiseSplitScreen, this::onSwipeAnimationFinished);
         mRecentsAnimationListener.addListener(this);
         return mRecentsAnimationListener;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index 5104fb8..b90f6c2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -24,6 +24,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.quickstep.views.TaskThumbnailView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -62,7 +63,7 @@
         return shortcuts;
     }
 
-    public TaskOverlay createOverlay(View thumbnailView) {
+    public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) {
         return new TaskOverlay();
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index dc35440..097ff46 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -17,7 +17,12 @@
 
 import static android.view.MotionEvent.ACTION_DOWN;
 
+import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
+import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_HINTS_IN_OVERVIEW;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.config.FeatureFlags.FAKE_LANDSCAPE_UI;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
@@ -27,7 +32,6 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
 
 import android.annotation.TargetApi;
@@ -61,6 +65,7 @@
 import android.view.WindowManager;
 
 import androidx.annotation.BinderThread;
+import androidx.annotation.UiThread;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.MainThreadExecutor;
@@ -81,6 +86,7 @@
 import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
 import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer;
+import com.android.quickstep.inputconsumers.ResetGestureInputConsumer;
 import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -94,7 +100,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
@@ -146,10 +151,7 @@
             mISystemUiProxy = ISystemUiProxy.Stub
                     .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
             MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
-            runWhenUserUnlocked(() -> {
-                mRecentsModel.setSystemUiProxy(mISystemUiProxy);
-                mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
-            });
+            MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet);
         }
 
         @Override
@@ -182,16 +184,9 @@
 
         @Override
         public void onAssistantVisibilityChanged(float visibility) {
-            if (mOverviewComponentObserver == null) {
-                // Save the visibility to be applied when the user is unlocked
-                mPendingAssistantVisibility = visibility;
-                return;
-            }
-
-            MAIN_THREAD_EXECUTOR.execute(() -> {
-                mOverviewComponentObserver.getActivityControlHelper()
-                        .onAssistantVisibilityChanged(visibility);
-            });
+            mLastAssistantVisibility = visibility;
+            MAIN_THREAD_EXECUTOR.execute(
+                    TouchInteractionService.this::onAssistantVisibilityChanged);
         }
 
         public void onBackAction(boolean completed, int downX, int downY, boolean isButton,
@@ -208,8 +203,7 @@
 
         public void onSystemUiStateChanged(int stateFlags) {
             mSystemUiStateFlags = stateFlags;
-            mOverviewInteractionState.setSystemUiStateFlags(stateFlags);
-            mOverviewComponentObserver.onSystemUiStateChanged(stateFlags);
+            MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiFlagsChanged);
         }
 
         /** Deprecated methods **/
@@ -236,6 +230,10 @@
         return sConnected;
     }
 
+    private final SwipeSharedState mSwipeSharedState = new SwipeSharedState();
+    private final InputConsumer mResetGestureInputConsumer =
+            new ResetGestureInputConsumer(mSwipeSharedState);
+
     private KeyguardManager mKM;
     private ActivityManagerWrapper mAM;
     private RecentsModel mRecentsModel;
@@ -246,13 +244,11 @@
     private OverviewCallbacks mOverviewCallbacks;
     private TaskOverlayFactory mTaskOverlayFactory;
     private InputConsumerController mInputConsumer;
-    private SwipeSharedState mSwipeSharedState;
     private boolean mAssistantAvailable;
-    private float mPendingAssistantVisibility = 0;
+    private float mLastAssistantVisibility = 0;
     private @SystemUiStateFlags int mSystemUiStateFlags;
 
     private boolean mIsUserUnlocked;
-    private List<Runnable> mOnUserUnlockedCallbacks;
     private BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -286,7 +282,7 @@
         // Everything else should be initialized in initWhenUserUnlocked() below.
         mKM = getSystemService(KeyguardManager.class);
         mMainChoreographer = Choreographer.getInstance();
-        mOnUserUnlockedCallbacks = new ArrayList<>();
+        mAM = ActivityManagerWrapper.getInstance();
 
         if (UserManagerCompat.getInstance(this).isUserUnlocked(Process.myUserHandle())) {
             initWhenUserUnlocked();
@@ -413,26 +409,21 @@
     }
 
     private void initWhenUserUnlocked() {
-        mIsUserUnlocked = true;
-
-        mAM = ActivityManagerWrapper.getInstance();
         mRecentsModel = RecentsModel.INSTANCE.get(this);
         mOverviewComponentObserver = new OverviewComponentObserver(this);
-        mOverviewComponentObserver.getActivityControlHelper().onAssistantVisibilityChanged(
-                mPendingAssistantVisibility);
 
         mOverviewCommandHelper = new OverviewCommandHelper(this, mOverviewComponentObserver);
         mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this);
         mOverviewCallbacks = OverviewCallbacks.get(this);
         mTaskOverlayFactory = TaskOverlayFactory.INSTANCE.get(this);
-        mSwipeSharedState = new SwipeSharedState(mOverviewComponentObserver);
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
-        mInputConsumer.registerInputConsumer();
+        mIsUserUnlocked = true;
 
-        for (Runnable callback : mOnUserUnlockedCallbacks) {
-            callback.run();
-        }
-        mOnUserUnlockedCallbacks.clear();
+        mSwipeSharedState.setOverviewComponentObserver(mOverviewComponentObserver);
+        mInputConsumer.registerInputConsumer();
+        onSystemUiProxySet();
+        onSystemUiFlagsChanged();
+        onAssistantVisibilityChanged();
 
         // Temporarily disable model preload
         // new ModelPreload().start(this);
@@ -440,11 +431,27 @@
         Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
     }
 
-    private void runWhenUserUnlocked(Runnable callback) {
+    @UiThread
+    private void onSystemUiProxySet() {
         if (mIsUserUnlocked) {
-            callback.run();
-        } else {
-            mOnUserUnlockedCallbacks.add(callback);
+            mRecentsModel.setSystemUiProxy(mISystemUiProxy);
+            mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
+        }
+    }
+
+    @UiThread
+    private void onSystemUiFlagsChanged() {
+        if (mIsUserUnlocked) {
+            mOverviewInteractionState.setSystemUiStateFlags(mSystemUiStateFlags);
+            mOverviewComponentObserver.onSystemUiStateChanged(mSystemUiStateFlags);
+        }
+    }
+
+    @UiThread
+    private void onAssistantVisibilityChanged() {
+        if (mIsUserUnlocked) {
+            mOverviewComponentObserver.getActivityControlHelper().onAssistantVisibilityChanged(
+                    mLastAssistantVisibility);
         }
     }
 
@@ -510,12 +517,14 @@
                 // launched while device is locked even after exiting direct boot mode (e.g. camera).
                 return createDeviceLockedInputConsumer(mAM.getRunningTask(0));
             } else {
-                return InputConsumer.NO_OP;
+                return mResetGestureInputConsumer;
             }
         }
 
-        InputConsumer base = isInValidSystemUiState
-                ? newBaseConsumer(useSharedState, event) : InputConsumer.NO_OP;
+        // When using sharedState, bypass systemState check as this is a followup gesture and the
+        // first gesture started in a valid system state.
+        InputConsumer base = isInValidSystemUiState || useSharedState
+                ? newBaseConsumer(useSharedState, event) : mResetGestureInputConsumer;
         if (mMode == Mode.NO_BUTTON) {
             final ActivityControlHelper activityControl =
                     mOverviewComponentObserver.getActivityControlHelper();
@@ -560,7 +569,7 @@
 
         if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher
                 && !mSwipeSharedState.recentsAnimationFinishInterrupted) {
-            return InputConsumer.NO_OP;
+            return mResetGestureInputConsumer;
         } else if (mSwipeSharedState.recentsAnimationFinishInterrupted) {
             // If the finish animation was interrupted, then continue using the other activity input
             // consumer but with the next task as the running task
@@ -573,7 +582,7 @@
             return createOverviewInputConsumer(event);
         } else if (mGestureBlockingActivity != null && runningTaskInfo != null
                 && mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) {
-            return InputConsumer.NO_OP;
+            return mResetGestureInputConsumer;
         } else {
             return createOtherActivityInputConsumer(event, runningTaskInfo);
         }
@@ -604,7 +613,7 @@
             return new DeviceLockedInputConsumer(this, mSwipeSharedState, mInputMonitorCompat,
                     mSwipeTouchRegion, taskInfo.taskId);
         } else {
-            return InputConsumer.NO_OP;
+            return mResetGestureInputConsumer;
         }
     }
 
@@ -613,10 +622,10 @@
                 mOverviewComponentObserver.getActivityControlHelper();
         BaseDraggingActivity activity = activityControl.getCreatedActivity();
         if (activity == null) {
-            return InputConsumer.NO_OP;
+            return mResetGestureInputConsumer;
         }
 
-        if (activity.getRootView().hasWindowFocus()) {
+        if (activity.getRootView().hasWindowFocus() || mSwipeSharedState.goingToLauncher) {
             return new OverviewInputConsumer(activity, mInputMonitorCompat,
                     false /* startingInActivityBounds */);
         } else {
@@ -630,7 +639,8 @@
      */
     private void onConsumerInactive(InputConsumer caller) {
         if (mConsumer == caller) {
-            mConsumer = InputConsumer.NO_OP;
+            mConsumer = mResetGestureInputConsumer;
+            mUncheckedConsumer = mConsumer;
         }
     }
 
@@ -666,6 +676,13 @@
                 mSwipeSharedState.dump("    ", pw);
             }
             pw.println("  mConsumer=" + mConsumer.getName());
+            pw.println("FeatureFlags:");
+            pw.println("  APPLY_CONFIG_AT_RUNTIME=" + APPLY_CONFIG_AT_RUNTIME.get());
+            pw.println("  QUICKSTEP_SPRINGS=" + QUICKSTEP_SPRINGS.get());
+            pw.println("  ADAPTIVE_ICON_WINDOW_ANIM=" + ADAPTIVE_ICON_WINDOW_ANIM.get());
+            pw.println("  ENABLE_QUICKSTEP_LIVE_TILE=" + ENABLE_QUICKSTEP_LIVE_TILE.get());
+            pw.println("  ENABLE_HINTS_IN_OVERVIEW=" + ENABLE_HINTS_IN_OVERVIEW.get());
+            pw.println("  FAKE_LANDSCAPE_UI=" + FAKE_LANDSCAPE_UI.get());
             TOUCH_INTERACTION_LOG.dump("", pw);
 
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index c7841d9..e9aa6d0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
@@ -218,7 +217,7 @@
             Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
     private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured";
 
-    private static final long SHELF_ANIM_DURATION = 120;
+    private static final long SHELF_ANIM_DURATION = 240;
     public static final long RECENTS_ATTACH_DURATION = 300;
 
     // Start resisting when swiping past this factor of mTransitionDragLength.
@@ -602,7 +601,7 @@
     }
 
     public void onMotionPauseChanged(boolean isPaused) {
-        setShelfState(isPaused ? PEEK : HIDE, FAST_OUT_SLOW_IN, SHELF_ANIM_DURATION);
+        setShelfState(isPaused ? PEEK : HIDE, OVERSHOOT_1_2, SHELF_ANIM_DURATION);
     }
 
     public void maybeUpdateRecentsAttachedState() {
@@ -625,7 +624,10 @@
                 : mRecentsAnimationWrapper.targetSet.findTask(mRunningTaskId);
         final boolean recentsAttachedToAppWindow;
         int runningTaskIndex = mRecentsView.getRunningTaskIndex();
-        if (mContinuingLastGesture) {
+        if (mGestureEndTarget != null) {
+            recentsAttachedToAppWindow = mGestureEndTarget.recentsAttachedToAppWindow;
+        } else if (mContinuingLastGesture
+                && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
             recentsAttachedToAppWindow = true;
             animate = false;
         } else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) {
@@ -633,17 +635,16 @@
             recentsAttachedToAppWindow = true;
             animate = false;
         } else {
-            if (mGestureEndTarget != null) {
-                recentsAttachedToAppWindow = mGestureEndTarget.recentsAttachedToAppWindow;
-            } else {
-                recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
-            }
+            recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
             if (animate) {
                 // Only animate if an adjacent task view is visible on screen.
                 TaskView adjacentTask1 = mRecentsView.getTaskViewAt(runningTaskIndex + 1);
                 TaskView adjacentTask2 = mRecentsView.getTaskViewAt(runningTaskIndex - 1);
+                float prevTranslationX = mRecentsView.getTranslationX();
+                mRecentsView.setTranslationX(0);
                 animate = (adjacentTask1 != null && adjacentTask1.getGlobalVisibleRect(TEMP_RECT))
                         || (adjacentTask2 != null && adjacentTask2.getGlobalVisibleRect(TEMP_RECT));
+                mRecentsView.setTranslationX(prevTranslationX);
             }
         }
         mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
@@ -701,13 +702,7 @@
 
         SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
         if (controller != null) {
-            float offsetX = 0;
-            if (mRecentsView != null) {
-                int startScroll = mRecentsView.getScrollForPage(mRecentsView.indexOfChild(
-                        mRecentsView.getRunningTaskView()));
-                offsetX = startScroll - mRecentsView.getScrollX();
-                offsetX *= mRecentsView.getScaleX();
-            }
+            float offsetX = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
             float offsetScale = getTaskCurveScaleForOffsetX(offsetX,
                     mClipAnimationHelper.getTargetRect().width());
             mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale);
@@ -751,6 +746,9 @@
                         ? 0 : (progress - mShiftAtGestureStart) / (1 - mShiftAtGestureStart));
     }
 
+    /**
+     * @param windowProgress 0 == app, 1 == overview
+     */
     private void updateSysUiFlags(float windowProgress) {
         if (mRecentsView != null) {
             TaskView centermostTask = mRecentsView.getTaskViewAt(mRecentsView
@@ -873,9 +871,7 @@
     @UiThread
     private InputConsumer createNewInputProxyHandler() {
         endRunningWindowAnim();
-        if (mLauncherTransitionController != null) {
-            mLauncherTransitionController.getAnimationPlayer().end();
-        }
+        endLauncherTransitionController();
         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             // Hide the task view, if not already hidden
             setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
@@ -1217,6 +1213,9 @@
                 if (mRecentsView != null) {
                     mRecentsView.post(mRecentsView::resetTaskVisuals);
                 }
+                // Make sure recents is in its final state
+                maybeUpdateRecentsAttachedState(false);
+                mActivityControlHelper.onSwipeUpToHomeComplete(mActivity);
             }
         });
         return anim;
@@ -1251,7 +1250,17 @@
                 if (!mCanceled) {
                     TaskView nextTask = mRecentsView.getTaskView(taskId);
                     if (nextTask != null) {
-                        nextTask.launchTask(false /* animate */, true /* freezeTaskList */);
+                        nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
+                                success -> {
+                            if (!success) {
+                                // We couldn't launch the task, so take user to overview so they can
+                                // decide what to do instead of staying in this broken state.
+                                endLauncherTransitionController();
+                                mActivityControlHelper.onLaunchTaskFailed(mActivity);
+                                nextTask.notifyTaskLaunchFailed(TAG);
+                                updateSysUiFlags(1 /* windowProgress == overview */);
+                            }
+                        }, mMainThreadHandler);
                         doLogGesture(NEW_TASK);
                     }
                     reset();
@@ -1314,6 +1323,7 @@
     }
 
     private void endLauncherTransitionController() {
+        setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
         if (mLauncherTransitionController != null) {
             mLauncherTransitionController.getAnimationPlayer().end();
             mLauncherTransitionController = null;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
index 489eb27..a1e5d47 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
@@ -32,6 +32,19 @@
     int TYPE_ACCESSIBILITY = 1 << 5;
     int TYPE_SCREEN_PINNED = 1 << 6;
     int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7;
+    int TYPE_RESET_GESTURE = 1 << 8;
+
+    String[] NAMES = new String[] {
+           "TYPE_NO_OP",                    // 0
+            "TYPE_OVERVIEW",                // 1
+            "TYPE_OTHER_ACTIVITY",          // 2
+            "TYPE_ASSISTANT",               // 3
+            "TYPE_DEVICE_LOCKED",           // 4
+            "TYPE_ACCESSIBILITY",           // 5
+            "TYPE_SCREEN_PINNED",           // 6
+            "TYPE_OVERVIEW_WITHOUT_FOCUS",  // 7
+            "TYPE_RESET_GESTURE",           // 8
+    };
 
     InputConsumer NO_OP = () -> TYPE_NO_OP;
 
@@ -66,23 +79,15 @@
     }
 
     default String getName() {
-        switch (getType()) {
-            case TYPE_OVERVIEW:
-                return "OVERVIEW";
-            case TYPE_OTHER_ACTIVITY:
-                return "OTHER_ACTIVITY";
-            case TYPE_ASSISTANT:
-                return "ASSISTANT";
-            case TYPE_DEVICE_LOCKED:
-                return "DEVICE_LOCKED";
-            case TYPE_ACCESSIBILITY:
-                return "ACCESSIBILITY";
-            case TYPE_SCREEN_PINNED:
-                return "SCREEN_PINNED";
-            case TYPE_OVERVIEW_WITHOUT_FOCUS:
-                return "TYPE_OVERVIEW_WITHOUT_FOCUS";
-            default:
-                return "NO_OP";
+        String name = "";
+        for (int i = 0; i < NAMES.length; i++) {
+            if ((getType() & (1 << i)) != 0) {
+                if (name.length() > 0) {
+                    name += ":";
+                }
+                name += NAMES[i];
+            }
         }
+        return name;
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 70ea627..b021df8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -27,7 +27,6 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.ActivityControlHelper;
 import com.android.quickstep.OverviewCallbacks;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputMonitorCompat;
@@ -114,13 +113,5 @@
             mActivity.dispatchKeyEvent(ev);
         }
     }
-
-    public static InputConsumer newInstanceWithinActivityBounds(
-            ActivityControlHelper activityHelper) {
-        BaseDraggingActivity activity = activityHelper.getCreatedActivity();
-        if (activity == null) {
-            return InputConsumer.NO_OP;
-        }
-        return new OverviewInputConsumer(activity, null, true);
-    }
 }
+
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
new file mode 100644
index 0000000..56cba21
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.inputconsumers;
+
+import android.view.MotionEvent;
+
+import com.android.quickstep.SwipeSharedState;
+
+/**
+ * A NO_OP input consumer which also resets any pending gesture
+ */
+public class ResetGestureInputConsumer implements InputConsumer {
+
+    private final SwipeSharedState mSwipeSharedState;
+
+    public ResetGestureInputConsumer(SwipeSharedState swipeSharedState) {
+        mSwipeSharedState = swipeSharedState;
+    }
+
+    @Override
+    public int getType() {
+        return TYPE_RESET_GESTURE;
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN
+                && mSwipeSharedState.getActiveListener() != null) {
+            mSwipeSharedState.clearAllState();
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
index 19a4963..6dc672e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -192,6 +192,7 @@
             float cornerRadius = 0f;
             float scale = Math.max(params.currentRect.width(), mTargetRect.width()) / crop.width();
             if (app.mode == targetSet.targetMode) {
+                alpha = mTaskAlphaCallback.apply(app, params.targetAlpha);
                 if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
                     mTmpMatrix.setRectToRect(mSourceRect, params.currentRect, ScaleToFit.FILL);
                     mTmpMatrix.postTranslate(app.position.x, app.position.y);
@@ -208,8 +209,11 @@
                         }
                         mCurrentCornerRadius = cornerRadius;
                     }
+                } else if (targetSet.hasRecents) {
+                    // If home has a different target then recents, reverse anim the
+                    // home target.
+                    alpha = 1 - (progress * params.targetAlpha);
                 }
-                alpha = mTaskAlphaCallback.apply(app, params.targetAlpha);
             } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && launcherOnTop) {
                 crop = null;
                 layer = Integer.MAX_VALUE;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
index 94e704a..83601e6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
@@ -49,6 +49,8 @@
     private final Consumer<SwipeAnimationTargetSet> mOnFinishListener;
     private RecentsAnimationControllerCompat mController;
 
+    private boolean mCancelled;
+
     public RecentsAnimationListenerSet(boolean shouldMinimizeSplitScreen,
             Consumer<SwipeAnimationTargetSet> onFinishListener) {
         mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
@@ -75,11 +77,16 @@
         SwipeAnimationTargetSet targetSet = new SwipeAnimationTargetSet(controller, targets,
                 homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen,
                 mOnFinishListener);
-        Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
-            for (SwipeAnimationListener listener : getListeners()) {
-                listener.onRecentsAnimationStart(targetSet);
-            }
-        });
+
+        if (mCancelled) {
+            targetSet.cancelAnimation();
+        } else {
+            Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
+                for (SwipeAnimationListener listener : getListeners()) {
+                    listener.onRecentsAnimationStart(targetSet);
+                }
+            });
+        }
     }
 
     @Override
@@ -99,4 +106,9 @@
     private SwipeAnimationListener[] getListeners() {
         return mListeners.toArray(new SwipeAnimationListener[mListeners.size()]);
     }
+
+    public void cancelListener() {
+        mCancelled = true;
+        onAnimationCanceled(false);
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
index f5a9e8a..df9efa2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
@@ -36,7 +36,6 @@
     private final boolean mShouldMinimizeSplitScreen;
     private final Consumer<SwipeAnimationTargetSet> mOnFinishListener;
 
-
     public final RecentsAnimationControllerCompat controller;
     public final Rect homeContentInsets;
     public final Rect minimizedHomeBounds;
@@ -103,6 +102,10 @@
         return controller != null ? controller.screenshotTask(taskId) : null;
     }
 
+    public void cancelAnimation() {
+        finishController(false /* toRecents */, null, false /* sendUserLeaveHint */);
+    }
+
     public interface SwipeAnimationListener {
 
         void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 204dd56..5aab944 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -37,7 +37,7 @@
 
 import androidx.annotation.StringRes;
 
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseActivity;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -187,12 +187,12 @@
                         mTask.getTopComponent().getPackageName()).addFlags(
                         Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         try {
-            final Launcher launcher = Launcher.getLauncher(getContext());
+            final BaseActivity activity = BaseActivity.fromContext(getContext());
             final ActivityOptions options = ActivityOptions.makeScaleUpAnimation(
                     this, 0, 0,
                     getWidth(), getHeight());
-            launcher.startActivity(intent, options.toBundle());
-            launcher.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
+            activity.startActivity(intent, options.toBundle());
+            activity.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
                     LauncherLogProto.ControlType.APP_USAGE_SETTINGS, this);
         } catch (ActivityNotFoundException e) {
             Log.e(TAG, "Failed to open app usage settings for task "
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index e38a315..f66e401 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -892,7 +892,7 @@
         mRunningTaskTileHidden = isHidden;
         TaskView runningTask = getRunningTaskView();
         if (runningTask != null) {
-            runningTask.setAlpha(isHidden ? 0 : mContentAlpha);
+            runningTask.setStableAlpha(isHidden ? 0 : mContentAlpha);
         }
     }
 
@@ -1294,7 +1294,7 @@
         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
             TaskView child = getTaskViewAt(i);
             if (!mRunningTaskTileHidden || child.getTask().key.id != mRunningTaskId) {
-                getChildAt(i).setAlpha(alpha);
+                child.setStableAlpha(alpha);
             }
         }
         mClearAllButton.setContentAlpha(mContentAlpha);
@@ -1676,6 +1676,16 @@
         return mClearAllButton;
     }
 
+    /**
+     * @return How many pixels the running task is offset on the x-axis due to the current scrollX.
+     */
+    public float getScrollOffset() {
+        int startScroll = getScrollForPage(getRunningTaskIndex());
+        int offsetX = startScroll - getScrollX();
+        offsetX *= getScaleX();
+        return offsetX;
+    }
+
     public Consumer<MotionEvent> getEventDispatcher(RotationMode rotationMode) {
         if (rotationMode.isTransposed) {
             Matrix transform = new Matrix();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 8b8240d..6f10b42 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -252,7 +252,7 @@
         }
     }
 
-    protected TaskView getTaskView() {
+    public TaskView getTaskView() {
         return (TaskView) getParent();
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index bf3e91f..694d501 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -163,6 +163,7 @@
     private ObjectAnimator mIconAndDimAnimator;
     private float mIconScaleAnimStartProgress = 0;
     private float mFocusTransitionProgress = 1;
+    private float mStableAlpha = 1;
 
     private boolean mShowScreenshot;
 
@@ -353,14 +354,15 @@
             mIconLoadRequest = iconCache.updateIconInBackground(mTask,
                     (task) -> {
                         setIcon(task.icon);
-                        if (isRunningTask()) {
+                        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
                             getRecentsView().updateLiveTileIcon(task.icon);
                         }
                         mDigitalWellBeingToast.initialize(
                                 mTask,
                                 contentDescription -> {
                                     setContentDescription(contentDescription);
-                                    if (mDigitalWellBeingToast.getVisibility() == VISIBLE) {
+                                    if (mDigitalWellBeingToast.getVisibility() == VISIBLE
+                                            && getRecentsView() != null) {
                                         getRecentsView().onDigitalWellbeingToastShown();
                                     }
                                 });
@@ -468,7 +470,7 @@
         setTranslationX(0f);
         setTranslationY(0f);
         setTranslationZ(0);
-        setAlpha(1f);
+        setAlpha(mStableAlpha);
         setIconScaleAndDim(1);
     }
 
@@ -477,6 +479,11 @@
         setFullscreenProgress(0);
     }
 
+    public void setStableAlpha(float parentAlpha) {
+        mStableAlpha = parentAlpha;
+        setAlpha(mStableAlpha);
+    }
+
     @Override
     public void onRecycle() {
         resetViewTransforms();
@@ -498,6 +505,8 @@
         mSnapshotView.setDimAlpha(curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
         setCurveScale(getCurveScaleForCurveInterpolation(curveInterpolation));
 
+        float fade = Utilities.boundToRange(1.0f - 2 * scrollState.linearInterpolation, 0f, 1f);
+        mTaskFooterContainer.setAlpha(fade);
         if (mMenuView != null) {
             mMenuView.setPosition(getX() - getRecentsView().getScrollX(), getY());
             mMenuView.setScaleX(getScaleX());
@@ -716,6 +725,9 @@
     }
 
     public boolean isRunningTask() {
+        if (getRecentsView() == null) {
+            return false;
+        }
         return this == getRecentsView().getRunningTaskView();
     }
 
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 1869188..6a595a1 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -40,6 +40,8 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="vertical"
+        android:animateLayoutChanges="true"
+        android:forceHasOverlappingRendering="true"
         android:layout_gravity="bottom|center_horizontal">
         <FrameLayout
             android:id="@+id/proactive_suggest_container"
diff --git a/quickstep/src/com/android/launcher3/proxy/StartActivityParams.java b/quickstep/src/com/android/launcher3/proxy/StartActivityParams.java
index 1e8bd93..bee8bb8 100644
--- a/quickstep/src/com/android/launcher3/proxy/StartActivityParams.java
+++ b/quickstep/src/com/android/launcher3/proxy/StartActivityParams.java
@@ -31,7 +31,7 @@
 
     private static final String TAG = "StartActivityParams";
 
-    private final PendingIntent mCallback;
+    private final PendingIntent mPICallback;
     public final int requestCode;
 
     public Intent intent;
@@ -44,13 +44,17 @@
     public Bundle options;
 
     public StartActivityParams(Activity activity, int requestCode) {
-        mCallback = activity.createPendingResult(requestCode, new Intent(),
-                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
+        this(activity.createPendingResult(requestCode, new Intent(),
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT), requestCode);
+    }
+
+    public StartActivityParams(PendingIntent pendingIntent, int requestCode) {
+        this.mPICallback = pendingIntent;
         this.requestCode = requestCode;
     }
 
     private StartActivityParams(Parcel parcel) {
-        mCallback = parcel.readTypedObject(PendingIntent.CREATOR);
+        mPICallback = parcel.readTypedObject(PendingIntent.CREATOR);
         requestCode = parcel.readInt();
         intent = parcel.readTypedObject(Intent.CREATOR);
 
@@ -70,7 +74,7 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeTypedObject(mCallback, flags);
+        parcel.writeTypedObject(mPICallback, flags);
         parcel.writeInt(requestCode);
         parcel.writeTypedObject(intent, flags);
 
@@ -84,7 +88,9 @@
 
     public void deliverResult(Context context, int resultCode, Intent data) {
         try {
-            mCallback.send(context, resultCode, data);
+            if (mPICallback != null) {
+                mPICallback.send(context, resultCode, data);
+            }
         } catch (CanceledException e) {
             Log.e(TAG, "Unable to send back result", e);
         }
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index b17d8d6..8675c3e 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -53,6 +53,7 @@
 
     void onSwipeUpToRecentsComplete(T activity);
 
+    default void onSwipeUpToHomeComplete(T activity) { }
     void onAssistantVisibilityChanged(float visibility);
 
     @NonNull HomeAnimationFactory prepareHomeUI(T activity);
@@ -92,6 +93,8 @@
 
     boolean isInLiveTileMode();
 
+    void onLaunchTaskFailed(T activity);
+
     interface ActivityInitListener {
 
         void register();
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
index 0df4e94..1229293 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
@@ -33,20 +33,26 @@
     public final RemoteAnimationTargetCompat[] unfilteredApps;
     public final RemoteAnimationTargetCompat[] apps;
     public final int targetMode;
+    public final boolean hasRecents;
 
     public RemoteAnimationTargetSet(RemoteAnimationTargetCompat[] apps, int targetMode) {
         ArrayList<RemoteAnimationTargetCompat> filteredApps = new ArrayList<>();
+        boolean hasRecents = false;
         if (apps != null) {
             for (RemoteAnimationTargetCompat target : apps) {
                 if (target.mode == targetMode) {
                     filteredApps.add(target);
                 }
+
+                hasRecents |= target.activityType ==
+                        RemoteAnimationTargetCompat.ACTIVITY_TYPE_RECENTS;
             }
         }
 
         this.unfilteredApps = apps;
         this.apps = filteredApps.toArray(new RemoteAnimationTargetCompat[filteredApps.size()]);
         this.targetMode = targetMode;
+        this.hasRecents = hasRecents;
     }
 
     public RemoteAnimationTargetCompat findTask(int taskId) {
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 20fdff2..0135911 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
 import static com.android.launcher3.tapl.TestHelpers.getHomeIntentInPackage;
 import static com.android.launcher3.tapl.TestHelpers.getLauncherInMyProcess;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.resolveSystemApp;
 import static com.android.launcher3.util.rule.ShellCommandRule.disableHeadsUpNotification;
 import static com.android.launcher3.util.rule.ShellCommandRule.getLauncherCommand;
 import static com.android.quickstep.NavigationModeSwitchRule.Mode.THREE_BUTTON;
@@ -108,7 +109,7 @@
     @NavigationModeSwitch(mode = THREE_BUTTON)
     @Test
     public void goToOverviewFromApp() {
-        startAppFast("com.android.settings");
+        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
 
         mLauncher.getBackground().switchToOverview();
     }
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 424ffde..6455056 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -175,6 +175,10 @@
         mActivityFlags &= ~ACTIVITY_STATE_STARTED & ~ACTIVITY_STATE_USER_ACTIVE;
         mForceInvisible = 0;
         super.onStop();
+
+        // Reset the overridden sysui flags used for the task-swipe launch animation, this is a
+        // catch all for if we do not get resumed (and therefore not paused below)
+        getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0);
     }
 
     @Override
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 151e2ca..4e905c7 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -461,13 +461,18 @@
     private void onIdpChanged(InvariantDeviceProfile idp) {
         mUserEventDispatcher = null;
 
+        DeviceProfile oldWallpaperProfile = getWallpaperDeviceProfile();
         initDeviceProfile(idp);
         dispatchDeviceProfileChanged();
         reapplyUi();
         mDragLayer.recreateControllers();
 
-        // TODO: We can probably avoid rebind when only screen size changed.
-        rebindModel();
+        // Calling onSaveInstanceState ensures that static cache used by listWidgets is
+        // initialized properly.
+        onSaveInstanceState(new Bundle());
+        if (oldWallpaperProfile != getWallpaperDeviceProfile()) {
+            rebindModel();
+        }
     }
 
     public void onAssistantVisibilityChanged(float visibility) {
@@ -889,7 +894,6 @@
             mLauncherCallbacks.onStart();
         }
         mAppWidgetHost.setListenIfResumed(true);
-        NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
         RaceConditionTracker.onEvent(ON_START_EVT, EXIT);
     }
 
@@ -909,6 +913,9 @@
             // Refresh shortcuts if the permission changed.
             mModel.refreshShortcutsIfRequired();
 
+            // Set the notification listener and fetch updated notifications when we resume
+            NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
+
             DiscoveryBounce.showForHomeIfNeeded(this);
 
             if (mPendingActivityRequestCode != -1 && isInState(NORMAL)) {
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 49b380b..f964b8d 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -8,18 +8,22 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.os.Build;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewDebug;
+import android.view.WindowInsets;
 
 import java.util.Collections;
 import java.util.List;
 
 public class LauncherRootView extends InsettableFrameLayout {
 
+    private final Rect mTempRect = new Rect();
+
     private final Launcher mLauncher;
 
     private final Paint mOpaquePaint;
@@ -56,9 +60,7 @@
         super.onFinishInflate();
     }
 
-    @TargetApi(23)
-    @Override
-    protected boolean fitSystemWindows(Rect insets) {
+    private void handleSystemWindowInsets(Rect insets) {
         mConsumedInsets.setEmpty();
         boolean drawInsetBar = false;
         if (mLauncher.isInMultiWindowMode()
@@ -66,13 +68,13 @@
             mConsumedInsets.left = insets.left;
             mConsumedInsets.right = insets.right;
             mConsumedInsets.bottom = insets.bottom;
-            insets = new Rect(0, insets.top, 0, 0);
+            insets.set(0, insets.top, 0, 0);
             drawInsetBar = true;
         } else  if ((insets.right > 0 || insets.left > 0) &&
                 getContext().getSystemService(ActivityManager.class).isLowRamDevice()) {
             mConsumedInsets.left = insets.left;
             mConsumedInsets.right = insets.right;
-            insets = new Rect(0, insets.top, 0, insets.bottom);
+            insets.set(0, insets.top, 0, insets.bottom);
             drawInsetBar = true;
         }
 
@@ -99,8 +101,19 @@
         if (resetState) {
             mLauncher.getStateManager().reapplyState(true /* cancelCurrentAnimation */);
         }
+    }
 
-        return false; // Let children get the full insets
+    @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        mTempRect.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
+                insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
+        handleSystemWindowInsets(mTempRect);
+        if (Utilities.ATLEAST_Q) {
+            return insets.inset(mConsumedInsets.left, mConsumedInsets.top,
+                    mConsumedInsets.right, mConsumedInsets.bottom);
+        } else {
+            return insets.replaceSystemWindowInsets(mTempRect);
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index fe6b522..8b03691 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -49,6 +49,7 @@
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.anim.PropertySetter.AnimatedPropertySetter;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.uioverrides.UiFactory;
 
@@ -496,6 +497,8 @@
         for (int i = mListeners.size() - 1; i >= 0; i--) {
             mListeners.get(i).onStateTransitionComplete(state);
         }
+
+        AccessibilityManagerCompat.sendStateEventToTest(mLauncher, state.ordinal);
     }
 
     public void onWindowFocusChanged() {
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index e204c63..c719c1c 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.states.InternalStateHandler;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.widget.PendingItemDragHelper;
 
 import java.util.UUID;
@@ -136,6 +137,9 @@
 
     @Override
     public boolean shouldStartDrag(double distanceDragged) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_DRAG_TAG, "BIDL.shouldStartDrag");
+        }
         // Stay in pre-drag mode, if workspace is locked.
         return !mLauncher.isWorkspaceLocked();
     }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 7b14fa2..9719a18 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -36,6 +36,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.View;
@@ -67,6 +68,7 @@
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.PackageUserKey;
@@ -447,6 +449,11 @@
 
             @Override
             public boolean shouldStartDrag(double distanceDragged) {
+                if (TestProtocol.sDebugTracing) {
+                    Log.d(TestProtocol.NO_DRAG_TAG,
+                            "createPreDragCondition().shouldStartDrag " + distanceDragged + ", "
+                                    + mStartDragThreshold);
+                }
                 return distanceDragged > mStartDragThreshold;
             }
 
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 4e5f7a5..6f53140 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -43,7 +43,6 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
@@ -541,7 +540,6 @@
                 android.util.Log.e(
                         TestProtocol.NO_ALLAPPS_EVENT_TAG, "onSwipeInteractionCompleted 2");
             }
-            AccessibilityManagerCompat.sendStateEventToTest(mLauncher, targetState.ordinal);
         }
     }
 
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 64fe2d7..abc93cd 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -395,7 +395,7 @@
                         DEFAULT_UI_TIMEOUT));
     }
 
-    protected static String resolveSystemApp(String category) {
+    public static String resolveSystemApp(String category) {
         return getInstrumentation().getContext().getPackageManager().resolveActivity(
                 new Intent(Intent.ACTION_MAIN).addCategory(category),
                 PackageManager.MATCH_SYSTEM_ONLY).
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 5190f00..d4bdafa 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -20,7 +20,6 @@
 
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
-import androidx.test.uiautomator.UiDevice;
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
@@ -70,11 +69,16 @@
      * Drags an object to the center of homescreen.
      */
     public Workspace dragToWorkspace() {
-        final UiDevice device = mLauncher.getDevice();
+        final Point launchableCenter = getObject().getVisibleCenter();
+        final Point displaySize = mLauncher.getRealDisplaySize();
+        final int width = displaySize.x / 2;
         Workspace.dragIconToWorkspace(
                 mLauncher,
                 this,
-                new Point(device.getDisplayWidth() / 2, device.getDisplayHeight() / 2),
+                new Point(
+                        launchableCenter.x >= width ?
+                                launchableCenter.x - width / 2 : launchableCenter.x + width / 2,
+                        displaySize.y / 2),
                 getLongPressIndicator());
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "dragged launchable to workspace")) {