Merge "Task footer - use temporary motion for show and hide." into ub-launcher3-qt-r1-dev
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index a6c4445..d66af1a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -98,9 +98,6 @@
         if (mStartState == OVERVIEW || mStartState == ALL_APPS) {
             return true;
         }
-        if (!mLauncher.hasWindowFocus()) {
-            return true;
-        }
         if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
             return true;
         }
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 d0a41f3..7809e45 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -16,16 +16,23 @@
 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;
 
+import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -36,6 +43,7 @@
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.os.UserHandle;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -43,16 +51,21 @@
 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;
 import com.android.quickstep.SysUINavigationMode.Mode;
@@ -98,6 +111,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) {
@@ -154,18 +174,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();
             }
         };
@@ -174,6 +199,9 @@
     @Override
     public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible,
             boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "prepareRecentsUI");
+        }
         final LauncherState startState = activity.getStateManager().getState();
 
         LauncherState resetState = startState;
@@ -196,7 +224,8 @@
         return new AnimationFactory() {
             private Animator mShelfAnim;
             private ShelfAnimState mShelfState;
-            private Animator mAttachToWindowAnim;
+            private Animator mAttachToWindowFadeAnim;
+            private SpringAnimation mAttachToWindowTranslationXAnim;
             private boolean mIsAttachedToWindow;
 
             @Override
@@ -262,20 +291,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(DAMPING_RATIO_LOW_BOUNCY)
+                                .setStiffness(STIFFNESS_LOW));
+                    }
+                    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();
             }
         };
     }
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 cbc94ba..dc35440 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -62,6 +62,7 @@
 
 import androidx.annotation.BinderThread;
 
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
@@ -79,6 +80,7 @@
 import com.android.quickstep.inputconsumers.InputConsumer;
 import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
+import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer;
 import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -566,10 +568,9 @@
             info.id = mSwipeSharedState.nextRunningTaskId;
             return createOtherActivityInputConsumer(event, info);
         } else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
-            return OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
-        } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
-                activityControl.isInLiveTileMode()) {
-            return OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
+            return createOverviewInputConsumer(event);
+        } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityControl.isInLiveTileMode()) {
+            return createOverviewInputConsumer(event);
         } else if (mGestureBlockingActivity != null && runningTaskInfo != null
                 && mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) {
             return InputConsumer.NO_OP;
@@ -578,20 +579,24 @@
         }
     }
 
+    private boolean disableHorizontalSwipe(MotionEvent event) {
+        // mExclusionRegion can change on binder thread, use a local instance here.
+        Region exclusionRegion = mExclusionRegion;
+        return mMode == Mode.NO_BUTTON && exclusionRegion != null
+                && exclusionRegion.contains((int) event.getX(), (int) event.getY());
+    }
+
     private OtherActivityInputConsumer createOtherActivityInputConsumer(MotionEvent event,
             RunningTaskInfo runningTaskInfo) {
         final ActivityControlHelper activityControl =
                 mOverviewComponentObserver.getActivityControlHelper();
         boolean shouldDefer = activityControl.deferStartingActivity(mActiveNavBarRegion, event);
 
-        // mExclusionRegion can change on binder thread, use a local instance here.
-        Region exclusionRegion = mExclusionRegion;
-        boolean disableHorizontalSwipe = mMode == Mode.NO_BUTTON && exclusionRegion != null
-                && exclusionRegion.contains((int) event.getX(), (int) event.getY());
         return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel,
                 mOverviewComponentObserver.getOverviewIntent(), activityControl,
                 shouldDefer, mOverviewCallbacks, mInputConsumer, this::onConsumerInactive,
-                mSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion, disableHorizontalSwipe);
+                mSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion,
+                disableHorizontalSwipe(event));
     }
 
     private InputConsumer createDeviceLockedInputConsumer(RunningTaskInfo taskInfo) {
@@ -603,6 +608,23 @@
         }
     }
 
+    public InputConsumer createOverviewInputConsumer(MotionEvent event) {
+        final ActivityControlHelper activityControl =
+                mOverviewComponentObserver.getActivityControlHelper();
+        BaseDraggingActivity activity = activityControl.getCreatedActivity();
+        if (activity == null) {
+            return InputConsumer.NO_OP;
+        }
+
+        if (activity.getRootView().hasWindowFocus()) {
+            return new OverviewInputConsumer(activity, mInputMonitorCompat,
+                    false /* startingInActivityBounds */);
+        } else {
+            return new OverviewWithoutFocusInputConsumer(this, mInputMonitorCompat,
+                    disableHorizontalSwipe(event));
+        }
+    }
+
     /**
      * To be called by the consumer when it's no longer active.
      */
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 87b7326..7d17f85 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;
@@ -59,6 +58,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.util.Log;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 import android.view.View;
@@ -83,6 +83,7 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -216,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.
@@ -267,6 +268,7 @@
     private MultiStateCallback mStateCallback;
     // Used to control launcher components throughout the swipe gesture.
     private AnimatorPlaybackController mLauncherTransitionController;
+    private boolean mHasLauncherTransitionControllerStarted;
 
     private T mActivity;
     private RecentsView mRecentsView;
@@ -457,17 +459,32 @@
     }
 
     private void onLauncherStart(final T activity) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart");
+        }
         if (mActivity != activity) {
             return;
         }
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 1");
+        }
         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
             return;
         }
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 2");
+        }
 
         // If we've already ended the gesture and are going home, don't prepare recents UI,
         // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
         if (mGestureEndTarget != HOME) {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 3");
+            }
             Runnable initAnimFactory = () -> {
+                if (TestProtocol.sDebugTracing) {
+                    Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 4");
+                }
                 mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
                         mWasLauncherAlreadyVisible, true,
                         this::onAnimatorPlaybackControllerCreated);
@@ -477,8 +494,14 @@
                 // Launcher is visible, but might be about to stop. Thus, if we prepare recents
                 // now, it might get overridden by moveToRestState() in onStop(). To avoid this,
                 // wait until the next gesture (and possibly launcher) starts.
+                if (TestProtocol.sDebugTracing) {
+                    Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 5");
+                }
                 mStateCallback.addCallback(STATE_GESTURE_STARTED, initAnimFactory);
             } else {
+                if (TestProtocol.sDebugTracing) {
+                    Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 6");
+                }
                 initAnimFactory.run();
             }
         }
@@ -578,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() {
@@ -601,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)) {
@@ -609,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);
@@ -647,8 +672,7 @@
     }
 
     private void buildAnimationController() {
-        if (mGestureEndTarget == HOME || (mLauncherTransitionController != null
-                && mLauncherTransitionController.getAnimationPlayer().isStarted())) {
+        if (mGestureEndTarget == HOME || mHasLauncherTransitionControllerStarted) {
             // We don't want a new mLauncherTransitionController if mGestureEndTarget == HOME (it
             // has its own animation) or if we're already animating the current controller.
             return;
@@ -678,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);
@@ -858,7 +876,9 @@
             setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
         }
 
-        return OverviewInputConsumer.newInstance(mActivityControlHelper, null, true);
+        BaseDraggingActivity activity = mActivityControlHelper.getCreatedActivity();
+        return activity == null
+                ? InputConsumer.NO_OP : new OverviewInputConsumer(activity, null, true);
     }
 
     private void endRunningWindowAnim() {
@@ -1122,6 +1142,7 @@
             }
             mLauncherTransitionController.getAnimationPlayer().start();
         }
+        mHasLauncherTransitionControllerStarted = true;
     }
 
     /**
@@ -1191,6 +1212,9 @@
                 if (mRecentsView != null) {
                     mRecentsView.post(mRecentsView::resetTaskVisuals);
                 }
+                // Make sure recents is in its final state
+                maybeUpdateRecentsAttachedState(false);
+                mActivityControlHelper.onSwipeUpToHomeComplete(mActivity);
             }
         });
         return anim;
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 6e7cb8f..489eb27 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
@@ -31,6 +31,7 @@
     int TYPE_DEVICE_LOCKED = 1 << 4;
     int TYPE_ACCESSIBILITY = 1 << 5;
     int TYPE_SCREEN_PINNED = 1 << 6;
+    int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7;
 
     InputConsumer NO_OP = () -> TYPE_NO_OP;
 
@@ -78,6 +79,8 @@
                 return "ACCESSIBILITY";
             case TYPE_SCREEN_PINNED:
                 return "SCREEN_PINNED";
+            case TYPE_OVERVIEW_WITHOUT_FOCUS:
+                return "TYPE_OVERVIEW_WITHOUT_FOCUS";
             default:
                 return "NO_OP";
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 0ed4c99..69b25db 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -28,7 +28,6 @@
 import static com.android.launcher3.uioverrides.RecentsUiFactory.ROTATION_SEASCAPE;
 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
-import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
@@ -43,10 +42,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.view.MotionEvent;
-import android.view.Surface;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
-import android.view.WindowManager;
 
 import androidx.annotation.UiThread;
 
@@ -65,6 +62,7 @@
 import com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget;
 import com.android.quickstep.util.CachedEventDispatcher;
 import com.android.quickstep.util.MotionPauseDetector;
+import com.android.quickstep.util.NavBarPosition;
 import com.android.quickstep.util.RecentsAnimationListenerSet;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.BackgroundExecutor;
@@ -95,7 +93,7 @@
     private final SysUINavigationMode.Mode mMode;
     private final RectF mSwipeTouchRegion;
 
-    private final int mDisplayRotation;
+    private final NavBarPosition mNavBarPosition;
 
     private final Consumer<OtherActivityInputConsumer> mOnCompleteCallback;
     private final MotionPauseDetector mMotionPauseDetector;
@@ -157,7 +155,7 @@
         mInputConsumer = inputConsumer;
         mSwipeSharedState = swipeSharedState;
 
-        mDisplayRotation = getSystemService(WindowManager.class).getDefaultDisplay().getRotation();
+        mNavBarPosition = new NavBarPosition(base);
         mDragSlop = QuickStepContract.getQuickStepDragSlopPx();
         float slop = QuickStepContract.getQuickStepTouchSlopPx();
         mSquaredTouchSlop = slop * slop;
@@ -188,9 +186,7 @@
         if (mPassedDragSlop && mInteractionHandler != null
                 && !mRecentsViewDispatcher.hasConsumer()) {
             mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher(
-                    isNavBarOnLeft()
-                            ? ROTATION_SEASCAPE
-                            : (isNavBarOnRight() ? ROTATION_LANDSCAPE : RotationMode.NORMAL)));
+                    mNavBarPosition.getRotationMode()));
         }
         int edgeFlags = ev.getEdgeFlags();
         ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR);
@@ -329,14 +325,6 @@
         mInteractionHandler.onGestureStarted();
     }
 
-    private boolean isNavBarOnRight() {
-        return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90;
-    }
-
-    private boolean isNavBarOnLeft() {
-        return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270;
-    }
-
     private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
         TOUCH_INTERACTION_LOG.addLog("startRecentsAnimation");
 
@@ -382,8 +370,8 @@
                         ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
                 float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
                 float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
-                float velocity = isNavBarOnRight() ? velocityX
-                        : isNavBarOnLeft() ? -velocityX
+                float velocity = mNavBarPosition.isRightEdge() ? velocityX
+                        : mNavBarPosition.isLeftEdge() ? -velocityX
                                 : velocityY;
 
                 mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
@@ -444,9 +432,9 @@
     }
 
     private float getDisplacement(MotionEvent ev) {
-        if (isNavBarOnRight()) {
+        if (mNavBarPosition.isRightEdge()) {
             return ev.getX() - mDownPos.x;
-        } else if (isNavBarOnLeft()) {
+        } else if (mNavBarPosition.isLeftEdge()) {
             return mDownPos.x - ev.getX();
         } else {
             return ev.getY() - mDownPos.y;
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 bab3c71..4851e67 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
@@ -51,7 +51,7 @@
     private final boolean mStartingInActivityBounds;
     private boolean mTargetHandledTouch;
 
-    OverviewInputConsumer(T activity, @Nullable InputMonitorCompat inputMonitor,
+    public OverviewInputConsumer(T activity, @Nullable InputMonitorCompat inputMonitor,
             boolean startingInActivityBounds) {
         mActivity = activity;
         mInputMonitor = inputMonitor;
@@ -115,12 +115,12 @@
         }
     }
 
-    public static InputConsumer newInstance(ActivityControlHelper activityHelper,
-            @Nullable InputMonitorCompat inputMonitor, boolean startingInActivityBounds) {
+    public static InputConsumer newInstanceWithinActivityBounds(
+            ActivityControlHelper activityHelper) {
         BaseDraggingActivity activity = activityHelper.getCreatedActivity();
         if (activity == null) {
             return InputConsumer.NO_OP;
         }
-        return new OverviewInputConsumer(activity, inputMonitor, startingInActivityBounds);
+        return new OverviewInputConsumer(activity, null, true);
     }
 }
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
new file mode 100644
index 0000000..425b8b6
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -0,0 +1,151 @@
+/*
+ * 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 static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static com.android.launcher3.Utilities.squaredHypot;
+import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.Utilities;
+import com.android.quickstep.OverviewCallbacks;
+import com.android.quickstep.util.NavBarPosition;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+public class OverviewWithoutFocusInputConsumer implements InputConsumer {
+
+    private final InputMonitorCompat mInputMonitor;
+    private final boolean mDisableHorizontalSwipe;
+    private final PointF mDownPos = new PointF();
+    private final float mSquaredTouchSlop;
+    private final Context mContext;
+    private final NavBarPosition mNavBarPosition;
+
+    private boolean mInterceptedTouch;
+    private VelocityTracker mVelocityTracker;
+
+
+    public OverviewWithoutFocusInputConsumer(Context context, InputMonitorCompat inputMonitor,
+            boolean disableHorizontalSwipe) {
+        mInputMonitor = inputMonitor;
+        mDisableHorizontalSwipe = disableHorizontalSwipe;
+        mContext = context;
+        mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
+        mNavBarPosition = new NavBarPosition(context);
+
+        mVelocityTracker = VelocityTracker.obtain();
+    }
+
+    @Override
+    public int getType() {
+        return TYPE_OVERVIEW_WITHOUT_FOCUS;
+    }
+
+    @Override
+    public boolean allowInterceptByParent() {
+        return !mInterceptedTouch;
+    }
+
+    private void endTouchTracking() {
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent ev) {
+        if (mVelocityTracker == null) {
+            return;
+        }
+
+        mVelocityTracker.addMovement(ev);
+        switch (ev.getActionMasked()) {
+            case ACTION_DOWN: {
+                mDownPos.set(ev.getX(), ev.getY());
+                break;
+            }
+            case ACTION_MOVE: {
+                if (!mInterceptedTouch) {
+                    float displacementX = ev.getX() - mDownPos.x;
+                    float displacementY = ev.getY() - mDownPos.y;
+                    if (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop) {
+                        if (mDisableHorizontalSwipe
+                                && Math.abs(displacementX) > Math.abs(displacementY)) {
+                            // Horizontal gesture is not allowed in this region
+                            endTouchTracking();
+                            break;
+                        }
+
+                        mInterceptedTouch = true;
+
+                        if (mInputMonitor != null) {
+                            mInputMonitor.pilferPointers();
+                        }
+                    }
+                }
+                break;
+            }
+
+            case ACTION_CANCEL:
+                endTouchTracking();
+                break;
+
+            case ACTION_UP: {
+                finishTouchTracking(ev);
+                endTouchTracking();
+                break;
+            }
+        }
+    }
+
+    private void finishTouchTracking(MotionEvent ev) {
+        mVelocityTracker.computeCurrentVelocity(100);
+        float velocityX = mVelocityTracker.getXVelocity();
+        float velocityY = mVelocityTracker.getYVelocity();
+        float velocity = mNavBarPosition.isRightEdge()
+                ? -velocityX : (mNavBarPosition.isLeftEdge() ? velocityX : -velocityY);
+
+        final boolean triggerQuickstep;
+        if (Math.abs(velocity) >= ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) {
+            triggerQuickstep = velocity > 0;
+        } else {
+            float displacementX = mDisableHorizontalSwipe ? 0 : (ev.getX() - mDownPos.x);
+            float displacementY = ev.getY() - mDownPos.y;
+            triggerQuickstep = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
+        }
+
+        if (triggerQuickstep) {
+            OverviewCallbacks.get(mContext).closeAllWindows();
+            ActivityManagerWrapper.getInstance()
+                    .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+            TOUCH_INTERACTION_LOG.addLog("startQuickstep");
+        } else {
+            // ignore
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java
new file mode 100644
index 0000000..3ce341d
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java
@@ -0,0 +1,55 @@
+/*
+ * 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.util;
+
+import static com.android.launcher3.uioverrides.RecentsUiFactory.ROTATION_LANDSCAPE;
+import static com.android.launcher3.uioverrides.RecentsUiFactory.ROTATION_SEASCAPE;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+
+import android.content.Context;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import com.android.launcher3.graphics.RotationMode;
+import com.android.quickstep.SysUINavigationMode;
+
+/**
+ * Utility class to check nav bar position
+ */
+public class NavBarPosition {
+
+    private final SysUINavigationMode.Mode mMode;
+    private final int mDisplayRotation;
+
+    public NavBarPosition(Context context) {
+        mMode = SysUINavigationMode.getMode(context);
+        mDisplayRotation = context.getSystemService(WindowManager.class)
+                .getDefaultDisplay().getRotation();
+    }
+
+    public boolean isRightEdge() {
+        return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90;
+    }
+
+    public boolean isLeftEdge() {
+        return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270;
+    }
+
+    public RotationMode getRotationMode() {
+        return isLeftEdge() ? ROTATION_SEASCAPE
+                : (isRightEdge() ? ROTATION_LANDSCAPE : RotationMode.NORMAL);
+    }
+}
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..90e123e 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
@@ -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/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index dcf2e3c..46161cb 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -108,7 +108,7 @@
     // Use a shorter duration for x or y translation to create a curve effect
     private static final long APP_LAUNCH_CURVED_DURATION = 250;
     private static final long APP_LAUNCH_ALPHA_DURATION = 50;
-    private static final long APP_LAUNCH_ALPHA_START_DELAY = 50;
+    private static final long APP_LAUNCH_ALPHA_START_DELAY = 25;
 
     // We scale the durations for the downward app launch animations (minus the scale animation).
     private static final float APP_LAUNCH_DOWN_DUR_SCALE_FACTOR = 0.8f;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 4891746..97cd38a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -47,7 +47,6 @@
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.QuickstepAppTransitionManagerImpl;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.proxy.ProxyActivityStarter;
 import com.android.launcher3.proxy.StartActivityParams;
 import com.android.quickstep.OverviewInteractionState;
@@ -64,10 +63,14 @@
 
 public class UiFactory extends RecentsUiFactory {
 
-    public static Runnable enableLiveTouchControllerChanges(DragLayer dl) {
-        NavigationModeChangeListener listener = m -> dl.recreateControllers();
-        SysUINavigationMode mode = SysUINavigationMode.INSTANCE.get(dl.getContext());
-        mode.addModeChangeListener(listener);
+    public static Runnable enableLiveUIChanges(Launcher launcher) {
+        NavigationModeChangeListener listener = m -> {
+            launcher.getDragLayer().recreateControllers();
+            launcher.getRotationHelper().setRotationHadDifferentUI(m != Mode.NO_BUTTON);
+        };
+        SysUINavigationMode mode = SysUINavigationMode.INSTANCE.get(launcher);
+        SysUINavigationMode.Mode m = mode.addModeChangeListener(listener);
+        launcher.getRotationHelper().setRotationHadDifferentUI(m != Mode.NO_BUTTON);
         return () -> mode.removeModeChangeListener(listener);
     }
 
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index b17d8d6..dcc1ace 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);
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index c2c568b..a412b39 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -18,13 +18,17 @@
 
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 
-import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
 
 /**
  * Base class for all instrumentation tests that deal with Quickstep.
  */
 public abstract class AbstractQuickStepTest extends AbstractLauncherUiTest {
-    @Rule
-    public TestRule mNavigationModeSwitcher = new NavigationModeSwitchRule(mLauncher);
+    @Override
+    protected TestRule getRulesInsideActivityMonitor() {
+        return RuleChain.
+                outerRule(new NavigationModeSwitchRule(mLauncher)).
+                around(super.getRulesInsideActivityMonitor());
+    }
 }
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index 835fee2..a1033f0 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -56,9 +56,9 @@
             android:paddingBottom="@dimen/folder_label_padding_bottom"
             android:paddingTop="@dimen/folder_label_padding_top"
             android:singleLine="true"
-            android:textColor="?android:attr/textColorTertiary"
+            android:textColor="?attr/folderTextColor"
             android:textColorHighlight="?android:attr/colorControlHighlight"
-            android:textColorHint="?android:attr/textColorHint"
+            android:textColorHint="?attr/folderTextColor"
             android:textSize="@dimen/folder_label_text_size" />
 
         <com.android.launcher3.pageindicators.PageIndicatorDots
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 855535b..151e2ca 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -80,6 +80,7 @@
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.PropertyListBuilder;
@@ -879,6 +880,9 @@
 
     @Override
     protected void onStart() {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "Launcher.onStart");
+        }
         RaceConditionTracker.onEvent(ON_START_EVT, ENTER);
         super.onStart();
         if (mLauncherCallbacks != null) {
@@ -1063,7 +1067,7 @@
 
         // Setup the drag layer
         mDragLayer.setup(mDragController, mWorkspace);
-        mCancelTouchController = UiFactory.enableLiveTouchControllerChanges(mDragLayer);
+        mCancelTouchController = UiFactory.enableLiveUIChanges(this);
 
         mWorkspace.setup(mDragController);
         // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
@@ -2238,8 +2242,9 @@
         }
         mPendingExecutor = executor;
         if (!isInState(ALL_APPS)) {
-            mAppsView.getAppsStore().setDeferUpdates(true);
-            mPendingExecutor.execute(() -> mAppsView.getAppsStore().setDeferUpdates(false));
+            mAppsView.getAppsStore().enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW);
+            mPendingExecutor.execute(() -> mAppsView.getAppsStore().disableDeferUpdates(
+                    AllAppsStore.DEFER_UPDATES_NEXT_DRAW));
         }
 
         executor.attachTo(this);
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 0d43e21..d68ff44 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -628,11 +628,12 @@
         final boolean result = super.dispatchTouchEvent(ev);
         switch (ev.getActionMasked()) {
             case MotionEvent.ACTION_DOWN:
-                if (result) mAllAppsStore.setDeferUpdates(true);
+                if (result) mAllAppsStore.enableDeferUpdates(
+                        AllAppsStore.DEFER_UPDATES_USER_INTERACTION);
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                mAllAppsStore.setDeferUpdates(false);
+                mAllAppsStore.disableDeferUpdates(AllAppsStore.DEFER_UPDATES_USER_INTERACTION);
                 break;
         }
         return result;
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 8e7fec8..160042e 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -29,7 +29,6 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -38,12 +37,19 @@
  */
 public class AllAppsStore {
 
+    // Defer updates flag used to defer all apps updates to the next draw.
+    public static final int DEFER_UPDATES_NEXT_DRAW = 1 << 0;
+    // Defer updates flag used to defer all apps updates while the user interacts with all apps.
+    public static final int DEFER_UPDATES_USER_INTERACTION = 1 << 1;
+    // Defer updates flag used to defer all apps updates by a test's request.
+    public static final int DEFER_UPDATES_TEST = 1 << 2;
+
     private PackageUserKey mTempKey = new PackageUserKey(null, null);
     private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
     private final List<OnUpdateListener> mUpdateListeners = new ArrayList<>();
     private final ArrayList<ViewGroup> mIconContainers = new ArrayList<>();
 
-    private boolean mDeferUpdates = false;
+    private int mDeferUpdatesFlags = 0;
     private boolean mUpdatePending = false;
 
     public Collection<AppInfo> getApps() {
@@ -62,17 +68,22 @@
         return mComponentToAppMap.get(key);
     }
 
-    public void setDeferUpdates(boolean deferUpdates) {
-        if (mDeferUpdates != deferUpdates) {
-            mDeferUpdates = deferUpdates;
+    public void enableDeferUpdates(int flag) {
+        mDeferUpdatesFlags |= flag;
+    }
 
-            if (!mDeferUpdates && mUpdatePending) {
-                notifyUpdate();
-                mUpdatePending = false;
-            }
+    public void disableDeferUpdates(int flag) {
+        mDeferUpdatesFlags &= ~flag;
+        if (mDeferUpdatesFlags == 0 && mUpdatePending) {
+            notifyUpdate();
+            mUpdatePending = false;
         }
     }
 
+    public int getDeferUpdatesFlags() {
+        return mDeferUpdatesFlags;
+    }
+
     /**
      * Adds or updates existing apps in the list
      */
@@ -95,7 +106,7 @@
 
 
     private void notifyUpdate() {
-        if (mDeferUpdates) {
+        if (mDeferUpdatesFlags != 0) {
             mUpdatePending = true;
             return;
         }
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/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 9d3c8f7..72a1abb 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -27,6 +27,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
+import android.util.Log;
 import android.view.DragEvent;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -491,6 +492,16 @@
         mLastTouch[0] = x;
         mLastTouch[1] = y;
 
+        if (TestProtocol.sDebugTracing) {
+           Log.d(TestProtocol.NO_DRAG_TAG,
+                    "handleMoveEvent Conditions " +
+                            mIsInPreDrag + ", " +
+                            (mIsInPreDrag && mOptions.preDragCondition != null) + ", " +
+                            (mIsInPreDrag && mOptions.preDragCondition != null
+                                    && mOptions.preDragCondition.shouldStartDrag(
+                                    mDistanceSinceScroll)));
+        }
+
         if (mIsInPreDrag && mOptions.preDragCondition != null
                 && mOptions.preDragCondition.shouldStartDrag(mDistanceSinceScroll)) {
             if (TestProtocol.sDebugTracing) {
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/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index cd96d6e..abf90e2 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -73,7 +73,8 @@
 
     // This is used to defer setting rotation flags until the activity is being created
     private boolean mInitialized;
-    public boolean mDestroyed;
+    private boolean mDestroyed;
+    private boolean mRotationHasDifferentUI;
 
     private int mLastActivityFlags = -1;
 
@@ -92,8 +93,12 @@
         }
     }
 
+    public void setRotationHadDifferentUI(boolean rotationHasDifferentUI) {
+        mRotationHasDifferentUI = rotationHasDifferentUI;
+    }
+
     public boolean homeScreenCanRotate() {
-        return mIgnoreAutoRotateSettings || mAutoRotateEnabled
+        return mRotationHasDifferentUI || mIgnoreAutoRotateSettings || mAutoRotateEnabled
                 || mStateHandlerRequest != REQUEST_NONE
                 || mLauncher.getDeviceProfile().isMultiWindowMode;
     }
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index b8476aa..d2e1961 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -23,9 +23,13 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.util.ResourceBasedOverride;
 
+import java.util.concurrent.ExecutionException;
+
 public class TestInformationHandler implements ResourceBasedOverride {
 
     public static TestInformationHandler newInstance(Context context) {
@@ -77,6 +81,32 @@
             case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
                 TestProtocol.sDebugTracing = false;
                 break;
+
+            case TestProtocol.REQUEST_FREEZE_APP_LIST:
+                new MainThreadExecutor().execute(() ->
+                        mLauncher.getAppsView().getAppsStore().enableDeferUpdates(
+                                AllAppsStore.DEFER_UPDATES_TEST));
+                break;
+
+            case TestProtocol.REQUEST_UNFREEZE_APP_LIST:
+                new MainThreadExecutor().execute(() ->
+                        mLauncher.getAppsView().getAppsStore().disableDeferUpdates(
+                                AllAppsStore.DEFER_UPDATES_TEST));
+                break;
+
+            case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
+                try {
+                    final int deferUpdatesFlags = new MainThreadExecutor().submit(() ->
+                            mLauncher.getAppsView().getAppsStore().getDeferUpdatesFlags()).get();
+                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                            deferUpdatesFlags);
+                } catch (ExecutionException e) {
+                    throw new RuntimeException(e);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                break;
+            }
         }
         return response;
     }
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 99efb22..6ffc2d9 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -57,6 +57,7 @@
     }
 
     public static final String TEST_INFO_RESPONSE_FIELD = "response";
+
     public static final String REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT =
             "home-to-overview-swipe-height";
     public static final String REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT =
@@ -65,6 +66,10 @@
             "all-apps-to-overview-swipe-height";
     public static final String REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT =
             "home-to-all-apps-swipe-height";
+    public static final String REQUEST_FREEZE_APP_LIST = "freeze-app-list";
+    public static final String REQUEST_UNFREEZE_APP_LIST = "unfreeze-app-list";
+    public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags";
+
     public static boolean sDebugTracing = false;
     public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
     public static final String REQUEST_DISABLE_DEBUG_TRACING = "disable-debug-tracing";
@@ -72,4 +77,5 @@
     public static final String NO_DRAG_TAG = "b/133009122";
     public static final String NO_START_TAG = "b/132900132";
     public static final String NO_START_TASK_TAG = "b/133765434";
+    public static final String NO_OVERVIEW_EVENT_TAG = "b/134532571";
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index 550327d..e41916c 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -27,7 +27,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.util.TouchController;
 
@@ -40,7 +39,7 @@
                 launcher.getDragController(), new AllAppsSwipeController(launcher)};
     }
 
-    public static Runnable enableLiveTouchControllerChanges(DragLayer dl) {
+    public static Runnable enableLiveUIChanges(Launcher l) {
         return null;
     }
 
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index e5cc858..64fe2d7 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -65,8 +65,8 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
-import org.junit.rules.TestWatcher;
 import org.junit.runners.model.Statement;
 
 import java.io.IOException;
@@ -94,7 +94,8 @@
 
     protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
     protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
-    protected final LauncherInstrumentation mLauncher;
+    protected final LauncherInstrumentation mLauncher =
+            new LauncherInstrumentation(getInstrumentation());
     protected Context mTargetContext;
     protected String mTargetPackage;
 
@@ -105,11 +106,9 @@
             throw new RuntimeException(e);
         }
         if (TestHelpers.isInLauncherProcess()) Utilities.enableRunningInTestHarnessForTests();
-        mLauncher = new LauncherInstrumentation(getInstrumentation());
     }
 
-    @Rule
-    public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
+    protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
 
     @Rule
     public ShellCommandRule mDefaultLauncherRule =
@@ -163,8 +162,14 @@
                 }
             } : base;
 
+    protected TestRule getRulesInsideActivityMonitor() {
+        return new FailureWatcher(this);
+    }
+
     @Rule
-    public TestWatcher mFailureWatcher = new FailureWatcher(this);
+    public TestRule mOrderSensitiveRules = RuleChain.
+            outerRule(mActivityMonitor).
+            around(getRulesInsideActivityMonitor());
 
     public UiDevice getDevice() {
         return mDevice;
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 4f8b87c..d171004 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -108,47 +108,63 @@
     @Test
     @Ignore
     public void testPressHomeOnAllAppsContextMenu() throws Exception {
-        mLauncher.getWorkspace().switchToAllApps().getAppIcon("TestActivity7").openMenu();
+        final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+        allApps.freeze();
+        try {
+            allApps.getAppIcon("TestActivity7").openMenu();
+        } finally {
+            allApps.unfreeze();
+        }
         mLauncher.pressHome();
     }
 
     public static void runAllAppsTest(AbstractLauncherUiTest test, AllApps allApps) {
-        assertNotNull("allApps parameter is null", allApps);
+        allApps.freeze();
+        try {
+            assertNotNull("allApps parameter is null", allApps);
 
-        assertTrue(
-                "Launcher internal state is not All Apps", test.isInState(LauncherState.ALL_APPS));
+            assertTrue(
+                    "Launcher internal state is not All Apps",
+                    test.isInState(LauncherState.ALL_APPS));
 
-        // Test flinging forward and backward.
-        test.executeOnLauncher(launcher -> assertEquals(
-                "All Apps started in already scrolled state", 0, test.getAllAppsScroll(launcher)));
+            // Test flinging forward and backward.
+            test.executeOnLauncher(launcher -> assertEquals(
+                    "All Apps started in already scrolled state", 0,
+                    test.getAllAppsScroll(launcher)));
 
-        allApps.flingForward();
-        assertTrue("Launcher internal state is not All Apps",
-                test.isInState(LauncherState.ALL_APPS));
-        final Integer flingForwardY = test.getFromLauncher(
-                launcher -> test.getAllAppsScroll(launcher));
-        test.executeOnLauncher(
-                launcher -> assertTrue("flingForward() didn't scroll App Apps", flingForwardY > 0));
+            allApps.flingForward();
+            assertTrue("Launcher internal state is not All Apps",
+                    test.isInState(LauncherState.ALL_APPS));
+            final Integer flingForwardY = test.getFromLauncher(
+                    launcher -> test.getAllAppsScroll(launcher));
+            test.executeOnLauncher(
+                    launcher -> assertTrue("flingForward() didn't scroll App Apps",
+                            flingForwardY > 0));
 
-        allApps.flingBackward();
-        assertTrue(
-                "Launcher internal state is not All Apps", test.isInState(LauncherState.ALL_APPS));
-        final Integer flingBackwardY = test.getFromLauncher(
-                launcher -> test.getAllAppsScroll(launcher));
-        test.executeOnLauncher(launcher -> assertTrue("flingBackward() didn't scroll App Apps",
-                flingBackwardY < flingForwardY));
+            allApps.flingBackward();
+            assertTrue(
+                    "Launcher internal state is not All Apps",
+                    test.isInState(LauncherState.ALL_APPS));
+            final Integer flingBackwardY = test.getFromLauncher(
+                    launcher -> test.getAllAppsScroll(launcher));
+            test.executeOnLauncher(launcher -> assertTrue("flingBackward() didn't scroll App Apps",
+                    flingBackwardY < flingForwardY));
 
-        // Test scrolling down to YouTube.
-        assertNotNull("All apps: can't fine YouTube", allApps.getAppIcon("YouTube"));
-        // Test scrolling up to Camera.
-        assertNotNull("All apps: can't fine Camera", allApps.getAppIcon("Camera"));
-        // Test failing to find a non-existing app.
-        final AllApps allAppsFinal = allApps;
-        expectFail("All apps: could find a non-existing app",
-                () -> allAppsFinal.getAppIcon("NO APP"));
+            // Test scrolling down to YouTube.
+            assertNotNull("All apps: can't fine YouTube", allApps.getAppIcon("YouTube"));
+            // Test scrolling up to Camera.
+            assertNotNull("All apps: can't fine Camera", allApps.getAppIcon("Camera"));
+            // Test failing to find a non-existing app.
+            final AllApps allAppsFinal = allApps;
+            expectFail("All apps: could find a non-existing app",
+                    () -> allAppsFinal.getAppIcon("NO APP"));
 
-        assertTrue(
-                "Launcher internal state is not All Apps", test.isInState(LauncherState.ALL_APPS));
+            assertTrue(
+                    "Launcher internal state is not All Apps",
+                    test.isInState(LauncherState.ALL_APPS));
+        } finally {
+            allApps.unfreeze();
+        }
     }
 
     @Test
@@ -199,12 +215,17 @@
     }
 
     public static void runIconLaunchFromAllAppsTest(AbstractLauncherUiTest test, AllApps allApps) {
-        final AppIcon app = allApps.getAppIcon("TestActivity7");
-        assertNotNull("AppIcon.launch returned null", app.launch(getAppPackageName()));
-        test.executeOnLauncher(launcher -> assertTrue(
-                "Launcher activity is the top activity; expecting another activity to be the top "
-                        + "one",
-                test.isInBackground(launcher)));
+        allApps.freeze();
+        try {
+            final AppIcon app = allApps.getAppIcon("TestActivity7");
+            assertNotNull("AppIcon.launch returned null", app.launch(getAppPackageName()));
+            test.executeOnLauncher(launcher -> assertTrue(
+                    "Launcher activity is the top activity; expecting another activity to be the top "
+                            + "one",
+                    test.isInBackground(launcher)));
+        } finally {
+            allApps.unfreeze();
+        }
     }
 
     @Test
@@ -260,20 +281,23 @@
     public void testLaunchMenuItem() throws Exception {
         if (!TestHelpers.isInLauncherProcess()) return;
 
-        final AppIconMenu menu = mLauncher.
+        final AllApps allApps = mLauncher.
                 getWorkspace().
-                switchToAllApps().
-                getAppIcon(APP_NAME).
-                openMenu();
+                switchToAllApps();
+        allApps.freeze();
+        try {
+            final AppIconMenu menu = allApps.
+                    getAppIcon(APP_NAME).
+                    openMenu();
 
-        executeOnLauncher(
-                launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
-                        isOptionsPopupVisible(launcher)));
+            executeOnLauncher(
+                    launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
+                            isOptionsPopupVisible(launcher)));
 
-        final AppIconMenuItem menuItem = menu.getMenuItem(1);
-        final String itemName = menuItem.getText();
-
-        menuItem.launch(getAppPackageName());
+            menu.getMenuItem(1).launch(getAppPackageName());
+        } finally {
+            allApps.unfreeze();
+        }
     }
 
     @Test
@@ -282,12 +306,18 @@
         // 1. Open all apps and wait for load complete.
         // 2. Drag icon to homescreen.
         // 3. Verify that the icon works on homescreen.
-        mLauncher.getWorkspace().
-                switchToAllApps().
-                getAppIcon(APP_NAME).
-                dragToWorkspace().
-                getWorkspaceAppIcon(APP_NAME).
-                launch(getAppPackageName());
+        final AllApps allApps = mLauncher.getWorkspace().
+                switchToAllApps();
+        allApps.freeze();
+        try {
+            allApps.
+                    getAppIcon(APP_NAME).
+                    dragToWorkspace().
+                    getWorkspaceAppIcon(APP_NAME).
+                    launch(getAppPackageName());
+        } finally {
+            allApps.unfreeze();
+        }
         executeOnLauncher(launcher -> assertTrue(
                 "Launcher activity is the top activity; expecting another activity to be the top "
                         + "one",
@@ -302,21 +332,27 @@
         // 1. Open all apps and wait for load complete.
         // 2. Find the app and long press it to show shortcuts.
         // 3. Press icon center until shortcuts appear
-        final AppIconMenuItem menuItem = mLauncher.
+        final AllApps allApps = mLauncher.
                 getWorkspace().
-                switchToAllApps().
-                getAppIcon(APP_NAME).
-                openMenu().
-                getMenuItem(0);
-        final String shortcutName = menuItem.getText();
+                switchToAllApps();
+        allApps.freeze();
+        try {
+            final AppIconMenuItem menuItem = allApps.
+                    getAppIcon(APP_NAME).
+                    openMenu().
+                    getMenuItem(0);
+            final String shortcutName = menuItem.getText();
 
-        // 4. Drag the first shortcut to the home screen.
-        // 5. Verify that the shortcut works on home screen
-        //    (the app opens and has the same text as the shortcut).
-        menuItem.
-                dragToWorkspace().
-                getWorkspaceAppIcon(shortcutName).
-                launch(getAppPackageName());
+            // 4. Drag the first shortcut to the home screen.
+            // 5. Verify that the shortcut works on home screen
+            //    (the app opens and has the same text as the shortcut).
+            menuItem.
+                    dragToWorkspace().
+                    getWorkspaceAppIcon(shortcutName).
+                    launch(getAppPackageName());
+        } finally {
+            allApps.unfreeze();
+        }
     }
 
     public static String getAppPackageName() {
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index d03035a..21d763e 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -48,6 +48,7 @@
                 "apps_list_view");
         // Wait for the recycler to populate.
         mLauncher.waitForObjectInContainer(appListRecycler, By.clazz(TextView.class));
+        verifyNotFrozen("All apps freeze flags upon opening all apps");
     }
 
     @Override
@@ -210,4 +211,25 @@
             verifyActiveContainer();
         }
     }
+
+    /**
+     * Freezes updating app list upon app install/uninstall/update.
+     */
+    public void freeze() {
+        mLauncher.getTestInfo(TestProtocol.REQUEST_FREEZE_APP_LIST);
+    }
+
+    /**
+     * Resumes updating app list upon app install/uninstall/update.
+     */
+    public void unfreeze() {
+        mLauncher.getTestInfo(TestProtocol.REQUEST_UNFREEZE_APP_LIST);
+        verifyNotFrozen("All apps freeze flags upon unfreezing");
+    }
+
+    private void verifyNotFrozen(String message) {
+        mLauncher.assertEquals(message, 0, mLauncher.getTestInfo(
+                TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS).
+                getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD));
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index ce95298..6c46192 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -59,6 +59,7 @@
     }
 
     protected void goToOverviewUnchecked(int expectedState) {
+        mLauncher.getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
         switch (mLauncher.getNavigationModel()) {
             case ZERO_BUTTON: {
                 final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
@@ -103,6 +104,7 @@
                 mLauncher.waitForSystemUiObject("recent_apps").click();
                 break;
         }
+        mLauncher.getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
     }
 
     protected String getSwipeHeightRequestName() {
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index e45fca8..8ebe525 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -292,6 +292,12 @@
         }
     }
 
+    void assertEquals(String message, long expected, long actual) {
+        if (expected != actual) {
+            fail(message + " expected: " + expected + " but was: " + actual);
+        }
+    }
+
     void assertNotEquals(String message, int unexpected, int actual) {
         if (unexpected == actual) {
             failEquals(message, actual);