Merge "Show ASAP for Recents Go and load content after" into ub-launcher3-master
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index 29e650c..321861b 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.uioverrides;
 
 import static android.view.View.VISIBLE;
-
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 
 import android.view.View;
@@ -26,6 +25,10 @@
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
+import com.android.launcher3.uioverrides.touchcontrollers.LandscapeStatesTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.views.IconRecentsView;
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
similarity index 95%
rename from go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
rename to go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 6293b82..6730e97 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.uioverrides.states;
 
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
@@ -100,4 +100,8 @@
     public static OverviewState newPeekState(int id) {
         return new OverviewState(id);
     }
+
+    public static OverviewState newSwitchState(int id) {
+        return new OverviewState(id);
+    }
 }
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java b/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeStatesTouchController.java
similarity index 97%
rename from go/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
rename to go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeStatesTouchController.java
index 2c91bc3..1ccd7d7 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeStatesTouchController.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java b/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
similarity index 97%
rename from go/quickstep/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java
rename to go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
index a3b41b0..011a4e7 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.uioverrides.touchcontrollers;
 
 import android.view.MotionEvent;
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewPeekState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewPeekState.java
deleted file mode 100644
index 8f4d697..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewPeekState.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.android.launcher3.uioverrides;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-
-public class OverviewPeekState extends OverviewState {
-    public OverviewPeekState(int id) {
-        super(id);
-    }
-
-    @Override
-    public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
-        ScaleAndTranslation result = super.getOverviewScaleAndTranslation(launcher);
-        result.translationX = NORMAL.getOverviewScaleAndTranslation(launcher).translationX
-                - launcher.getResources().getDimension(R.dimen.overview_peek_distance);
-        return result;
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index 8e62cd3..d0a2f27 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -29,6 +29,13 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
+import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
@@ -60,6 +67,7 @@
         list.add(launcher.getDragController());
 
         if (swipeUpToHome) {
+            list.add(new QuickSwitchTouchController(launcher));
             list.add(new FlingAndHoldTouchController(launcher));
         } else {
             if (launcher.getDeviceProfile().isVerticalBarLayout()) {
@@ -68,6 +76,9 @@
             } else {
                 list.add(new PortraitStatesTouchController(launcher,
                         swipeUpEnabled /* allowDragToOverview */));
+                if (swipeUpEnabled) {
+                    list.add(new QuickSwitchTouchController(launcher));
+                }
             }
         }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
similarity index 98%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java
rename to quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 726ae05..d8f1628 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.uioverrides.states;
 
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
new file mode 100644
index 0000000..bc1d4ba
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
@@ -0,0 +1,32 @@
+/*
+ * 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.launcher3.uioverrides.states;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+
+public class OverviewPeekState extends OverviewState {
+    public OverviewPeekState(int id) {
+        super(id);
+    }
+
+    @Override
+    public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
+        ScaleAndTranslation result = super.getOverviewScaleAndTranslation(launcher);
+        result.translationX = NORMAL.getOverviewScaleAndTranslation(launcher).translationX
+                - launcher.getResources().getDimension(R.dimen.overview_peek_distance);
+        return result;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
similarity index 92%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
rename to quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 1754b80..94c1545 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -13,11 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.uioverrides.states;
 
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.logging.LoggerUtils.getTargetStr;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
 
@@ -52,7 +53,11 @@
     }
 
     protected OverviewState(int id, int transitionDuration, int stateFlags) {
-        super(id, ContainerType.TASKSWITCHER, transitionDuration, stateFlags);
+        this(id, ContainerType.TASKSWITCHER, transitionDuration, stateFlags);
+    }
+
+    protected OverviewState(int id, int logContainer, int transitionDuration, int stateFlags) {
+        super(id, logContainer, transitionDuration, stateFlags);
     }
 
     @Override
@@ -92,6 +97,7 @@
         DiscoveryBounce.showForOverviewIfNeeded(launcher);
     }
 
+    @Override
     public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
         return new PageAlphaProvider(DEACCEL_2) {
             @Override
@@ -159,4 +165,8 @@
     public static OverviewState newPeekState(int id) {
         return new OverviewPeekState(id);
     }
+
+    public static OverviewState newSwitchState(int id) {
+        return new QuickSwitchState(id);
+    }
 }
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
new file mode 100644
index 0000000..fa07e27
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -0,0 +1,89 @@
+/*
+ * 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.launcher3.uioverrides.states;
+
+import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+
+import android.graphics.Rect;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskThumbnailView;
+import com.android.quickstep.views.TaskView;
+
+/**
+ * State to indicate we are about to launch a recent task. Note that this state is only used when
+ * quick switching from launcher; quick switching from an app uses WindowTransformSwipeHelper.
+ * @see com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget#NEW_TASK
+ */
+public class QuickSwitchState extends OverviewState {
+    private static final int STATE_FLAGS =
+            FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_DISABLE_ACCESSIBILITY;
+
+    public QuickSwitchState(int id) {
+        super(id, LauncherLogProto.ContainerType.APP, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
+    }
+
+    @Override
+    public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
+        RecentsView recentsView = launcher.getOverviewPanel();
+        if (recentsView.getTaskViewCount() == 0) {
+            return super.getOverviewScaleAndTranslation(launcher);
+        }
+        // Compute scale and translation y such that the most recent task view fills the screen.
+        TaskThumbnailView dummyThumbnail = recentsView.getTaskViewAt(0).getThumbnail();
+        ClipAnimationHelper clipAnimationHelper = new ClipAnimationHelper(launcher);
+        clipAnimationHelper.fromTaskThumbnailView(dummyThumbnail, recentsView);
+        Rect targetRect = new Rect();
+        recentsView.getTaskSize(targetRect);
+        clipAnimationHelper.updateTargetRect(targetRect);
+        float toScale = clipAnimationHelper.getSourceRect().width()
+                / clipAnimationHelper.getTargetRect().width();
+        float toTranslationY = clipAnimationHelper.getSourceRect().centerY()
+                - clipAnimationHelper.getTargetRect().centerY();
+        return new ScaleAndTranslation(toScale, 0, toTranslationY);
+    }
+
+    @Override
+    public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
+        float shiftRange = launcher.getAllAppsController().getShiftRange();
+        float shiftProgress = getVerticalProgress(launcher) - NORMAL.getVerticalProgress(launcher);
+        float translationY = shiftProgress * shiftRange;
+        return new ScaleAndTranslation(1, 0, translationY);
+    }
+
+    @Override
+    public float getVerticalProgress(Launcher launcher) {
+        return BACKGROUND_APP.getVerticalProgress(launcher);
+    }
+
+    @Override
+    public int getVisibleElements(Launcher launcher) {
+        return NONE;
+    }
+
+    @Override
+    public void onStateTransitionEnd(Launcher launcher) {
+        TaskView tasktolaunch = launcher.<RecentsView>getOverviewPanel().getTaskViewAt(0);
+        if (tasktolaunch != null) {
+            tasktolaunch.launchTask(false);
+        } else {
+            launcher.getStateManager().goToState(NORMAL);
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
similarity index 98%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/FlingAndHoldTouchController.java
rename to quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index 7b33abb..0757e85 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewToAllAppsTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
similarity index 97%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewToAllAppsTouchController.java
rename to quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
index a069ed5..82ab34b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewToAllAppsTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
similarity index 94%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java
rename to quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
index eead4c8..5337c39 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.uioverrides.touchcontrollers;
 
-import static com.android.launcher3.uioverrides.PortraitStatesTouchController.isTouchOverHotseat;
+import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.isTouchOverHotseat;
 
 import android.view.MotionEvent;
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
new file mode 100644
index 0000000..91a31dd
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -0,0 +1,147 @@
+/*
+ * 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.launcher3.uioverrides.touchcontrollers;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.QUICK_SWITCH;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.view.MotionEvent;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.touch.AbstractStateChangeTouchController;
+import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Handles quick switching to a recent task from the home screen.
+ */
+public class QuickSwitchTouchController extends AbstractStateChangeTouchController {
+
+    private @Nullable TaskView mTaskToLaunch;
+
+    public QuickSwitchTouchController(Launcher launcher) {
+        super(launcher, SwipeDetector.HORIZONTAL);
+    }
+
+    @Override
+    protected boolean canInterceptTouch(MotionEvent ev) {
+        if (mCurrentAnimation != null) {
+            return true;
+        }
+        if (!mLauncher.isInState(LauncherState.NORMAL)) {
+            return false;
+        }
+        if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        return isDragTowardPositive ? QUICK_SWITCH : NORMAL;
+    }
+
+    @Override
+    public void onDragStart(boolean start) {
+        super.onDragStart(start);
+        mStartContainerType = LauncherLogProto.ContainerType.NAVBAR;
+        mTaskToLaunch = mLauncher.<RecentsView>getOverviewPanel().getTaskViewAt(0);
+    }
+
+    @Override
+    protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
+        super.onSwipeInteractionCompleted(targetState, logAction);
+        mTaskToLaunch = null;
+    }
+
+    @Override
+    protected float initCurrentAnimation(int animComponents) {
+        AnimatorSetBuilder animatorSetBuilder = new AnimatorSetBuilder();
+        setupInterpolators(animatorSetBuilder);
+        long accuracy = (long) (getShiftRange() * 2);
+        mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
+                animatorSetBuilder, accuracy, this::clearState, LauncherStateManager.ANIM_ALL);
+        mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator -> {
+            updateFullscreenProgress((Float) valueAnimator.getAnimatedValue());
+        });
+        return 1 / getShiftRange();
+    }
+
+    private void setupInterpolators(AnimatorSetBuilder animatorSetBuilder) {
+        animatorSetBuilder.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_2);
+        animatorSetBuilder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_2);
+        if (FeatureFlags.SWIPE_HOME.get()) {
+            // Overview lives to the left of workspace, so translate down later than over
+            animatorSetBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL_2);
+            animatorSetBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, ACCEL_2);
+            animatorSetBuilder.setInterpolator(ANIM_OVERVIEW_SCALE, ACCEL_2);
+            animatorSetBuilder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, ACCEL_2);
+            animatorSetBuilder.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
+        } else {
+            animatorSetBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, LINEAR);
+            animatorSetBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, LINEAR);
+        }
+    }
+
+    @Override
+    protected void updateProgress(float progress) {
+        super.updateProgress(progress);
+        updateFullscreenProgress(progress);
+    }
+
+    private void updateFullscreenProgress(float progress) {
+        if (mTaskToLaunch != null) {
+            mTaskToLaunch.setFullscreenProgress(progress);
+        }
+    }
+
+    @Override
+    protected float getShiftRange() {
+        return mLauncher.getDeviceProfile().widthPx / 2f;
+    }
+
+    @Override
+    protected int getLogContainerTypeForNormalState() {
+        return LauncherLogProto.ContainerType.NAVBAR;
+    }
+
+    @Override
+    protected int getDirectionForLog() {
+        return Utilities.isRtl(mLauncher.getResources()) ? Direction.LEFT : Direction.RIGHT;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
similarity index 99%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
rename to quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index fb1828b..a2219e8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
index 02ffdb3..7794d8d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
@@ -19,7 +19,6 @@
 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.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -46,7 +45,7 @@
     private final BaseDragLayer mTarget;
     private final int[] mLocationOnScreen = new int[2];
     private final PointF mDownPos = new PointF();
-    private final int mTouchSlop;
+    private final int mTouchSlopSquared;
 
     private final boolean mStartingInActivityBounds;
 
@@ -56,7 +55,8 @@
     OverviewInputConsumer(T activity, boolean startingInActivityBounds) {
         mActivity = activity;
         mTarget = activity.getDragLayer();
-        mTouchSlop = ViewConfiguration.get(mActivity).getScaledTouchSlop();
+        int touchSlop = ViewConfiguration.get(mActivity).getScaledTouchSlop();
+        mTouchSlopSquared = touchSlop * touchSlop;
         mStartingInActivityBounds = startingInActivityBounds;
     }
 
@@ -88,10 +88,11 @@
                             false /* closeActiveWindows */);
                     break;
                 case ACTION_MOVE: {
-                    float displacement = mActivity.getDeviceProfile().isLandscape ?
-                            ev.getX() - mDownPos.x : ev.getY() - mDownPos.y;
-                    if (Math.abs(displacement) >= mTouchSlop) {
-                        // Start tracking only when mTouchSlop is crossed.
+                    float x = ev.getX() - mDownPos.x;
+                    float y = ev.getY() - mDownPos.y;
+                    double hypotSquared = x * x + y * y;
+                    if (hypotSquared >= mTouchSlopSquared) {
+                        // Start tracking only when touch slop is crossed.
                         startTouchTracking(ev, true /* updateLocationOffset */,
                                 true /* closeActiveWindows */);
                     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java
index 9463cc9..a113604 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java
@@ -15,7 +15,7 @@
  */
 package com.android.quickstep.fallback;
 
-import com.android.launcher3.uioverrides.TaskViewTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
 import com.android.quickstep.RecentsActivity;
 
 public class RecentsTaskController extends TaskViewTouchController<RecentsActivity> {
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 a02df62..ddb94d2 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
@@ -24,7 +24,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
-import static com.android.launcher3.uioverrides.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
+import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 89093e9..f0204b9 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -19,7 +19,8 @@
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -96,15 +97,17 @@
         ScaleAndTranslation scaleAndTranslation = toState.getOverviewScaleAndTranslation(mLauncher);
         Interpolator scaleInterpolator = builder.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR);
         setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndTranslation.scale, scaleInterpolator);
-        Interpolator translateInterpolator = builder.getInterpolator(
-                ANIM_OVERVIEW_TRANSLATE, LINEAR);
+        Interpolator translateXInterpolator = builder.getInterpolator(
+                ANIM_OVERVIEW_TRANSLATE_X, LINEAR);
+        Interpolator translateYInterpolator = builder.getInterpolator(
+                ANIM_OVERVIEW_TRANSLATE_Y, LINEAR);
         float translationX = scaleAndTranslation.translationX;
         if (mRecentsView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
             translationX = -translationX;
         }
-        setter.setFloat(mRecentsView, View.TRANSLATION_X, translationX, translateInterpolator);
+        setter.setFloat(mRecentsView, View.TRANSLATION_X, translationX, translateXInterpolator);
         setter.setFloat(mRecentsView, View.TRANSLATION_Y, scaleAndTranslation.translationY,
-                translateInterpolator);
+                translateYInterpolator);
         setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
                 builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
similarity index 98%
rename from quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
rename to quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index c629e33..ab24f5f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.uioverrides.states;
 
 import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
similarity index 96%
rename from quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
index 086cbdb..0605953 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
@@ -1,4 +1,4 @@
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -41,7 +41,7 @@
 
     @Override
     protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
-        boolean draggingFromNav = mLauncher.getDeviceProfile().isSeascape() != isDragTowardPositive;
+        boolean draggingFromNav = mLauncher.getDeviceProfile().isSeascape() == isDragTowardPositive;
         return draggingFromNav ? OVERVIEW : NORMAL;
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
similarity index 98%
rename from quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 62e525c..ce50b68 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
 import static com.android.launcher3.LauncherState.ALL_APPS;
@@ -43,6 +43,8 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.uioverrides.states.OverviewState;
+import com.android.launcher3.uioverrides.touchcontrollers.PortraitOverviewStateTouchHelper;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.RecentsModel;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
similarity index 98%
rename from quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index 8f33e40..12e6f12 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
diff --git a/quickstep/src/com/android/quickstep/TestInformationProvider.java b/quickstep/src/com/android/quickstep/TestInformationProvider.java
index e57d3ec..b37ddda 100644
--- a/quickstep/src/com/android/quickstep/TestInformationProvider.java
+++ b/quickstep/src/com/android/quickstep/TestInformationProvider.java
@@ -30,7 +30,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.TestProtocol;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.uioverrides.OverviewState;
+import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.quickstep.util.LayoutUtils;
 
 public class TestInformationProvider extends ContentProvider {
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index 6854aa8..c77726e 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -27,5 +27,5 @@
 public abstract class AbstractQuickStepTest extends AbstractLauncherUiTest {
     @Rule
     public TestRule mQuickstepOnOffExecutor =
-            new QuickStepOnOffRule(mMainThreadExecutor, mLauncher);
+            new NavigationModeSwitchRule(mLauncher);
 }
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 88b50d9..f436831 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -17,26 +17,31 @@
 
 import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
 
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
 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.util.rule.ShellCommandRule.disableHeadsUpNotification;
 import static com.android.launcher3.util.rule.ShellCommandRule.getLauncherCommand;
-import static com.android.quickstep.QuickStepOnOffRule.Mode.OFF;
+import static com.android.quickstep.NavigationModeSwitchRule.Mode.THREE_BUTTON;
 
 import static org.junit.Assert.assertTrue;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 
-import com.android.launcher3.MainThreadExecutor;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
+
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
-import com.android.quickstep.QuickStepOnOffRule.QuickstepOnOff;
+import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -44,12 +49,6 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.model.Statement;
 
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.Until;
-
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 /**
@@ -72,7 +71,7 @@
         mDevice = UiDevice.getInstance(instrumentation);
         mLauncher = new LauncherInstrumentation(instrumentation);
 
-        mQuickstepOnOffExecutor = new QuickStepOnOffRule(new MainThreadExecutor(), mLauncher);
+        mQuickstepOnOffExecutor = new NavigationModeSwitchRule(mLauncher);
         mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
                 getHomeIntentInPackage(context),
                 MATCH_DISABLED_COMPONENTS).get(0).activityInfo;
@@ -94,7 +93,7 @@
         };
     }
 
-    @QuickstepOnOff(mode = OFF)
+    @NavigationModeSwitch(mode = THREE_BUTTON)
     @Test
     public void goToOverviewFromHome() {
         mDevice.pressHome();
@@ -104,7 +103,7 @@
         mLauncher.getBackground().switchToOverview();
     }
 
-    @QuickstepOnOff(mode = OFF)
+    @NavigationModeSwitch(mode = THREE_BUTTON)
     @Test
     public void goToOverviewFromApp() {
         startAppFast("com.android.settings");
diff --git a/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
similarity index 63%
rename from quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java
rename to quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 12bd0ca..8633b21 100644
--- a/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -18,9 +18,9 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
-import static com.android.quickstep.QuickStepOnOffRule.Mode.BOTH;
-import static com.android.quickstep.QuickStepOnOffRule.Mode.OFF;
-import static com.android.quickstep.QuickStepOnOffRule.Mode.ON;
+import static com.android.quickstep.NavigationModeSwitchRule.Mode.ALL;
+import static com.android.quickstep.NavigationModeSwitchRule.Mode.THREE_BUTTON;
+import static com.android.quickstep.NavigationModeSwitchRule.Mode.TWO_BUTTON;
 import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_2BUTTON_OVERLAY;
 import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_3BUTTON_OVERLAY;
 import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_GESTURAL_OVERLAY;
@@ -34,49 +34,46 @@
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.systemui.shared.system.QuickStepContract;
 
+import org.junit.Assert;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
-import java.io.IOException;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
-import java.util.concurrent.Executor;
 
 /**
  * Test rule that allows executing a test with Quickstep on and then Quickstep off.
  * The test should be annotated with @QuickstepOnOff.
  */
-public class QuickStepOnOffRule implements TestRule {
+public class NavigationModeSwitchRule implements TestRule {
 
     static final String TAG = "QuickStepOnOffRule";
 
     public enum Mode {
-        ON, OFF, BOTH
+        THREE_BUTTON, TWO_BUTTON, ZERO_BUTTON, ALL
     }
 
     // Annotation for tests that need to be run with quickstep enabled and disabled.
     @Retention(RetentionPolicy.RUNTIME)
     @Target(ElementType.METHOD)
-    public @interface QuickstepOnOff {
-        Mode mode() default BOTH;
+    public @interface NavigationModeSwitch {
+        Mode mode() default ALL;
     }
 
-    private final Executor mMainThreadExecutor;
     private final LauncherInstrumentation mLauncher;
 
-    public QuickStepOnOffRule(Executor mainThreadExecutor, LauncherInstrumentation launcher) {
+    public NavigationModeSwitchRule(LauncherInstrumentation launcher) {
         mLauncher = launcher;
-        this.mMainThreadExecutor = mainThreadExecutor;
     }
 
     @Override
     public Statement apply(Statement base, Description description) {
         if (TestHelpers.isInLauncherProcess() &&
-                description.getAnnotation(QuickstepOnOff.class) != null) {
-            Mode mode = description.getAnnotation(QuickstepOnOff.class).mode();
+                description.getAnnotation(NavigationModeSwitch.class) != null) {
+            Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode();
             return new Statement() {
                 @Override
                 public void evaluate() throws Throwable {
@@ -86,15 +83,20 @@
                             : QuickStepContract.isSwipeUpMode(context)
                                     ? NAV_BAR_MODE_2BUTTON_OVERLAY
                                     : NAV_BAR_MODE_3BUTTON_OVERLAY;
+                    final LauncherInstrumentation.NavigationModel originalMode =
+                            mLauncher.getNavigationModel();
                     try {
-                        if (mode == ON || mode == BOTH) {
-                            evaluateWithQuickstepOn();
+//                        if (mode == ZERO_BUTTON || mode == ALL) {
+//                            evaluateWithZeroButtons();
+//                        }
+                        if (mode == TWO_BUTTON || mode == ALL) {
+                            evaluateWithTwoButtons();
                         }
-                        if (mode == OFF || mode == BOTH) {
-                            evaluateWithQuickstepOff();
+                        if (mode == THREE_BUTTON || mode == ALL) {
+                            evaluateWithThreeButtons();
                         }
                     } finally {
-                        setActiveOverlay(prevOverlayPkg);
+                        setActiveOverlay(prevOverlayPkg, originalMode);
                     }
                 }
 
@@ -102,17 +104,26 @@
                     base.evaluate();
                 }
 
-                private void evaluateWithQuickstepOff() throws Throwable {
-                    setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY);
+                private void evaluateWithThreeButtons() throws Throwable {
+                    setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.THREE_BUTTON);
                     evaluateWithoutChangingSetting(base);
                 }
 
-                private void evaluateWithQuickstepOn() throws Throwable {
-                    setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY);
+                private void evaluateWithTwoButtons() throws Throwable {
+                    setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.TWO_BUTTON);
                     base.evaluate();
                 }
 
-                private void setActiveOverlay(String overlayPackage) {
+                private void evaluateWithZeroButtons() throws Throwable {
+                    setActiveOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.ZERO_BUTTON);
+                    base.evaluate();
+                }
+
+                private void setActiveOverlay(String overlayPackage,
+                        LauncherInstrumentation.NavigationModel expectedMode) throws Exception {
                     setOverlayPackageEnabled(NAV_BAR_MODE_3BUTTON_OVERLAY,
                             overlayPackage == NAV_BAR_MODE_3BUTTON_OVERLAY);
                     setOverlayPackageEnabled(NAV_BAR_MODE_2BUTTON_OVERLAY,
@@ -120,18 +131,19 @@
                     setOverlayPackageEnabled(NAV_BAR_MODE_GESTURAL_OVERLAY,
                             overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
 
-                    // TODO: Wait until nav bar mode has applied
+                    for (int i = 0; i != 100; ++i) {
+                        if (mLauncher.getNavigationModel() == expectedMode) return;
+                        Thread.sleep(100);
+                    }
+                    Assert.fail("Couldn't switch to " + overlayPackage);
                 }
 
-                private void setOverlayPackageEnabled(String overlayPackage, boolean enable) {
+                private void setOverlayPackageEnabled(String overlayPackage, boolean enable)
+                        throws Exception {
                     Log.d(TAG, "setOverlayPackageEnabled: " + overlayPackage + " " + enable);
                     final String action = enable ? "enable" : "disable";
-                    try {
-                        UiDevice.getInstance(getInstrumentation()).executeShellCommand(
-                                "cmd overlay " + action + " " + overlayPackage);
-                    } catch (IOException e) {
-                        e.printStackTrace();
-                    }
+                    UiDevice.getInstance(getInstrumentation()).executeShellCommand(
+                            "cmd overlay " + action + " " + overlayPackage);
                 }
             };
         } else {
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 6031dcd..554aef4 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -26,8 +26,8 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.util.RaceConditionReproducer;
-import com.android.quickstep.QuickStepOnOffRule.Mode;
-import com.android.quickstep.QuickStepOnOffRule.QuickstepOnOff;
+import com.android.quickstep.NavigationModeSwitchRule.Mode;
+import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -62,7 +62,7 @@
 
     @Test
     @Ignore // Ignoring until gestural navigation event sequence settles
-    @QuickstepOnOff(mode = Mode.ON)
+    @NavigationModeSwitch(mode = Mode.TWO_BUTTON)
     public void testPressHome() {
         runTest(enterEvt(Launcher.ON_CREATE_EVT),
                 exitEvt(Launcher.ON_CREATE_EVT),
@@ -77,14 +77,14 @@
 
     @Test
     @Ignore // Ignoring until gestural navigation event sequence settles
-    @QuickstepOnOff(mode = Mode.ON)
+    @NavigationModeSwitch(mode = Mode.TWO_BUTTON)
     public void testSwipeToOverview() {
         closeLauncherActivity();
         mLauncher.getBackground().switchToOverview();
     }
 
     @Test
-    @QuickstepOnOff
+    @NavigationModeSwitch
     public void testStressPressHome() {
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
             // Destroy Launcher activity.
@@ -96,7 +96,7 @@
     }
 
     @Test
-    @QuickstepOnOff
+    @NavigationModeSwitch
     public void testStressSwipeToOverview() {
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
             // Destroy Launcher activity.
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index c60cf45..226e5ee 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -37,7 +37,7 @@
 import com.android.launcher3.tapl.OverviewTask;
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.ui.TaplTestsLauncher3;
-import com.android.quickstep.QuickStepOnOffRule.QuickstepOnOff;
+import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 import com.android.quickstep.views.RecentsView;
 
 import org.junit.Before;
@@ -79,7 +79,7 @@
     }
 
     @Test
-    @QuickstepOnOff
+    @NavigationModeSwitch
     @PortraitLandscape
     public void testWorkspaceSwitchToAllApps() {
         assertNotNull("switchToAllApps() returned null",
@@ -198,7 +198,7 @@
     }
 
     @Test
-    @QuickstepOnOff
+    @NavigationModeSwitch
     @PortraitLandscape
     public void testSwitchToOverview() throws Exception {
         assertNotNull("Workspace.switchToOverview() returned null",
@@ -208,7 +208,7 @@
     }
 
     @Test
-    @QuickstepOnOff
+    @NavigationModeSwitch
     @PortraitLandscape
     public void testBackground() throws Exception {
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index a3d5994..c65a871 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.TestProtocol.NORMAL_STATE_ORDINAL;
 import static com.android.launcher3.TestProtocol.OVERVIEW_PEEK_STATE_ORDINAL;
 import static com.android.launcher3.TestProtocol.OVERVIEW_STATE_ORDINAL;
+import static com.android.launcher3.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
 import static com.android.launcher3.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
@@ -31,9 +32,9 @@
 
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.states.SpringLoadedState;
-import com.android.launcher3.uioverrides.AllAppsState;
-import com.android.launcher3.uioverrides.OverviewState;
 import com.android.launcher3.uioverrides.UiFactory;
+import com.android.launcher3.uioverrides.states.AllAppsState;
+import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
@@ -77,7 +78,7 @@
                 }
             };
 
-    private static final LauncherState[] sAllStates = new LauncherState[6];
+    private static final LauncherState[] sAllStates = new LauncherState[7];
 
     /**
      * TODO: Create a separate class for NORMAL state.
@@ -97,6 +98,8 @@
     public static final LauncherState OVERVIEW = new OverviewState(OVERVIEW_STATE_ORDINAL);
     public static final LauncherState OVERVIEW_PEEK =
             OverviewState.newPeekState(OVERVIEW_PEEK_STATE_ORDINAL);
+    public static final LauncherState QUICK_SWITCH =
+            OverviewState.newSwitchState(QUICK_SWITCH_STATE_ORDINAL);
     public static final LauncherState BACKGROUND_APP =
             OverviewState.newBackgroundState(BACKGROUND_APP_STATE_ORDINAL);
 
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 19c896f..5f7538b 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -22,7 +22,7 @@
 import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
@@ -296,7 +296,7 @@
             builder.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
             builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
             builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
-            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE, OVERSHOOT_1_7);
+            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
             builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
 
             // Start from a higher overview scale, but only if we're invisible so we don't jump.
@@ -305,7 +305,7 @@
             builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
             builder.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
             builder.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
-            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE, ACCEL);
+            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
             builder.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
             Workspace workspace = mLauncher.getWorkspace();
 
diff --git a/src/com/android/launcher3/TestProtocol.java b/src/com/android/launcher3/TestProtocol.java
index 8b7279c..49a736e 100644
--- a/src/com/android/launcher3/TestProtocol.java
+++ b/src/com/android/launcher3/TestProtocol.java
@@ -29,8 +29,9 @@
     public static final int SPRING_LOADED_STATE_ORDINAL = 1;
     public static final int OVERVIEW_STATE_ORDINAL = 2;
     public static final int OVERVIEW_PEEK_STATE_ORDINAL = 3;
-    public static final int ALL_APPS_STATE_ORDINAL = 4;
-    public static final int BACKGROUND_APP_STATE_ORDINAL = 5;
+    public static final int QUICK_SWITCH_STATE_ORDINAL = 4;
+    public static final int ALL_APPS_STATE_ORDINAL = 5;
+    public static final int BACKGROUND_APP_STATE_ORDINAL = 6;
 
     public static final String TEST_INFO_RESPONSE_FIELD = "response";
     public static final String REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT =
@@ -41,4 +42,7 @@
             "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 boolean sDebugTracing = false;
+    public static final String NO_DRAG_TAG = "b/129434166";
 }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index dd755cb..fd4b508 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -308,10 +308,14 @@
             Log.e(TAG, "mapToRange: range has 0 length");
             return toMin;
         }
-        float progress = Math.abs(t - fromMin) / Math.abs(fromMax - fromMin);
+        float progress = getProgress(t, fromMin, fromMax);
         return mapRange(interpolator.getInterpolation(progress), toMin, toMax);
     }
 
+    public static float getProgress(float current, float min, float max) {
+        return Math.abs(current - min) / Math.abs(max - min);
+    }
+
     public static float mapRange(float value, float min, float max) {
         return min + (value * (max - min));
     }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index d05f916..d24a5a6 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1446,6 +1446,10 @@
 
     public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
             DragPreviewProvider previewProvider, DragOptions dragOptions) {
+        if (com.android.launcher3.TestProtocol.sDebugTracing) {
+            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                    "beginDragShared");
+        }
         float iconScale = 1f;
         if (child instanceof BubbleTextView) {
             Drawable icon = ((BubbleTextView) child).getIcon();
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 21fdd3d..99a8801 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.ZOOM_OUT;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
@@ -114,7 +115,9 @@
             return;
         }
 
-        Interpolator translationInterpolator = !playAtomicComponent ? LINEAR : ZOOM_OUT;
+        Interpolator translationInterpolator = !playAtomicComponent
+                ? LINEAR
+                : builder.getInterpolator(ANIM_WORKSPACE_TRANSLATE, ZOOM_OUT);
         propertySetter.setFloat(mWorkspace, View.TRANSLATION_X,
                 scaleAndTranslation.translationX, translationInterpolator);
         propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index a4ecec7..4a1d432 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -205,7 +205,7 @@
         mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
 
         setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
-                (visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, LINEAR);
+                (visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, allAppsFade);
     }
 
     public AnimatorListenerAdapter getProgressAnimatorListener() {
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index 3ac9d3c..5c498f8 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -30,11 +30,13 @@
 
     public static final int ANIM_VERTICAL_PROGRESS = 0;
     public static final int ANIM_WORKSPACE_SCALE = 1;
-    public static final int ANIM_WORKSPACE_FADE = 2;
-    public static final int ANIM_OVERVIEW_SCALE = 3;
-    public static final int ANIM_OVERVIEW_TRANSLATE = 4;
-    public static final int ANIM_OVERVIEW_FADE = 5;
-    public static final int ANIM_ALL_APPS_FADE = 6;
+    public static final int ANIM_WORKSPACE_TRANSLATE = 2;
+    public static final int ANIM_WORKSPACE_FADE = 3;
+    public static final int ANIM_OVERVIEW_SCALE = 4;
+    public static final int ANIM_OVERVIEW_TRANSLATE_X = 5;
+    public static final int ANIM_OVERVIEW_TRANSLATE_Y = 6;
+    public static final int ANIM_OVERVIEW_FADE = 7;
+    public static final int ANIM_ALL_APPS_FADE = 8;
 
     public static final int FLAG_DONT_ANIMATE_OVERVIEW = 1 << 0;
 
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 03dc66e..8b100d9 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -219,6 +219,9 @@
     }
 
     private void callOnDragStart() {
+        if (com.android.launcher3.TestProtocol.sDebugTracing) {
+            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG, "callOnDragStart");
+        }
         if (mOptions.preDragCondition != null) {
             mOptions.preDragCondition.onPreDragEnd(mDragObject, true /* dragStarted*/);
         }
@@ -472,6 +475,9 @@
     }
 
     private void handleMoveEvent(int x, int y) {
+        if (com.android.launcher3.TestProtocol.sDebugTracing) {
+            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG, "handleMoveEvent1");
+        }
         mDragObject.dragView.move(x, y);
 
         // Drop on someone?
@@ -488,6 +494,10 @@
 
         if (mIsInPreDrag && mOptions.preDragCondition != null
                 && mOptions.preDragCondition.shouldStartDrag(mDistanceSinceScroll)) {
+            if (com.android.launcher3.TestProtocol.sDebugTracing) {
+                android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                        "handleMoveEvent2");
+            }
             callOnDragStart();
         }
     }
@@ -525,6 +535,10 @@
      * Call this from a drag source view.
      */
     public boolean onControllerTouchEvent(MotionEvent ev) {
+        if (com.android.launcher3.TestProtocol.sDebugTracing) {
+            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                    "onControllerTouchEvent1");
+        }
         if (mDragDriver == null || mOptions == null || mOptions.isAccessibleDrag) {
             return false;
         }
@@ -545,6 +559,10 @@
                 break;
         }
 
+        if (com.android.launcher3.TestProtocol.sDebugTracing) {
+            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                    "onControllerTouchEvent2");
+        }
         return mDragDriver.onTouchEvent(ev);
     }
 
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index 84fc94d..551f2d0 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -45,10 +45,18 @@
     public void onDragViewAnimationEnd() { }
 
     public boolean onTouchEvent(MotionEvent ev) {
+        if (com.android.launcher3.TestProtocol.sDebugTracing) {
+            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                    "onTouchEvent " + ev);
+        }
         final int action = ev.getAction();
 
         switch (action) {
             case MotionEvent.ACTION_MOVE:
+                if (com.android.launcher3.TestProtocol.sDebugTracing) {
+                    android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                            "onTouchEvent MOVE");
+                }
                 mEventListener.onDriverDragMove(ev.getX(), ev.getY());
                 break;
             case MotionEvent.ACTION_UP:
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 6950a1f..9f902ed 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -126,6 +126,10 @@
     protected boolean findActiveController(MotionEvent ev) {
         if (mActivity.getStateManager().getState().disableInteraction) {
             // You Shall Not Pass!!!
+            if (com.android.launcher3.TestProtocol.sDebugTracing) {
+                android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                        "mActiveController = null");
+            }
             mActiveController = null;
             return true;
         }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 521f5c1..bcd5701 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -199,6 +199,7 @@
     }
 
     public void getPreviewBounds(Rect outBounds) {
+        mPreviewItemManager.recomputePreviewDrawingParams();
         mBackground.getBounds(outBounds);
     }
 
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 10ecc4f..080a0cb 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -187,6 +187,10 @@
      * @return the container if shown or null.
      */
     public static PopupContainerWithArrow showForIcon(BubbleTextView icon) {
+        if (com.android.launcher3.TestProtocol.sDebugTracing) {
+            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                    "PopupContainerWithArrow.showForIcon");
+        }
         Launcher launcher = Launcher.getLauncher(icon.getContext());
         if (getOpen(launcher) != null) {
             // There is already an items container open, so don't open this one.
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 86deb43..c403e76 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -69,6 +69,7 @@
 
     protected final Launcher mLauncher;
     protected final SwipeDetector mDetector;
+    protected final SwipeDetector.Direction mSwipeDirection;
 
     private boolean mNoIntercept;
     protected int mStartContainerType;
@@ -105,6 +106,7 @@
     public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
         mLauncher = l;
         mDetector = new SwipeDetector(l, this, dir);
+        mSwipeDirection = dir;
     }
 
     protected long getAtomicDuration() {
@@ -230,6 +232,11 @@
 
     @Override
     public void onDragStart(boolean start) {
+        if (com.android.launcher3.TestProtocol.sDebugTracing) {
+            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                    "AbstractStateChangeTouchController.onDragStart() called with: start = [" +
+                            start + "]");
+        }
         mStartState = mLauncher.getStateManager().getState();
         if (mStartState == ALL_APPS) {
             mStartContainerType = LauncherLogProto.ContainerType.ALLAPPS;
@@ -261,8 +268,14 @@
     public boolean onDrag(float displacement) {
         float deltaProgress = mProgressMultiplier * (displacement - mDisplacementShift);
         float progress = deltaProgress + mStartProgress;
+        if (com.android.launcher3.TestProtocol.sDebugTracing) {
+            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                    "AbstractStateChangeTouchController.onDrag() called with: displacement = [" +
+                            displacement + "], progress = [" + progress + "]");
+        }
         updateProgress(progress);
-        boolean isDragTowardPositive = (displacement - mDisplacementShift) < 0;
+        boolean isDragTowardPositive = mSwipeDirection.isPositive(
+                displacement - mDisplacementShift);
         if (progress <= 0) {
             if (reinitCurrentAnimation(false, isDragTowardPositive)) {
                 mDisplacementShift = displacement;
@@ -379,6 +392,12 @@
                     ? MIN_PROGRESS_TO_ALL_APPS : SUCCESS_TRANSITION_PROGRESS;
             targetState = (interpolatedProgress > successProgress) ? mToState : mFromState;
         }
+        if (com.android.launcher3.TestProtocol.sDebugTracing) {
+            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                    "AbstractStateChangeTouchController.onDragEnd() called with: velocity = [" +
+                            velocity + "], fling = [" + fling + "], target state: " +
+                            targetState.getClass().getSimpleName());
+        }
 
         final float endProgress;
         final float startProgress;
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 3639090..3c77860 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -23,7 +23,6 @@
 import static com.android.launcher3.Launcher.REQUEST_BIND_PENDING_APPWIDGET;
 import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET;
 import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS;
-import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_DEFAULT;
 
 import android.app.AlertDialog;
 import android.content.Intent;
@@ -33,6 +32,8 @@
 import android.view.View.OnClickListener;
 import android.widget.Toast;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.FolderInfo;
@@ -50,8 +51,6 @@
 import com.android.launcher3.widget.PendingAppWidgetHostView;
 import com.android.launcher3.widget.WidgetAddFlowHandler;
 
-import androidx.annotation.Nullable;
-
 /**
  * Class for handling clicks on workspace and all-apps items
  */
@@ -67,6 +66,14 @@
     }
 
     private static void onClick(View v, String sourceContainer) {
+        if (com.android.launcher3.TestProtocol.sDebugTracing) {
+            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                    "onClick() called with: v = [" + v.getClass().getSimpleName() +
+                            "], sourceContainer = [" +
+                            (sourceContainer != null ?
+                                    sourceContainer.getClass().getSimpleName() : "null")
+                            + "]");
+        }
         // Make sure that rogue clicks don't get through while allapps is launching, or after the
         // view has detached (it's possible for this to happen if the view is removed mid touch).
         if (v.getWindowToken() == null) {
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index babbcdd..003b442 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -74,6 +74,10 @@
     }
 
     private static boolean onAllAppsItemLongClick(View v) {
+        if (com.android.launcher3.TestProtocol.sDebugTracing) {
+            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                    "onAllAppsItemLongClick");
+        }
         Launcher launcher = Launcher.getLauncher(v.getContext());
         if (!canStartDrag(launcher)) return false;
         // When we have exited all apps or are in transition, disregard long clicks
diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java
index a0a410e..e558fc7 100644
--- a/src/com/android/launcher3/touch/SwipeDetector.java
+++ b/src/com/android/launcher3/touch/SwipeDetector.java
@@ -24,6 +24,8 @@
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 
+import com.android.launcher3.Utilities;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
@@ -64,20 +66,25 @@
 
     public static abstract class Direction {
 
-        abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint);
+        abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint,
+                boolean isRtl);
 
         /**
          * Distance in pixels a touch can wander before we think the user is scrolling.
          */
         abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos);
 
-        abstract float getVelocity(VelocityTracker tracker);
+        abstract float getVelocity(VelocityTracker tracker, boolean isRtl);
+
+        abstract boolean isPositive(float displacement);
+
+        abstract boolean isNegative(float displacement);
     }
 
     public static final Direction VERTICAL = new Direction() {
 
         @Override
-        float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint) {
+        float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint, boolean isRtl) {
             return ev.getY(pointerIndex) - refPoint.y;
         }
 
@@ -87,16 +94,32 @@
         }
 
         @Override
-        float getVelocity(VelocityTracker tracker) {
+        float getVelocity(VelocityTracker tracker, boolean isRtl) {
             return tracker.getYVelocity();
         }
+
+        @Override
+        boolean isPositive(float displacement) {
+            // Up
+            return displacement < 0;
+        }
+
+        @Override
+        boolean isNegative(float displacement) {
+            // Down
+            return displacement > 0;
+        }
     };
 
     public static final Direction HORIZONTAL = new Direction() {
 
         @Override
-        float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint) {
-            return ev.getX(pointerIndex) - refPoint.x;
+        float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint, boolean isRtl) {
+            float displacement = ev.getX(pointerIndex) - refPoint.x;
+            if (isRtl) {
+                displacement = -displacement;
+            }
+            return displacement;
         }
 
         @Override
@@ -105,8 +128,24 @@
         }
 
         @Override
-        float getVelocity(VelocityTracker tracker) {
-            return tracker.getXVelocity();
+        float getVelocity(VelocityTracker tracker, boolean isRtl) {
+            float velocity = tracker.getXVelocity();
+            if (isRtl) {
+                velocity = -velocity;
+            }
+            return velocity;
+        }
+
+        @Override
+        boolean isPositive(float displacement) {
+            // Right
+            return displacement > 0;
+        }
+
+        @Override
+        boolean isNegative(float displacement) {
+            // Left
+            return displacement < 0;
         }
     };
 
@@ -159,6 +198,7 @@
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
     private final Direction mDir;
+    private final boolean mIsRtl;
 
     private final float mTouchSlop;
     private final float mMaxVelocity;
@@ -183,14 +223,15 @@
     }
 
     public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) {
-        this(ViewConfiguration.get(context), l, dir);
+        this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources()));
     }
 
     @VisibleForTesting
     protected SwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
-            @NonNull Direction dir) {
+            @NonNull Direction dir, boolean isRtl) {
         mListener = l;
         mDir = dir;
+        mIsRtl = isRtl;
         mTouchSlop = config.getScaledTouchSlop();
         mMaxVelocity = config.getScaledMaximumFlingVelocity();
     }
@@ -212,8 +253,8 @@
         }
 
         // Check if the client is interested in scroll in current direction.
-        if (((mScrollConditions & DIRECTION_NEGATIVE) > 0 && mDisplacement > 0) ||
-                ((mScrollConditions & DIRECTION_POSITIVE) > 0 && mDisplacement < 0)) {
+        if (((mScrollConditions & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(mDisplacement)) ||
+                ((mScrollConditions & DIRECTION_POSITIVE) > 0 && mDir.isPositive(mDisplacement))) {
             return true;
         }
         return false;
@@ -259,7 +300,7 @@
                 if (pointerIndex == INVALID_POINTER_ID) {
                     break;
                 }
-                mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos);
+                mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos, mIsRtl);
 
                 // handle state and listener calls.
                 if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) {
@@ -315,7 +356,7 @@
      * @see #DIRECTION_BOTH
      */
     public boolean wasInitialTouchPositive() {
-        return mSubtractDisplacement < 0;
+        return mDir.isPositive(mSubtractDisplacement);
     }
 
     private boolean reportDragging() {
@@ -332,7 +373,7 @@
 
     private void reportDragEnd() {
         mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
-        float velocity = mDir.getVelocity(mVelocityTracker) / 1000;
+        float velocity = mDir.getVelocity(mVelocityTracker, mIsRtl) / 1000;
         if (DBG) {
             Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f",
                     mDisplacement, velocity));
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 4545a1e..bd6bfd6 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -114,16 +114,28 @@
     }
 
     protected boolean findActiveController(MotionEvent ev) {
+        if (com.android.launcher3.TestProtocol.sDebugTracing) {
+            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                    "mActiveController = null");
+        }
         mActiveController = null;
 
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
         if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
+            if (com.android.launcher3.TestProtocol.sDebugTracing) {
+                android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                        "setting controller1: " + topView.getClass().getSimpleName());
+            }
             mActiveController = topView;
             return true;
         }
 
         for (TouchController controller : mControllers) {
             if (controller.onControllerInterceptTouchEvent(ev)) {
+                if (com.android.launcher3.TestProtocol.sDebugTracing) {
+                    android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                            "setting controller1: " + controller.getClass().getSimpleName());
+                }
                 mActiveController = controller;
                 return true;
             }
@@ -193,8 +205,17 @@
         }
 
         if (mActiveController != null) {
+            if (com.android.launcher3.TestProtocol.sDebugTracing) {
+                android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                        "BaseDragLayer before onControllerTouchEvent " +
+                                mActiveController.getClass().getSimpleName());
+            }
             return mActiveController.onControllerTouchEvent(ev);
         } else {
+            if (com.android.launcher3.TestProtocol.sDebugTracing) {
+                android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
+                        "BaseDragLayer no controller");
+            }
             // In case no child view handled the touch event, we may not get onIntercept anymore
             return findActiveController(ev);
         }
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index d1e2c88..49ec292 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -427,7 +427,9 @@
                         public void onAnimationEnd(Animator animation) {
                             folderIcon.setBackgroundVisible(true);
                             folderIcon.animateBgShadowAndStroke();
-                            folderIcon.animateDotScale(0, 1f);
+                            if (folderIcon.hasDot()) {
+                                folderIcon.animateDotScale(0, 1f);
+                            }
                         }
                     });
                 } else {
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundAppState.java b/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundAppState.java
deleted file mode 100644
index 9133b07..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundAppState.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2018 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.launcher3.uioverrides;
-
-/**
- * A dummy background app state
- */
-public class BackgroundAppState extends OverviewState {
-
-    public BackgroundAppState(int id) {
-        super(id);
-    }
-}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
similarity index 97%
rename from src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
rename to src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
index bca335d..7006d77 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.uioverrides.states;
 
 import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
 import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN;
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
similarity index 89%
rename from src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
rename to src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
index 56e0aa5..aeba788 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.uioverrides.states;
 
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 
@@ -36,4 +36,8 @@
     public static OverviewState newPeekState(int id) {
         return new OverviewState(id);
     }
+
+    public static OverviewState newSwitchState(int id) {
+        return new OverviewState(id);
+    }
 }
diff --git a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
index b600473..4ebf54c 100644
--- a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
+++ b/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
@@ -15,9 +15,12 @@
  */
 package com.android.launcher3.touch;
 
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyFloat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
 import android.util.Log;
 import android.view.ViewConfiguration;
 
@@ -29,11 +32,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyFloat;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -63,7 +64,7 @@
         doReturn(orgConfig.getScaledMaximumFlingVelocity()).when(mMockConfig)
                 .getScaledMaximumFlingVelocity();
 
-        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL);
+        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL, false);
         mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
         mTouchSlop = orgConfig.getScaledTouchSlop();
         doReturn(mTouchSlop).when(mMockConfig).getScaledTouchSlop();
@@ -72,7 +73,19 @@
     }
 
     @Test
-    public void testDragStart_vertical() {
+    public void testDragStart_verticalPositive() {
+        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL, false);
+        mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+        mGenerator.put(0, 100, 100);
+        mGenerator.move(0, 100, 100 - mTouchSlop);
+        // TODO: actually calculate the following parameters and do exact value checks.
+        verify(mMockListener).onDragStart(anyBoolean());
+    }
+
+    @Test
+    public void testDragStart_verticalNegative() {
+        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL, false);
+        mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_NEGATIVE, false);
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100, 100 + mTouchSlop);
         // TODO: actually calculate the following parameters and do exact value checks.
@@ -88,9 +101,42 @@
     }
 
     @Test
-    public void testDragStart_horizontal() {
-        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL);
-        mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
+    public void testDragStart_horizontalPositive() {
+        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL, false);
+        mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+
+        mGenerator.put(0, 100, 100);
+        mGenerator.move(0, 100 + mTouchSlop, 100);
+        // TODO: actually calculate the following parameters and do exact value checks.
+        verify(mMockListener).onDragStart(anyBoolean());
+    }
+
+    @Test
+    public void testDragStart_horizontalNegative() {
+        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL, false);
+        mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_NEGATIVE, false);
+
+        mGenerator.put(0, 100, 100);
+        mGenerator.move(0, 100 - mTouchSlop, 100);
+        // TODO: actually calculate the following parameters and do exact value checks.
+        verify(mMockListener).onDragStart(anyBoolean());
+    }
+
+    @Test
+    public void testDragStart_horizontalRtlPositive() {
+        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL, true);
+        mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+
+        mGenerator.put(0, 100, 100);
+        mGenerator.move(0, 100 - mTouchSlop, 100);
+        // TODO: actually calculate the following parameters and do exact value checks.
+        verify(mMockListener).onDragStart(anyBoolean());
+    }
+
+    @Test
+    public void testDragStart_horizontalRtlNegative() {
+        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL, true);
+        mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_NEGATIVE, false);
 
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100 + mTouchSlop, 100);
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 6f2f280..91ebd9b 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -34,6 +34,7 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.TestProtocol;
 import com.android.launcher3.popup.ArrowPopup;
 import com.android.launcher3.tapl.AllApps;
 import com.android.launcher3.tapl.AppIcon;
@@ -327,18 +328,23 @@
     @Test
     @PortraitLandscape
     public void testDragAppIcon() throws Throwable {
-        LauncherActivityInfo settingsApp = getSettingsApp();
+        try {
+            TestProtocol.sDebugTracing = true;
+            LauncherActivityInfo settingsApp = getSettingsApp();
 
-        final String appName = settingsApp.getLabel().toString();
-        // 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(appName).
-                dragToWorkspace().
-                getWorkspaceAppIcon(appName).
-                launch(settingsApp.getComponentName().getPackageName());
+            final String appName = settingsApp.getLabel().toString();
+            // 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(appName).
+                    dragToWorkspace().
+                    getWorkspaceAppIcon(appName).
+                    launch(settingsApp.getComponentName().getPackageName());
+        } finally {
+            TestProtocol.sDebugTracing = false;
+        }
     }
 
     @Test
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 125deaf..b71f790 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -166,23 +166,28 @@
     }
 
     public NavigationModel getNavigationModel() {
-        return isSwipeUpEnabled() ? NavigationModel.TWO_BUTTON : NavigationModel.THREE_BUTTON;
-    }
-
-    static boolean needSlowGestures() {
-        return Build.MODEL.contains("Cuttlefish");
-    }
-
-    private boolean isSwipeUpEnabled() {
         final Context baseContext = mInstrumentation.getTargetContext();
         try {
             // Workaround, use constructed context because both the instrumentation context and the
             // app context are not constructed with resources that take overlays into account
-            Context ctx = baseContext.createPackageContext(getLauncherPackageName(), 0);
-            return !QuickStepContract.isLegacyMode(ctx);
+            final Context ctx = baseContext.createPackageContext("android", 0);
+            if (QuickStepContract.isGesturalMode(ctx)) {
+                return NavigationModel.ZERO_BUTTON;
+            } else if (QuickStepContract.isSwipeUpMode(ctx)) {
+                return NavigationModel.TWO_BUTTON;
+            } else if (QuickStepContract.isLegacyMode(ctx)) {
+                return NavigationModel.THREE_BUTTON;
+            } else {
+                fail("Can't detect navigation mode");
+            }
         } catch (PackageManager.NameNotFoundException e) {
-            return false;
+            fail(e.toString());
         }
+        return NavigationModel.THREE_BUTTON;
+    }
+
+    static boolean needSlowGestures() {
+        return Build.MODEL.contains("Cuttlefish");
     }
 
     static void log(String message) {
@@ -233,9 +238,13 @@
     private UiObject2 verifyContainerType(ContainerType containerType) {
         assertEquals("Unexpected display rotation",
                 mExpectedRotation, mDevice.getDisplayRotation());
-        assertTrue("Presence of recents button doesn't match isSwipeUpEnabled()",
-                isSwipeUpEnabled() ==
-                        (mDevice.findObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")) == null));
+        final NavigationModel navigationModel = getNavigationModel();
+        assertTrue("Presence of recents button doesn't match the interaction mode",
+                (navigationModel == NavigationModel.THREE_BUTTON) ==
+                        mDevice.hasObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")));
+        assertTrue("Presence of home button doesn't match the interaction mode",
+                (navigationModel != NavigationModel.ZERO_BUTTON) ==
+                        mDevice.hasObject(By.res(SYSTEMUI_PACKAGE, "home")));
         log("verifyContainerType: " + containerType);
 
         try (Closable c = addContextLayer(
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 9979f50..22b5564 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -136,7 +136,9 @@
 
     static void dragIconToWorkspace(LauncherInstrumentation launcher, Launchable launchable,
             Point dest, int icon_drag_speed) {
+        LauncherInstrumentation.log("dragIconToWorkspace: begin");
         launchable.getObject().drag(dest, icon_drag_speed);
+        LauncherInstrumentation.log("dragIconToWorkspace: end");
         launcher.waitUntilGone("drop_target_bar");
     }