Snap for 5434517 from 36dcc7f53e9d920bab535d0dba17f76159a0e3b9 to rvc-release

Change-Id: Ie4c12afd97c026c0bb5c5cea5a1f255ea53928b8
diff --git a/Android.bp b/Android.bp
index c583244..5acec37 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-java_library_static {
+android_library {
     name: "launcher-aosp-tapl",
     static_libs: [
         "androidx.annotation_annotation",
@@ -23,9 +23,9 @@
     ],
     srcs: [
         "tests/tapl/**/*.java",
-        "quickstep/src/com/android/quickstep/SwipeUpSetting.java",
         "src/com/android/launcher3/util/SecureSettingsObserver.java",
         "src/com/android/launcher3/TestProtocol.java",
     ],
+    manifest: "tests/tapl/AndroidManifest.xml",
     platform_apis: true,
 }
diff --git a/Android.mk b/Android.mk
index 9d92a9d..06ee66f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -16,18 +16,6 @@
 
 LOCAL_PATH := $(call my-dir)
 
-#
-# Prebuilt Java Libraries
-#
-include $(CLEAR_VARS)
-LOCAL_MODULE := libSharedSystemUI
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-LOCAL_SRC_FILES := quickstep/libs/sysui_shared.jar
-LOCAL_UNINSTALLABLE_MODULE := true
-LOCAL_SDK_VERSION := current
-include $(BUILD_PREBUILT)
-
 include $(CLEAR_VARS)
 LOCAL_MODULE := libPluginCore
 LOCAL_MODULE_TAGS := optional
@@ -37,14 +25,6 @@
 LOCAL_SDK_VERSION := current
 include $(BUILD_PREBUILT)
 
-include $(CLEAR_VARS)
-LOCAL_MODULE := libLauncherProtos
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-LOCAL_SRC_FILES := libs/launcher_protos.jar
-LOCAL_UNINSTALLABLE_MODULE := true
-LOCAL_SDK_VERSION := current
-include $(BUILD_PREBUILT)
 #
 # Build rule for plugin lib (needed to write a plugin).
 #
@@ -53,7 +33,7 @@
 LOCAL_AAPT2_ONLY := true
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_JAVA_LIBRARIES := libPluginCore
+LOCAL_STATIC_JAVA_LIBRARIES:= libPluginCore
 
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src_plugins)
diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java b/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java
index 12fa46b..0a2f18f 100644
--- a/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java
+++ b/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java
@@ -26,12 +26,11 @@
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.os.Bundle;
-import com.google.android.material.circularreveal.cardview.CircularRevealCardView;
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewAnimationUtils;
+import android.view.inputmethod.InputMethodManager;
 import android.widget.GridView;
 import android.widget.ImageButton;
 import android.widget.PopupMenu;
@@ -41,6 +40,9 @@
 import androidx.lifecycle.ViewModelProvider;
 import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory;
 
+import com.google.android.material.circularreveal.cardview.CircularRevealCardView;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
 import java.util.HashSet;
 import java.util.Set;
 
@@ -141,6 +143,16 @@
 
     public void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
+
+        if (Intent.ACTION_MAIN.equals(intent.getAction())) {
+            // Hide keyboard.
+            final View v = getWindow().peekDecorView();
+            if (v != null && v.getWindowToken() != null) {
+                getSystemService(InputMethodManager.class).hideSoftInputFromWindow(
+                        v.getWindowToken(), 0);
+            }
+        }
+
         // A new intent will bring the launcher to top. Hide the app drawer to reset the state.
         showAppDrawer(false);
     }
diff --git a/build.gradle b/build.gradle
index 15a3513..e296455 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,6 +9,9 @@
     }
 }
 
+final String ANDROID_TOP = "${rootDir}/../../.."
+final String FRAMEWORK_PREBUILTS_DIR = "${ANDROID_TOP}/prebuilts/framework_intermediates/"
+
 apply plugin: 'com.android.application'
 apply plugin: 'com.google.protobuf'
 
@@ -148,16 +151,16 @@
     implementation "androidx.recyclerview:recyclerview:${ANDROID_X_VERSION}"
     implementation "androidx.preference:preference:${ANDROID_X_VERSION}"
     implementation project(':IconLoader')
-    implementation fileTree(dir: "libs", include: 'launcher_protos.jar')
+    implementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/libs", include: 'launcher_protos.jar')
 
     // Recents lib dependency
-    withQuickstepImplementation fileTree(dir: "quickstep/libs", include: 'sysui_shared.jar')
+    withQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/quickstep/libs", include: 'sysui_shared.jar')
 
     // Recents lib dependency for Go
-    withQuickstepIconRecentsImplementation fileTree(dir: "quickstep/libs", include: 'sysui_shared.jar')
+    withQuickstepIconRecentsImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/quickstep/libs", include: 'sysui_shared.jar')
 
     // Required for AOSP to compile. This is already included in the sysui_shared.jar
-    withoutQuickstepImplementation fileTree(dir: "libs", include: 'plugin_core.jar')
+    withoutQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/libs", include: 'plugin_core.jar')
 
     testImplementation 'junit:junit:4.12'
     androidTestImplementation "org.mockito:mockito-core:1.9.5"
diff --git a/go/quickstep/res/drawable/clear_all_button.xml b/go/quickstep/res/drawable/clear_all_button.xml
new file mode 100644
index 0000000..acac32d
--- /dev/null
+++ b/go/quickstep/res/drawable/clear_all_button.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/clear_all_button_bg"/>
+    <corners android:radius="4dp"/>
+</shape>
diff --git a/go/quickstep/res/layout/icon_recents_root_view.xml b/go/quickstep/res/layout/icon_recents_root_view.xml
index 6c50950..fddb1d3 100644
--- a/go/quickstep/res/layout/icon_recents_root_view.xml
+++ b/go/quickstep/res/layout/icon_recents_root_view.xml
@@ -19,10 +19,38 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
-    <androidx.recyclerview.widget.RecyclerView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/recent_task_recycler_view"
+    <LinearLayout
+        android:id="@+id/recent_task_content_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:scrollbars="none"/>
+        android:orientation="vertical"
+        android:visibility="gone">
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/recent_task_recycler_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:scrollbars="none"/>
+        <Button
+            android:id="@+id/clear_all_button"
+            android:layout_width="@dimen/clear_all_button_width"
+            android:layout_height="@dimen/clear_all_button_height"
+            android:layout_gravity="center_horizontal"
+            android:layout_marginVertical="@dimen/task_item_half_vert_margin"
+            android:background="@drawable/clear_all_button"
+            android:gravity="center"
+            android:text="@string/recents_clear_all"
+            android:textAllCaps="false"
+            android:textColor="@color/clear_all_button_text"
+            android:textSize="14sp"/>
+    </LinearLayout>
+    <TextView
+        android:id="@+id/recent_task_empty_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:text="@string/recents_empty_message"
+        android:textColor="@android:color/white"
+        android:textSize="25sp"
+        android:visibility="gone"/>
 </com.android.quickstep.views.IconRecentsView>
\ No newline at end of file
diff --git a/go/quickstep/res/layout/task_item_view.xml b/go/quickstep/res/layout/task_item_view.xml
new file mode 100644
index 0000000..048e9c5
--- /dev/null
+++ b/go/quickstep/res/layout/task_item_view.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<com.android.quickstep.views.TaskItemView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal">
+    <FrameLayout
+        android:id="@+id/task_icon_and_thumbnail"
+        android:layout_width="@dimen/task_thumbnail_and_icon_view_size"
+        android:layout_height="@dimen/task_thumbnail_and_icon_view_size"
+        android:layout_gravity="center_vertical"
+        android:layout_marginHorizontal="8dp"
+        android:layout_marginVertical="@dimen/task_item_half_vert_margin">
+        <ImageView
+            android:id="@+id/task_thumbnail"
+            android:layout_width="@dimen/task_thumbnail_width"
+            android:layout_height="@dimen/task_thumbnail_height"
+            android:layout_gravity="top|start"/>
+        <ImageView
+            android:id="@+id/task_icon"
+            android:layout_width="@dimen/task_icon_size"
+            android:layout_height="@dimen/task_icon_size"
+            android:layout_gravity="bottom|end"/>
+    </FrameLayout>
+    <TextView
+        android:id="@+id/task_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginHorizontal="8dp"
+        android:singleLine="true"
+        android:textColor="@android:color/white"
+        android:textSize="24sp"/>
+</com.android.quickstep.views.TaskItemView>
diff --git a/go/quickstep/res/values/colors.xml b/go/quickstep/res/values/colors.xml
new file mode 100644
index 0000000..ff9dc9c
--- /dev/null
+++ b/go/quickstep/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<resources>
+    <color name="clear_all_button_bg">#FFDADCE0</color>
+    <color name="clear_all_button_text">#FF5F6368</color>
+</resources>
diff --git a/go/quickstep/res/values/dimens.xml b/go/quickstep/res/values/dimens.xml
new file mode 100644
index 0000000..e2fa387
--- /dev/null
+++ b/go/quickstep/res/values/dimens.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<resources>
+    <dimen name="task_item_half_vert_margin">8dp</dimen>
+    <dimen name="task_thumbnail_and_icon_view_size">60dp</dimen>
+    <dimen name="task_thumbnail_height">60dp</dimen>
+    <dimen name="task_thumbnail_width">36dp</dimen>
+    <dimen name="task_icon_size">36dp</dimen>
+
+    <dimen name="clear_all_button_width">106dp</dimen>
+    <dimen name="clear_all_button_height">36dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
index 96b4ae5..d189c50 100644
--- a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
+++ b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
@@ -31,7 +31,8 @@
     @Override
     protected void composeRecentsLaunchAnimator(AnimatorSet anim, View v,
             RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
-        //TODO: Implement this based off IconRecentsView
+        // Stubbed. Recents launch animation will come from the recents view itself and will not
+        // use remote animations.
     }
 
     @Override
diff --git a/go/quickstep/src/com/android/launcher3/LauncherRecentsToActivityHelper.java b/go/quickstep/src/com/android/launcher3/LauncherRecentsToActivityHelper.java
new file mode 100644
index 0000000..c12da94
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/LauncherRecentsToActivityHelper.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+
+import com.android.quickstep.RecentsToActivityHelper;
+
+/**
+ * {@link RecentsToActivityHelper} for when the recents implementation is contained in
+ * {@link Launcher}.
+ */
+public final class LauncherRecentsToActivityHelper implements RecentsToActivityHelper {
+
+    private final Launcher mLauncher;
+
+    public LauncherRecentsToActivityHelper(Launcher launcher) {
+        mLauncher = launcher;
+    }
+
+    @Override
+    public void leaveRecents() {
+        mLauncher.getStateManager().goToState(NORMAL);
+    }
+}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index d7cba39..d9b9686 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,8 +25,13 @@
 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.SysUINavigationMode;
+import com.android.quickstep.views.IconRecentsView;
 
 import java.util.ArrayList;
 
@@ -35,7 +39,7 @@
  * Provides recents-related {@link UiFactory} logic and classes.
  */
 public abstract class RecentsUiFactory {
-    
+
     public static final boolean GO_LOW_RAM_RECENTS_ENABLED = true;
     // Scale recents takes before animating in
     private static final float RECENTS_PREPARE_SCALE = 1.33f;
@@ -48,8 +52,8 @@
             list.add(new LandscapeStatesTouchController(launcher));
             list.add(new LandscapeEdgeSwipeController(launcher));
         } else {
-            boolean allowDragToOverview = OverviewInteractionState.INSTANCE.get(launcher)
-                    .isSwipeUpGestureEnabled();
+            boolean allowDragToOverview = SysUINavigationMode.INSTANCE.get(launcher)
+                    .getMode().hasGestures;
             list.add(new PortraitStatesTouchController(launcher, allowDragToOverview));
         }
         if (FeatureFlags.PULL_DOWN_STATUS_BAR && Utilities.IS_DEBUG_DEVICE
@@ -87,7 +91,10 @@
      *
      * @param launcher the launcher activity
      */
-    public static void resetOverview(Launcher launcher) {}
+    public static void resetOverview(Launcher launcher) {
+        IconRecentsView recentsView = launcher.getOverviewPanel();
+        recentsView.setTransitionedFromApp(false);
+    }
 
     /**
      * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index f1cb75b..0b12ab0 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -16,15 +16,15 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
-import static com.android.quickstep.views.IconRecentsView.TRANSLATION_Y_FACTOR;
 
 import android.util.FloatProperty;
 
-import androidx.annotation.NonNull;
-
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherRecentsToActivityHelper;
 import com.android.quickstep.views.IconRecentsView;
 
+import androidx.annotation.NonNull;
+
 /**
  * State handler for Go's {@link IconRecentsView}.
  */
@@ -33,11 +33,8 @@
 
     public RecentsViewStateController(@NonNull Launcher launcher) {
         super(launcher);
-    }
-
-    @Override
-    FloatProperty<IconRecentsView> getTranslationYFactorProperty() {
-        return TRANSLATION_Y_FACTOR;
+        launcher.<IconRecentsView>getOverviewPanel().setRecentsToActivityHelper(
+                new LauncherRecentsToActivityHelper(launcher));
     }
 
     @Override
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 81%
rename from go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
rename to go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 47e0e61..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;
@@ -43,8 +43,8 @@
     }
 
     @Override
-    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
-        return new float[] {1f, 0f};
+    public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
+        return new ScaleAndTranslation(1f, 0f, 0f);
     }
 
     @Override
@@ -84,9 +84,24 @@
         super.onBackPressed(launcher);
     }
 
-
     public static float getDefaultSwipeHeight(Launcher launcher) {
-        DeviceProfile dp = launcher.getDeviceProfile();
+        return getDefaultSwipeHeight(launcher.getDeviceProfile());
+    }
+
+    public static float getDefaultSwipeHeight(DeviceProfile dp) {
         return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
     }
+
+
+    public static OverviewState newBackgroundState(int id) {
+        return new OverviewState(id);
+    }
+
+    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/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index f199643..d1d697c 100644
--- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -15,15 +15,30 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.quickstep.util.RemoteAnimationProvider.getLayer;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
+import android.graphics.Matrix;
+import android.graphics.Rect;
 import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.NonNull;
 
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.views.IconRecentsView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 
 /**
  * Provider for the atomic remote window animation from the app to the overview.
@@ -33,13 +48,17 @@
 final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> implements
         RemoteAnimationProvider {
 
-    private static final long RECENTS_LAUNCH_DURATION = 250;
+    private static final long APP_TO_THUMBNAIL_FADE_DURATION = 50;
+    private static final long APP_SCALE_DOWN_DURATION = 400;
     private static final String TAG = "AppToOverviewAnimationProvider";
 
     private final ActivityControlHelper<T> mHelper;
+    private final int mTargetTaskId;
+    private IconRecentsView mRecentsView;
 
     AppToOverviewAnimationProvider(ActivityControlHelper<T> helper, int targetTaskId) {
         mHelper = helper;
+        mTargetTaskId = targetTaskId;
     }
 
     /**
@@ -54,35 +73,163 @@
                         false /* animate activity */, (controller) -> {
                             controller.dispatchOnStart();
                             ValueAnimator anim = controller.getAnimationPlayer()
-                                    .setDuration(RECENTS_LAUNCH_DURATION);
+                                    .setDuration(getRecentsLaunchDuration());
                             anim.setInterpolator(FAST_OUT_SLOW_IN);
                             anim.start();
                         });
         factory.onRemoteAnimationReceived(null);
-        factory.createActivityController(RECENTS_LAUNCH_DURATION);
+        factory.createActivityController(getRecentsLaunchDuration());
+        mRecentsView = activity.getOverviewPanel();
         return false;
     }
 
     /**
-     * Create remote window animation from the currently running app to the overview panel.
+     * Create remote window animation from the currently running app to the overview panel. Should
+     * be called after {@link #onActivityReady}.
      *
      * @param targetCompats the target apps
      * @return animation from app to overview
      */
     @Override
     public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
-        //TODO: Implement the overview to app window animation for Go.
         AnimatorSet anim = new AnimatorSet();
-        anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION));
+        if (mRecentsView == null) {
+            if (Log.isLoggable(TAG, Log.WARN)) {
+                Log.w(TAG, "No recents view. Using stub animation.");
+            }
+            anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
+            return anim;
+        }
+
+        RemoteAnimationTargetSet targetSet =
+                new RemoteAnimationTargetSet(targetCompats, MODE_CLOSING);
+        mRecentsView.setTransitionedFromApp(!targetSet.isAnimatingHome());
+
+        RemoteAnimationTargetCompat recentsTarget = null;
+        RemoteAnimationTargetCompat closingAppTarget = null;
+
+        for (RemoteAnimationTargetCompat target : targetCompats) {
+            if (target.mode == MODE_OPENING) {
+                recentsTarget = target;
+            } else if (target.mode == MODE_CLOSING && target.taskId == mTargetTaskId) {
+                closingAppTarget = target;
+            }
+        }
+
+        if (closingAppTarget == null) {
+            if (Log.isLoggable(TAG, Log.WARN)) {
+                Log.w(TAG, "No closing app target. Using stub animation.");
+            }
+            anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
+            return anim;
+        }
+        if (recentsTarget == null) {
+            if (Log.isLoggable(TAG, Log.WARN)) {
+                Log.w(TAG, "No recents target. Using stub animation.");
+            }
+            anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
+            return anim;
+        }
+
+        View thumbnailView = mRecentsView.getThumbnailViewForTask(mTargetTaskId);
+        if (thumbnailView == null) {
+            // TODO: We should either 1) guarantee the view is loaded before attempting this
+            // or 2) have a backup animation.
+            if (Log.isLoggable(TAG, Log.WARN)) {
+                Log.w(TAG, "No thumbnail view for running task. Using stub animation.");
+            }
+            anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
+            return anim;
+        }
+
+        playAppScaleDownAnim(anim, closingAppTarget, recentsTarget, thumbnailView);
+
         return anim;
     }
 
     /**
+     * Animate a closing app to scale down to the location of the thumbnail view in recents.
+     *
+     * @param anim animator set
+     * @param appTarget the app surface thats closing
+     * @param recentsTarget the surface containing recents
+     * @param thumbnailView the thumbnail view to animate to
+     */
+    private void playAppScaleDownAnim(@NonNull AnimatorSet anim,
+            @NonNull RemoteAnimationTargetCompat appTarget,
+            @NonNull RemoteAnimationTargetCompat recentsTarget, @NonNull View thumbnailView) {
+
+        // Identify where the entering remote app should animate to.
+        Rect endRect = new Rect();
+        thumbnailView.getGlobalVisibleRect(endRect);
+
+        Rect appBounds = appTarget.sourceContainerBounds;
+
+        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1);
+        valueAnimator.setDuration(APP_SCALE_DOWN_DURATION);
+
+        SyncRtSurfaceTransactionApplierCompat surfaceApplier =
+                new SyncRtSurfaceTransactionApplierCompat(thumbnailView);
+
+        // Keep recents visible throughout the animation.
+        SurfaceParams[] params = new SurfaceParams[2];
+        // Closing app should stay on top.
+        int boostedMode = MODE_CLOSING;
+        params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */,
+                null /* windowCrop */, getLayer(recentsTarget, boostedMode), 0 /* cornerRadius */);
+
+        valueAnimator.addUpdateListener(new MultiValueUpdateListener() {
+            private final FloatProp mScaleX;
+            private final FloatProp mScaleY;
+            private final FloatProp mTranslationX;
+            private final FloatProp mTranslationY;
+            private final FloatProp mAlpha;
+
+            {
+                // Scale down and move to view location.
+                float endScaleX = ((float) endRect.width()) / appBounds.width();
+                mScaleX = new FloatProp(1f, endScaleX, 0, APP_SCALE_DOWN_DURATION,
+                        ACCEL_DEACCEL);
+                float endScaleY = ((float) endRect.height()) / appBounds.height();
+                mScaleY = new FloatProp(1f, endScaleY, 0, APP_SCALE_DOWN_DURATION,
+                        ACCEL_DEACCEL);
+                float endTranslationX = endRect.left -
+                        (appBounds.width() - thumbnailView.getWidth()) / 2.0f;
+                mTranslationX = new FloatProp(0, endTranslationX, 0, APP_SCALE_DOWN_DURATION,
+                        ACCEL_DEACCEL);
+                float endTranslationY = endRect.top -
+                        (appBounds.height() - thumbnailView.getHeight()) / 2.0f;
+                mTranslationY = new FloatProp(0, endTranslationY, 0, APP_SCALE_DOWN_DURATION,
+                        ACCEL_DEACCEL);
+
+                // Fade out quietly near the end to be replaced by the real view.
+                mAlpha = new FloatProp(1.0f, 0,
+                        APP_SCALE_DOWN_DURATION - APP_TO_THUMBNAIL_FADE_DURATION,
+                        APP_TO_THUMBNAIL_FADE_DURATION, ACCEL_2);
+            }
+
+            @Override
+            public void onUpdate(float percent) {
+                Matrix m = new Matrix();
+                m.setScale(mScaleX.value, mScaleY.value,
+                        appBounds.width() / 2.0f, appBounds.height() / 2.0f);
+                m.postTranslate(mTranslationX.value, mTranslationY.value);
+
+                params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, m,
+                        null /* windowCrop */, getLayer(appTarget, boostedMode),
+                        0 /* cornerRadius */);
+                surfaceApplier.scheduleApply(params);
+            }
+        });
+        anim.play(valueAnimator);
+    }
+
+    /**
      * Get duration of animation from app to overview.
      *
      * @return duration of animation
      */
     long getRecentsLaunchDuration() {
-        return RECENTS_LAUNCH_DURATION;
+        return APP_SCALE_DOWN_DURATION;
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
index abb9242..bba08a4 100644
--- a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -26,7 +26,6 @@
 
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.quickstep.views.IconRecentsView;
 
@@ -110,11 +109,6 @@
     }
 
     @Override
-    public AlphaProperty getAlphaProperty(RecentsActivity activity) {
-        return activity.getDragLayer().getAlphaProperty(0);
-    }
-
-    @Override
     public int getContainerType() {
         return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
     }
diff --git a/go/quickstep/src/com/android/quickstep/FallbackRecentsToActivityHelper.java b/go/quickstep/src/com/android/quickstep/FallbackRecentsToActivityHelper.java
new file mode 100644
index 0000000..a845f93
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/FallbackRecentsToActivityHelper.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+/**
+ * {@link RecentsToActivityHelper} for when we are using the fallback recents in
+ * {@link BaseRecentsActivity}.
+ */
+public final class FallbackRecentsToActivityHelper implements RecentsToActivityHelper {
+
+    BaseRecentsActivity mActivity;
+
+    public FallbackRecentsToActivityHelper(BaseRecentsActivity activity) {
+        mActivity = activity;
+    }
+
+    @Override
+    public void leaveRecents() {
+        mActivity.startHome();
+    }
+}
diff --git a/go/quickstep/src/com/android/quickstep/GoActivityControlHelper.java b/go/quickstep/src/com/android/quickstep/GoActivityControlHelper.java
index 7078871..8b6f8bc 100644
--- a/go/quickstep/src/com/android/quickstep/GoActivityControlHelper.java
+++ b/go/quickstep/src/com/android/quickstep/GoActivityControlHelper.java
@@ -34,6 +34,11 @@
     }
 
     @Override
+    public void onAssistantVisibilityChanged(float visibility) {
+        // Go does not support assistant visibility transitions.
+    }
+
+    @Override
     public HomeAnimationFactory prepareHomeUI(T activity) {
         // Go does not support gestures from app to home.
         return null;
diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
index 76685f3..40db7dd 100644
--- a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -21,10 +21,9 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherInitListener;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.quickstep.views.IconRecentsView;
 
 import java.util.function.BiPredicate;
@@ -40,10 +39,14 @@
     public AnimationFactory prepareRecentsUI(Launcher activity,
             boolean activityVisible, boolean animateActivity,
             Consumer<AnimatorPlaybackController> callback) {
+        LauncherState fromState = activity.getStateManager().getState();
         //TODO: Implement this based off where the recents view needs to be for app => recents anim.
         return new AnimationFactory() {
             @Override
-            public void createActivityController(long transitionLength) {}
+            public void createActivityController(long transitionLength) {
+                callback.accept(activity.getStateManager().createAnimationToNewWorkspace(
+                        fromState, OVERVIEW, transitionLength));
+            }
 
             @Override
             public void onTransitionCancelled() {}
@@ -94,11 +97,6 @@
     }
 
     @Override
-    public AlphaProperty getAlphaProperty(Launcher activity) {
-        return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP);
-    }
-
-    @Override
     public int getContainerType() {
         final Launcher launcher = getVisibleLauncher();
         return launcher != null ? launcher.getStateManager().getState().containerType
diff --git a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index d9873fe..379cc10 100644
--- a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -96,7 +96,7 @@
             if (recents == null) {
                 return false;
             }
-            //TODO: Launch last running task or go to home.
+            recents.handleOverviewCommand();
             return true;
         }
     }
@@ -146,7 +146,7 @@
         protected boolean handleCommand(long elapsedTime) {
             IconRecentsView recents = mHelper.getVisibleRecentsView();
             if (recents != null) {
-                //TODO: Launch next task in icon recents.
+                recents.handleOverviewCommand();
                 return true;
             } else if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
                 // The user tried to launch back into overview too quickly, either after
diff --git a/go/quickstep/src/com/android/quickstep/RecentsActivity.java b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
index a186aaa..f2ca368 100644
--- a/go/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -36,6 +36,7 @@
         setContentView(R.layout.fallback_recents_activity);
         mRecentsRootView = findViewById(R.id.drag_layer);
         mIconRecentsView = findViewById(R.id.overview_panel);
+        mIconRecentsView.setRecentsToActivityHelper(new FallbackRecentsToActivityHelper(this));
     }
 
     @Override
@@ -60,15 +61,13 @@
 
     @Override
     public ActivityOptions getActivityLaunchOptions(View v) {
-        //TODO: Hook into recents launch animation
+        // Stubbed. Recents launch animation will come from the recents view itself.
         return null;
     }
 
     @Override
     protected void onStart() {
-        // Set the alpha to 1 before calling super, as it may get set back to 0 due to
-        // onActivityStart callback.
-        mIconRecentsView.setAlpha(0);
+        mIconRecentsView.onBeginTransitionToOverview();
         super.onStart();
     }
 }
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/BackgroundAppState.java b/go/quickstep/src/com/android/quickstep/RecentsToActivityHelper.java
similarity index 61%
rename from go/quickstep/src/com/android/launcher3/uioverrides/BackgroundAppState.java
rename to go/quickstep/src/com/android/quickstep/RecentsToActivityHelper.java
index 4038c99..8f3b707 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/BackgroundAppState.java
+++ b/go/quickstep/src/com/android/quickstep/RecentsToActivityHelper.java
@@ -13,14 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.uioverrides;
+package com.android.quickstep;
 
 /**
- * State indicating that the Launcher is behind an app. Same as {@link OverviewState} for Go as we
- * do not support swipe to overview or swipe to home.
+ * Generic interface providing methods to the recents implementation that allow it to callback to
+ * the containing activity.
  */
-public final class BackgroundAppState extends OverviewState {
-    public BackgroundAppState(int id) {
-        super(id);
-    }
+public interface RecentsToActivityHelper {
+
+    /**
+     * The default action to take when leaving/closing recents. In general, this should be used to
+     * go to the appropriate home state.
+     */
+    void leaveRecents();
 }
diff --git a/go/quickstep/src/com/android/quickstep/TaskActionController.java b/go/quickstep/src/com/android/quickstep/TaskActionController.java
new file mode 100644
index 0000000..77b287b
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/TaskActionController.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import android.app.ActivityOptions;
+import android.view.View;
+
+import com.android.quickstep.views.TaskItemView;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+/**
+ * Controller that provides logic for task-related commands on recents and updating the model/view
+ * as appropriate.
+ */
+public final class TaskActionController {
+
+    private final TaskListLoader mLoader;
+    private final TaskAdapter mAdapter;
+
+    public TaskActionController(TaskListLoader loader, TaskAdapter adapter) {
+        mLoader = loader;
+        mAdapter = adapter;
+    }
+
+    /**
+     * Launch the task associated with the task holder, animating into the app.
+     *
+     * @param viewHolder the task view holder to launch
+     */
+    public void launchTask(TaskHolder viewHolder) {
+        TaskItemView itemView = (TaskItemView) (viewHolder.itemView);
+        View v = itemView.getThumbnailView();
+        int left = 0;
+        int top = 0;
+        int width = v.getMeasuredWidth();
+        int height = v.getMeasuredHeight();
+
+        ActivityOptions opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
+        ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(viewHolder.getTask().key,
+                opts, null /* resultCallback */, null /* resultCallbackHandler */);
+    }
+
+    /**
+     * Removes the task holder and the task, updating the model and the view.
+     *
+     * @param viewHolder the task view holder to remove
+     */
+    public void removeTask(TaskHolder viewHolder) {
+        int position = viewHolder.getAdapterPosition();
+        Task task = viewHolder.getTask();
+        ActivityManagerWrapper.getInstance().removeTask(task.key.id);
+        mLoader.removeTask(task);
+        mAdapter.notifyItemRemoved(position);
+    }
+
+    /**
+     * Clears all tasks and updates the model and view.
+     */
+    public void clearAllTasks() {
+        int count = mAdapter.getItemCount();
+        ActivityManagerWrapper.getInstance().removeAllRecentTasks();
+        mLoader.clearAllTasks();
+        mAdapter.notifyItemRangeRemoved(0 /* positionStart */, count);
+    }
+}
diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
index 57cd60a..c98eca6 100644
--- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java
+++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
@@ -15,15 +15,19 @@
  */
 package com.android.quickstep;
 
+import android.util.ArrayMap;
+import android.view.LayoutInflater;
 import android.view.ViewGroup;
-import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView.Adapter;
 
+import com.android.launcher3.R;
+import com.android.quickstep.views.TaskItemView;
 import com.android.systemui.shared.recents.model.Task;
 
-import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Recycler view adapter that dynamically inflates and binds {@link TaskHolder} instances with the
@@ -34,26 +38,67 @@
     private static final int MAX_TASKS_TO_DISPLAY = 6;
     private static final String TAG = "TaskAdapter";
     private final TaskListLoader mLoader;
+    private final ArrayMap<Integer, TaskItemView> mTaskIdToViewMap = new ArrayMap<>();
+    private TaskActionController mTaskActionController;
 
     public TaskAdapter(@NonNull TaskListLoader loader) {
         mLoader = loader;
     }
 
+    public void setActionController(TaskActionController taskActionController) {
+        mTaskActionController = taskActionController;
+    }
+
+    /**
+     * Get task item view for a given task id if it's attached to the view.
+     *
+     * @param taskId task id to search for
+     * @return corresponding task item view if it's attached, null otherwise
+     */
+    public @Nullable TaskItemView getTaskItemView(int taskId) {
+        return mTaskIdToViewMap.get(taskId);
+    }
+
     @Override
     public TaskHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        // TODO: Swap in an actual task view here (view w/ icon, label, etc.)
-        TextView stubView = new TextView(parent.getContext());
-        return new TaskHolder(stubView);
+        TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.task_item_view, parent, false);
+        TaskHolder holder = new TaskHolder(itemView);
+        itemView.setOnClickListener(view -> mTaskActionController.launchTask(holder));
+        return holder;
     }
 
     @Override
     public void onBindViewHolder(TaskHolder holder, int position) {
-        ArrayList<Task> tasks = mLoader.getCurrentTaskList();
+        List<Task> tasks = mLoader.getCurrentTaskList();
         if (position >= tasks.size()) {
             // Task list has updated.
             return;
         }
-        holder.bindTask(tasks.get(position));
+        Task task = tasks.get(position);
+        holder.bindTask(task);
+        mLoader.loadTaskIconAndLabel(task, () -> {
+            // Ensure holder still has the same task.
+            if (task.equals(holder.getTask())) {
+                holder.getTaskItemView().setIcon(task.icon);
+                holder.getTaskItemView().setLabel(task.titleDescription);
+            }
+        });
+        mLoader.loadTaskThumbnail(task, () -> {
+            if (task.equals(holder.getTask())) {
+                holder.getTaskItemView().setThumbnail(task.thumbnail.thumbnail);
+            }
+        });
+    }
+
+    @Override
+    public void onViewAttachedToWindow(@NonNull TaskHolder holder) {
+        mTaskIdToViewMap.put(holder.getTask().key.id, (TaskItemView) holder.itemView);
+    }
+
+    @Override
+    public void onViewDetachedFromWindow(@NonNull TaskHolder holder) {
+        mTaskIdToViewMap.remove(holder.getTask().key.id);
     }
 
     @Override
diff --git a/go/quickstep/src/com/android/quickstep/TaskHolder.java b/go/quickstep/src/com/android/quickstep/TaskHolder.java
index 1ea6d76..744afd7 100644
--- a/go/quickstep/src/com/android/quickstep/TaskHolder.java
+++ b/go/quickstep/src/com/android/quickstep/TaskHolder.java
@@ -15,34 +15,46 @@
  */
 package com.android.quickstep;
 
-import android.widget.TextView;
-
+import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
+import com.android.quickstep.views.TaskItemView;
 import com.android.systemui.shared.recents.model.Task;
 
 /**
  * A recycler view holder that holds the task view and binds {@link Task} content (app title, icon,
  * etc.) to the view.
  */
-final class TaskHolder extends ViewHolder {
+public final class TaskHolder extends ViewHolder {
 
-    // TODO: Implement the actual task view to be held.
-    // For now, we just use a simple text view.
-    private final TextView mStubView;
+    private final TaskItemView mTaskItemView;
+    private Task mTask;
 
-    public TaskHolder(TextView stubView) {
-        super(stubView);
-        mStubView = stubView;
+    public TaskHolder(TaskItemView itemView) {
+        super(itemView);
+        mTaskItemView = itemView;
+    }
+
+    public TaskItemView getTaskItemView() {
+        return mTaskItemView;
     }
 
     /**
-     * Bind task content to the view. This includes the task icon and title as well as binding
-     * input handlers such as which task to launch/remove.
+     * Bind a task to the holder, resetting the view and preparing it for content to load in.
      *
-     * @param task the task to bind to the view this
+     * @param task the task to bind to the view
      */
     public void bindTask(Task task) {
-        mStubView.setText("Stub task view: " + task.titleDescription);
+        mTask = task;
+        mTaskItemView.resetTaskItemView();
+    }
+
+    /**
+     * Gets the task currently bound to this view
+     *
+     * @return the current task
+     */
+    public @NonNull Task getTask() {
+        return mTask;
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/TaskListLoader.java b/go/quickstep/src/com/android/quickstep/TaskListLoader.java
index 9f359b4..1234989 100644
--- a/go/quickstep/src/com/android/quickstep/TaskListLoader.java
+++ b/go/quickstep/src/com/android/quickstep/TaskListLoader.java
@@ -24,7 +24,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.List;
 import java.util.function.Consumer;
 
 /**
@@ -38,35 +38,48 @@
 
     private ArrayList<Task> mTaskList = new ArrayList<>();
     private int mTaskListChangeId;
+    private RecentsModel.TaskThumbnailChangeListener listener = (taskId, thumbnailData) -> {
+        Task foundTask = null;
+        for (Task task : mTaskList) {
+            if (task.key.id == taskId) {
+                foundTask = task;
+                break;
+            }
+        }
+        if (foundTask != null) {
+            foundTask.thumbnail = thumbnailData;
+        }
+        return foundTask;
+    };
 
     public TaskListLoader(Context context) {
         mRecentsModel = RecentsModel.INSTANCE.get(context);
+        mRecentsModel.addThumbnailChangeListener(listener);
     }
 
     /**
-     * Returns the current task list as of the last completed load (see
-     * {@link #loadTaskList}). This list of tasks is guaranteed to always have all its task
-     * content loaded.
+     * Returns the current task list as of the last completed load (see {@link #loadTaskList}) as a
+     * read-only list. This list of tasks is not guaranteed to have all content loaded.
      *
-     * @return the current list of tasks w/ all content loaded
+     * @return the current list of tasks
      */
-    public ArrayList<Task> getCurrentTaskList() {
-        return mTaskList;
+    public List<Task> getCurrentTaskList() {
+        return Collections.unmodifiableList(mTaskList);
     }
 
     /**
-     * Fetches the most recent tasks and updates the task list asynchronously. In addition it
-     * loads the content for each task (icon and label). The callback and task list being updated
-     * only occur when all task content is fully loaded and up-to-date.
+     * Fetches the most recent tasks and updates the task list asynchronously. This call does not
+     * provide guarantees the task content (icon, thumbnail, label) are loaded but will fill in
+     * what it has. May run the callback immediately if there have been no changes in the task
+     * list.
      *
-     * @param onTasksLoadedCallback callback for when the tasks are fully loaded. Done on the UI
-     *                              thread
+     * @param onLoadedCallback callback to run when task list is loaded
      */
-    public void loadTaskList(@Nullable Consumer<ArrayList<Task>> onTasksLoadedCallback) {
+    public void loadTaskList(@Nullable Consumer<ArrayList<Task>> onLoadedCallback) {
         if (mRecentsModel.isTaskListValid(mTaskListChangeId)) {
             // Current task list is already up to date. No need to update.
-            if (onTasksLoadedCallback != null) {
-                onTasksLoadedCallback.accept(mTaskList);
+            if (onLoadedCallback != null) {
+                onLoadedCallback.accept(mTaskList);
             }
             return;
         }
@@ -75,44 +88,56 @@
             // Reverse tasks to put most recent at the bottom of the view
             Collections.reverse(tasks);
             // Load task content
-            loadTaskContents(tasks, () -> {
-                mTaskList = tasks;
-                if (onTasksLoadedCallback != null) {
-                    onTasksLoadedCallback.accept(mTaskList);
+            for (Task task : tasks) {
+                int loadedPos = mTaskList.indexOf(task);
+                if (loadedPos == -1) {
+                    continue;
                 }
-            });
+                Task loadedTask = mTaskList.get(loadedPos);
+                task.icon = loadedTask.icon;
+                task.titleDescription = loadedTask.titleDescription;
+                task.thumbnail = loadedTask.thumbnail;
+            }
+            mTaskList = tasks;
+            onLoadedCallback.accept(tasks);
         });
     }
 
     /**
-     * Loads task content for a list of tasks, including the label and the icon. Uses the list of
-     * tasks since the last load as a cache for loaded content.
+     * Load task icon and label asynchronously if it is not already loaded in the task. If the task
+     * already has an icon, this calls the callback immediately.
      *
-     * @param tasksToLoad list of tasks that need to load their content
-     * @param onLoadedCallback runnable to run after all tasks have loaded their content
+     * @param task task to update with icon + label
+     * @param onLoadedCallback callback to run when task has icon and label
      */
-    private void loadTaskContents(ArrayList<Task> tasksToLoad,
-            @Nullable Runnable onLoadedCallback) {
-        AtomicInteger loadRequestsCount = new AtomicInteger(0);
-        for (Task task : tasksToLoad) {
-            int index = mTaskList.indexOf(task);
-            if (index >= 0) {
-                // If we've already loaded the task and have its content then just copy it over.
-                Task loadedTask = mTaskList.get(index);
-                task.titleDescription = loadedTask.titleDescription;
-                task.icon = loadedTask.icon;
-            } else {
-                // Otherwise, load the content in the background.
-                loadRequestsCount.getAndIncrement();
-                mRecentsModel.getIconCache().updateIconInBackground(task, loadedTask -> {
-                    if (loadRequestsCount.decrementAndGet() == 0 && onLoadedCallback != null) {
-                        onLoadedCallback.run();
-                    }
-                });
-            }
-        }
-        if (loadRequestsCount.get() == 0 && onLoadedCallback != null) {
-            onLoadedCallback.run();
-        }
+    public void loadTaskIconAndLabel(Task task, @Nullable Runnable onLoadedCallback) {
+        mRecentsModel.getIconCache().updateIconInBackground(task,
+                loadedTask -> onLoadedCallback.run());
+    }
+
+    /**
+     * Load thumbnail asynchronously if not already loaded in the task. If the task already has a
+     * thumbnail or if the thumbnail is cached, this calls the callback immediately.
+     *
+     * @param task task to update with the thumbnail
+     * @param onLoadedCallback callback to run when task has thumbnail
+     */
+    public void loadTaskThumbnail(Task task, @Nullable Runnable onLoadedCallback) {
+        mRecentsModel.getThumbnailCache().updateThumbnailInBackground(task,
+                thumbnail -> onLoadedCallback.run());
+    }
+
+    /**
+     * Removes the task from the current task list.
+     */
+    void removeTask(Task task) {
+        mTaskList.remove(task);
+    }
+
+    /**
+     * Clears the current task list.
+     */
+    void clearAllTasks() {
+        mTaskList.clear();
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java b/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java
new file mode 100644
index 0000000..98407d8
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static androidx.recyclerview.widget.ItemTouchHelper.RIGHT;
+
+import androidx.recyclerview.widget.ItemTouchHelper;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+/**
+ * Callback for swipe input on {@link TaskHolder} views in the recents view.
+ */
+public final class TaskSwipeCallback extends ItemTouchHelper.SimpleCallback {
+
+    private final TaskActionController mTaskActionController;
+
+    public TaskSwipeCallback(TaskActionController taskActionController) {
+        super(0 /* dragDirs */, RIGHT);
+        mTaskActionController = taskActionController;
+    }
+
+    @Override
+    public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder,
+            ViewHolder target) {
+        return false;
+    }
+
+    @Override
+    public void onSwiped(ViewHolder viewHolder, int direction) {
+        if (direction == RIGHT) {
+            mTaskActionController.removeTask((TaskHolder) viewHolder);
+        }
+    }
+}
diff --git a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 89a8454..c579c8a 100644
--- a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -30,7 +30,6 @@
 
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
 
 /**
  * Service connected by system-UI for handling touch interaction.
@@ -38,8 +37,6 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class TouchInteractionService extends Service {
 
-    public static final int EDGE_NAV_BAR = 1 << 8;
-
     private static final String TAG = "TouchInteractionService";
 
     private final IBinder mMyBinder = new IOverviewProxy.Stub() {
@@ -83,6 +80,15 @@
             // TODO handle assistant
         }
 
+        @Override
+        public void onAssistantVisibilityChanged(float visibility) {
+            // TODO handle assistant
+        }
+
+        public void onBackAction(boolean completed, int downX, int downY, boolean isButton,
+                boolean gestureSwipeLeft) {
+        }
+
         /** Deprecated methods **/
         public void onQuickStep(MotionEvent motionEvent) { }
 
diff --git a/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java b/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java
index d748e89..c0ebcb5 100644
--- a/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java
+++ b/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.util.AttributeSet;
 
+import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.RecentsActivity;
 
@@ -27,5 +28,7 @@
 public final class GoRecentsActivityRootView extends BaseDragLayer<RecentsActivity> {
     public GoRecentsActivityRootView(Context context, AttributeSet attrs) {
         super(context, attrs, 1 /* alphaChannelCount */);
+        // Go leaves touch control to the view itself.
+        mControllers = new TouchController[0];
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index afb0540..5bb4c5a 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -17,18 +17,33 @@
 
 import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
+import android.view.View;
 import android.view.ViewDebug;
 import android.widget.FrameLayout;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.ItemTouchHelper;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
 
 import com.android.launcher3.R;
+import com.android.quickstep.RecentsToActivityHelper;
+import com.android.quickstep.TaskActionController;
 import com.android.quickstep.TaskAdapter;
+import com.android.quickstep.TaskHolder;
 import com.android.quickstep.TaskListLoader;
+import com.android.quickstep.TaskSwipeCallback;
 
 /**
  * Root view for the icon recents view. Acts as the main interface to the rest of the Launcher code
@@ -36,20 +51,6 @@
  */
 public final class IconRecentsView extends FrameLayout {
 
-    public static final FloatProperty<IconRecentsView> TRANSLATION_Y_FACTOR =
-            new FloatProperty<IconRecentsView>("translationYFactor") {
-
-                @Override
-                public void setValue(IconRecentsView view, float v) {
-                    view.setTranslationYFactor(v);
-                }
-
-                @Override
-                public Float get(IconRecentsView view) {
-                    return view.mTranslationYFactor;
-                }
-            };
-
     public static final FloatProperty<IconRecentsView> CONTENT_ALPHA =
             new FloatProperty<IconRecentsView>("contentAlpha") {
                 @Override
@@ -67,6 +68,11 @@
                     return ALPHA.get(view);
                 }
             };
+    private static final long CROSSFADE_DURATION = 300;
+    private static final long ITEM_ANIMATE_OUT_DURATION = 150;
+    private static final long ITEM_ANIMATE_OUT_DELAY_BETWEEN = 40;
+    private static final float ITEM_ANIMATE_OUT_TRANSLATION_X_RATIO = .25f;
+    private static final long CLEAR_ALL_FADE_DELAY = 120;
 
     /**
      * A ratio representing the view's relative placement within its padded space. For example, 0
@@ -75,34 +81,80 @@
     @ViewDebug.ExportedProperty(category = "launcher")
 
     private final Context mContext;
+    private final TaskListLoader mTaskLoader;
+    private final TaskAdapter mTaskAdapter;
+    private final TaskActionController mTaskActionController;
 
-    private float mTranslationYFactor;
-    private TaskAdapter mTaskAdapter;
+    private RecentsToActivityHelper mActivityHelper;
     private RecyclerView mTaskRecyclerView;
-    private TaskListLoader mTaskLoader;
+    private View mEmptyView;
+    private View mContentView;
+    private View mClearAllView;
+    private boolean mTransitionedFromApp;
 
     public IconRecentsView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
+        mTaskLoader = new TaskListLoader(mContext);
+        mTaskAdapter = new TaskAdapter(mTaskLoader);
+        mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter);
+        mTaskAdapter.setActionController(mTaskActionController);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mTaskLoader = new TaskListLoader(mContext);
-        mTaskAdapter = new TaskAdapter(mTaskLoader);
-        mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view);
-        mTaskRecyclerView.setAdapter(mTaskAdapter);
-        mTaskRecyclerView.setLayoutManager(
-                new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */));
+        if (mTaskRecyclerView == null) {
+            mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view);
+            mTaskRecyclerView.setAdapter(mTaskAdapter);
+            mTaskRecyclerView.setLayoutManager(
+                    new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */));
+            ItemTouchHelper helper = new ItemTouchHelper(
+                    new TaskSwipeCallback(mTaskActionController));
+            helper.attachToRecyclerView(mTaskRecyclerView);
+
+            mEmptyView = findViewById(R.id.recent_task_empty_view);
+            mContentView = findViewById(R.id.recent_task_content_view);
+            mTaskAdapter.registerAdapterDataObserver(new AdapterDataObserver() {
+                @Override
+                public void onChanged() {
+                    updateContentViewVisibility();
+                }
+
+                @Override
+                public void onItemRangeRemoved(int positionStart, int itemCount) {
+                    updateContentViewVisibility();
+                }
+            });
+            mClearAllView = findViewById(R.id.clear_all_button);
+            mClearAllView.setOnClickListener(v -> animateClearAllTasks());
+        }
+    }
+
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        TaskItemView[] itemViews = getTaskViews();
+        for (TaskItemView itemView : itemViews) {
+            itemView.setEnabled(enabled);
+        }
+        mClearAllView.setEnabled(enabled);
+    }
+
+    /**
+     * Set activity helper for the view to callback to.
+     *
+     * @param helper the activity helper
+     */
+    public void setRecentsToActivityHelper(@NonNull RecentsToActivityHelper helper) {
+        mActivityHelper = helper;
     }
 
     /**
      * Logic for when we know we are going to overview/recents and will be putting up the recents
      * view. This should be used to prepare recents (e.g. load any task data, etc.) before it
      * becomes visible.
-     *
-     * TODO: Hook this up for fallback recents activity as well
      */
     public void onBeginTransitionToOverview() {
         // Load any task changes
@@ -113,12 +165,161 @@
         });
     }
 
-    public void setTranslationYFactor(float translationFactor) {
-        mTranslationYFactor = translationFactor;
-        setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
+    /**
+     * Set whether we transitioned to recents from the most recent app.
+     *
+     * @param transitionedFromApp true if transitioned from the most recent app, false otherwise
+     */
+    public void setTransitionedFromApp(boolean transitionedFromApp) {
+        mTransitionedFromApp = transitionedFromApp;
     }
 
-    private float computeTranslationYForFactor(float translationYFactor) {
-        return translationYFactor * (getPaddingBottom() - getPaddingTop());
+    /**
+     * Handles input from the overview button. Launch the most recent task unless we just came from
+     * the app. In that case, we launch the next most recent.
+     */
+    public void handleOverviewCommand() {
+        int childCount = mTaskRecyclerView.getChildCount();
+        if (childCount == 0) {
+            // Do nothing
+            return;
+        }
+        TaskHolder taskToLaunch;
+        if (mTransitionedFromApp && childCount > 1) {
+            // Launch the next most recent app
+            TaskItemView itemView = (TaskItemView) mTaskRecyclerView.getChildAt(1);
+            taskToLaunch = (TaskHolder) mTaskRecyclerView.getChildViewHolder(itemView);
+        } else {
+            // Launch the most recent app
+            TaskItemView itemView = (TaskItemView) mTaskRecyclerView.getChildAt(0);
+            taskToLaunch = (TaskHolder) mTaskRecyclerView.getChildViewHolder(itemView);
+        }
+        mTaskActionController.launchTask(taskToLaunch);
+    }
+
+    /**
+     * Get the thumbnail view associated with a task for the purposes of animation.
+     *
+     * @param taskId task id of thumbnail view to get
+     * @return the thumbnail view for the task if attached, null otherwise
+     */
+    public @Nullable View getThumbnailViewForTask(int taskId) {
+        TaskItemView view = mTaskAdapter.getTaskItemView(taskId);
+        if (view == null) {
+            return null;
+        }
+        return view.getThumbnailView();
+    }
+
+    /**
+     * Clear all tasks and animate out.
+     */
+    private void animateClearAllTasks() {
+        setEnabled(false);
+        TaskItemView[] itemViews = getTaskViews();
+
+        AnimatorSet clearAnim = new AnimatorSet();
+        long currentDelay = 0;
+
+        // Animate each item view to the right and fade out.
+        for (TaskItemView itemView : itemViews) {
+            PropertyValuesHolder transXproperty = PropertyValuesHolder.ofFloat(TRANSLATION_X,
+                    0, itemView.getWidth() * ITEM_ANIMATE_OUT_TRANSLATION_X_RATIO);
+            PropertyValuesHolder alphaProperty = PropertyValuesHolder.ofFloat(ALPHA, 1.0f, 0f);
+            ObjectAnimator itemAnim = ObjectAnimator.ofPropertyValuesHolder(itemView,
+                    transXproperty, alphaProperty);
+            itemAnim.setDuration(ITEM_ANIMATE_OUT_DURATION);
+            itemAnim.setStartDelay(currentDelay);
+
+            clearAnim.play(itemAnim);
+            currentDelay += ITEM_ANIMATE_OUT_DELAY_BETWEEN;
+        }
+
+        // Animate view fading and leave recents when faded enough.
+        ValueAnimator contentAlpha = ValueAnimator.ofFloat(1.0f, 0f)
+                .setDuration(CROSSFADE_DURATION);
+        contentAlpha.setStartDelay(CLEAR_ALL_FADE_DELAY);
+        contentAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            private boolean mLeftRecents = false;
+
+            @Override
+            public void onAnimationUpdate(ValueAnimator valueAnimator) {
+                mContentView.setAlpha((float) valueAnimator.getAnimatedValue());
+                // Leave recents while fading out.
+                if ((float) valueAnimator.getAnimatedValue() < .5f && !mLeftRecents) {
+                    mActivityHelper.leaveRecents();
+                    mLeftRecents = true;
+                }
+            }
+        });
+
+        clearAnim.play(contentAlpha);
+        clearAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                for (TaskItemView itemView : itemViews) {
+                    itemView.setTranslationX(0);
+                    itemView.setAlpha(1.0f);
+                }
+                setEnabled(true);
+                mContentView.setVisibility(GONE);
+                mTaskActionController.clearAllTasks();
+            }
+        });
+        clearAnim.start();
+    }
+
+    /**
+     * Get attached task item views ordered by most recent.
+     *
+     * @return array of attached task item views
+     */
+    private TaskItemView[] getTaskViews() {
+        int taskCount = mTaskRecyclerView.getChildCount();
+        TaskItemView[] itemViews = new TaskItemView[taskCount];
+        for (int i = 0; i < taskCount; i ++) {
+            itemViews[i] = (TaskItemView) mTaskRecyclerView.getChildAt(i);
+        }
+        return itemViews;
+    }
+
+    /**
+     * Update the content view so that the appropriate view is shown based off the current list
+     * of tasks.
+     */
+    private void updateContentViewVisibility() {
+        int taskListSize = mTaskLoader.getCurrentTaskList().size();
+        if (mEmptyView.getVisibility() != VISIBLE && taskListSize == 0) {
+            crossfadeViews(mEmptyView, mContentView);
+            mActivityHelper.leaveRecents();
+        }
+        if (mContentView.getVisibility() != VISIBLE && taskListSize > 0) {
+            crossfadeViews(mContentView, mEmptyView);
+        }
+    }
+
+    /**
+     * Animate views so that one view fades in while the other fades out.
+     *
+     * @param fadeInView view that should fade in
+     * @param fadeOutView view that should fade out
+     */
+    private void crossfadeViews(View fadeInView, View fadeOutView) {
+        fadeInView.setVisibility(VISIBLE);
+        fadeInView.setAlpha(0f);
+        fadeInView.animate()
+                .alpha(1f)
+                .setDuration(CROSSFADE_DURATION)
+                .setListener(null);
+
+        fadeOutView.animate()
+                .alpha(0f)
+                .setDuration(CROSSFADE_DURATION)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        fadeOutView.setVisibility(GONE);
+                    }
+                });
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
new file mode 100644
index 0000000..d831b20
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+
+/**
+ * View representing an individual task item with the icon + thumbnail adjacent to the task label.
+ */
+public final class TaskItemView extends LinearLayout {
+
+    private static final String DEFAULT_LABEL = "...";
+    private final Drawable mDefaultIcon;
+    private TextView mLabelView;
+    private ImageView mIconView;
+    private ImageView mThumbnailView;
+
+    public TaskItemView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mDefaultIcon = context.getResources().getDrawable(
+                android.R.drawable.sym_def_app_icon, context.getTheme());
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mLabelView = findViewById(R.id.task_label);
+        mThumbnailView = findViewById(R.id.task_thumbnail);
+        mIconView = findViewById(R.id.task_icon);
+    }
+
+    /**
+     * Resets task item view to default values.
+     */
+    public void resetTaskItemView() {
+        setLabel(DEFAULT_LABEL);
+        setIcon(null);
+        setThumbnail(null);
+    }
+
+    /**
+     * Set the label for the task item. Sets to a default label if null.
+     *
+     * @param label task label
+     */
+    public void setLabel(@Nullable String label) {
+        if (label == null) {
+            mLabelView.setText(DEFAULT_LABEL);
+            return;
+        }
+        mLabelView.setText(label);
+    }
+
+    /**
+     * Set the icon for the task item. Sets to a default icon if null.
+     *
+     * @param icon task icon
+     */
+    public void setIcon(@Nullable Drawable icon) {
+        // TODO: Scale the icon up based off the padding on the side
+        // The icon proper is actually smaller than the drawable and has "padding" on the side for
+        // the purpose of drawing the shadow, allowing the icon to pop up, so we need to scale the
+        // view if we want the icon to be flush with the bottom of the thumbnail.
+        if (icon == null) {
+            mIconView.setImageDrawable(mDefaultIcon);
+            return;
+        }
+        mIconView.setImageDrawable(icon);
+    }
+
+    /**
+     * Set the task thumbnail for the task. Sets to a default thumbnail if null.
+     *
+     * @param thumbnail task thumbnail for the task
+     */
+    public void setThumbnail(@Nullable Bitmap thumbnail) {
+        if (thumbnail == null) {
+            mThumbnailView.setImageBitmap(null);
+            mThumbnailView.setBackgroundColor(Color.GRAY);
+            return;
+        }
+        mThumbnailView.setBackgroundColor(Color.TRANSPARENT);
+        mThumbnailView.setImageBitmap(thumbnail);
+    }
+
+    public View getThumbnailView() {
+        return mThumbnailView;
+    }
+}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index 96b0a9e..ab4b64c 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -61,6 +61,7 @@
 
         mCanvas = new Canvas();
         mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
+        clear();
     }
 
     protected void clear() {
@@ -114,11 +115,6 @@
     }
 
     public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
-            boolean shrinkNonAdaptiveIcons, boolean isInstantApp) {
-        return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, isInstantApp, null);
-    }
-
-    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
             int iconAppTargetSdk) {
         return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);
     }
diff --git a/libs/README.txt b/libs/README.txt
deleted file mode 100644
index 9109592..0000000
--- a/libs/README.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-These jar are compiled in the frameworks/base of the platform tree.
-
-launcher_protos.jar is defined as launcherprotosnano in the following file:
-frameworks/base/core/protos/android/stats/launcher/Android.bp
-
-plugin_core.jar is defined as PluginCoreLib in the following file:
-frameworks/base/packages/SystemUI/plugin/Android.bp
-
diff --git a/libs/launcher_protos.jar b/libs/launcher_protos.jar
deleted file mode 100644
index c043936..0000000
--- a/libs/launcher_protos.jar
+++ /dev/null
Binary files differ
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 4129ae8..4974dcb 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -106,7 +106,7 @@
   RESIZE_HANDLE = 8;
   VERTICAL_SCROLL = 9;
   HOME_INTENT = 10; // Deprecated, use enum Command instead
-  BACK_BUTTON = 11; // Deprecated, use enum Command instead
+  BACK_BUTTON = 11;
   QUICK_SCRUB_BUTTON = 12;
   CLEAR_ALL_BUTTON = 13;
   CANCEL_TARGET = 14;
@@ -114,6 +114,7 @@
   SPLIT_SCREEN_TARGET = 16;
   REMOTE_ACTION_SHORTCUT = 17;
   APP_USAGE_SETTINGS = 18;
+  BACK_GESTURE = 19;
 }
 
 enum TipType {
@@ -122,6 +123,7 @@
   SWIPE_UP_TEXT = 2;
   QUICK_SCRUB_TEXT = 3;
   PREDICTION_TEXT = 4;
+  DWB_TOAST = 5;
 }
 
 // Used to define the action component of the LauncherEvent.
@@ -141,6 +143,7 @@
     SWIPE = 3;
     FLING = 4;
     PINCH = 5;
+    SWIPE_NOOP = 6;
   }
 
   enum Direction {
@@ -149,6 +152,8 @@
     DOWN = 2;
     LEFT = 3;
     RIGHT = 4;
+    UPRIGHT = 5;
+    UPLEFT = 6;
   }
   enum Command {
     HOME_INTENT = 0;
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 62d0500..97fc284 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -73,6 +73,14 @@
             </intent-filter>
         </provider>
 
+        <provider
+            android:name="com.android.quickstep.TestInformationProvider"
+            android:authorities="${packageName}.TestInfo"
+            android:readPermission="android.permission.WRITE_SECURE_SETTINGS"
+            android:writePermission="android.permission.WRITE_SECURE_SETTINGS"
+            android:exported="true">
+        </provider>
+
         <service
             android:name="com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompatVL$ColorExtractionService"
             tools:node="remove" />
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
deleted file mode 100644
index 8c58e3e..0000000
--- a/quickstep/libs/sysui_shared.jar
+++ /dev/null
Binary files differ
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/FlingAndHoldTouchController.java
deleted file mode 100644
index a41362f..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/FlingAndHoldTouchController.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
-
-import android.animation.ValueAnimator;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.quickstep.util.MotionPauseDetector;
-import com.android.quickstep.views.RecentsView;
-
-/**
- * Touch controller which handles swipe and hold to go to Overview
- */
-public class FlingAndHoldTouchController extends PortraitStatesTouchController {
-
-    private final MotionPauseDetector mMotionPauseDetector;
-
-    public FlingAndHoldTouchController(Launcher l) {
-        super(l, false /* allowDragToOverview */);
-        mMotionPauseDetector = new MotionPauseDetector(l);
-    }
-
-    @Override
-    public void onDragStart(boolean start) {
-        mMotionPauseDetector.clear();
-
-        super.onDragStart(start);
-
-        if (mStartState == NORMAL) {
-            mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
-                RecentsView recentsView = mLauncher.getOverviewPanel();
-                recentsView.setOverviewStateEnabled(isPaused);
-                maybeUpdateAtomicAnim(NORMAL, OVERVIEW, isPaused ? 1 : 0);
-            });
-        }
-    }
-
-    @Override
-    public boolean onDrag(float displacement) {
-        mMotionPauseDetector.addPosition(displacement, 0);
-        return super.onDrag(displacement);
-    }
-
-    @Override
-    public void onDragEnd(float velocity, boolean fling) {
-        if (mMotionPauseDetector.isPaused() && mStartState == NORMAL) {
-            float range = getShiftRange();
-            long maxAccuracy = (long) (2 * range);
-
-            // Let the state manager know that the animation didn't go to the target state,
-            // but don't cancel ourselves (we already clean up when the animation completes).
-            Runnable onCancel = mCurrentAnimation.getOnCancelRunnable();
-            mCurrentAnimation.setOnCancelRunnable(null);
-            mCurrentAnimation.dispatchOnCancel();
-            mCurrentAnimation = mLauncher.getStateManager()
-                    .createAnimationToNewWorkspace(OVERVIEW, new AnimatorSetBuilder(), maxAccuracy,
-                            onCancel, NON_ATOMIC_COMPONENT);
-
-            final int logAction = fling ? Touch.FLING : Touch.SWIPE;
-            mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(OVERVIEW, logAction));
-
-
-            ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
-            maybeUpdateAtomicAnim(NORMAL, OVERVIEW, 1f);
-            mCurrentAnimation.dispatchOnStartWithVelocity(1, velocity);
-
-            // TODO: Find a better duration
-            anim.setDuration(100);
-            anim.start();
-            settleAtomicAnimation(1f, anim.getDuration());
-        } else {
-            super.onDragEnd(velocity, fling);
-        }
-        mMotionPauseDetector.clear();
-    }
-}
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 81ff0c7..f507d0f 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
@@ -17,11 +17,9 @@
 package com.android.launcher3.uioverrides;
 
 import static android.view.View.VISIBLE;
-
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.config.FeatureFlags.SWIPE_HOME;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
@@ -30,10 +28,19 @@
 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.NavBarToHomeTouchController;
+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;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -52,15 +59,13 @@
     private static final float RECENTS_PREPARE_SCALE = 1.33f;
 
     public static TouchController[] createTouchControllers(Launcher launcher) {
-        boolean swipeUpEnabled = OverviewInteractionState.INSTANCE.get(launcher)
-                .isSwipeUpGestureEnabled();
-        boolean swipeUpToHome = swipeUpEnabled && SWIPE_HOME.get();
-
+        Mode mode = SysUINavigationMode.INSTANCE.get(launcher).getMode();
 
         ArrayList<TouchController> list = new ArrayList<>();
         list.add(launcher.getDragController());
-
-        if (swipeUpToHome) {
+        if (mode == Mode.NO_BUTTON) {
+            list.add(new QuickSwitchTouchController(launcher));
+            list.add(new NavBarToHomeTouchController(launcher));
             list.add(new FlingAndHoldTouchController(launcher));
         } else {
             if (launcher.getDeviceProfile().isVerticalBarLayout()) {
@@ -68,7 +73,10 @@
                 list.add(new LandscapeEdgeSwipeController(launcher));
             } else {
                 list.add(new PortraitStatesTouchController(launcher,
-                        swipeUpEnabled /* allowDragToOverview */));
+                        mode.hasGestures /* allowDragToOverview */));
+                if (mode.hasGestures) {
+                    list.add(new QuickSwitchTouchController(launcher));
+                }
             }
         }
 
@@ -98,6 +106,10 @@
      * @param launcher the launcher activity
      */
     public static void prepareToShowOverview(Launcher launcher) {
+        if (FeatureFlags.SWIPE_HOME.get()) {
+            // Overview lives on the side, so doesn't scale in from above.
+            return;
+        }
         RecentsView overview = launcher.getOverviewPanel();
         if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
             SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 0b3bd6c..0d5574f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.uioverrides;
 
-import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 
 import android.animation.ValueAnimator;
@@ -23,8 +22,6 @@
 import android.os.Build;
 import android.util.FloatProperty;
 
-import androidx.annotation.NonNull;
-
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
@@ -32,6 +29,8 @@
 import com.android.quickstep.views.LauncherRecentsView;
 import com.android.quickstep.views.RecentsView;
 
+import androidx.annotation.NonNull;
+
 /**
  * State handler for handling UI changes for {@link LauncherRecentsView}. In addition to managing
  * the basic view properties, this class also manages changes in the task visuals.
@@ -80,11 +79,6 @@
     }
 
     @Override
-    FloatProperty<LauncherRecentsView> getTranslationYFactorProperty() {
-        return TRANSLATION_Y_FACTOR;
-    }
-
-    @Override
     FloatProperty<RecentsView> getContentAlphaProperty() {
         return CONTENT_ALPHA;
     }
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 84%
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 963f1fa..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,13 +13,14 @@
  * 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 android.os.RemoteException;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
@@ -51,7 +52,7 @@
     }
 
     @Override
-    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+    public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
         // Initialize the recents view scale to what it would be when starting swipe up
         RecentsView recentsView = launcher.getOverviewPanel();
         recentsView.getTaskSize(sTempRect);
@@ -70,6 +71,15 @@
             }
         }
         float scale = (float) appWidth / sTempRect.width();
-        return new float[] { scale, 0f };
+        return new ScaleAndTranslation(scale, 0f, 0f);
+    }
+
+    @Override
+    public int getVisibleElements(Launcher launcher) {
+        if (FeatureFlags.SWIPE_HOME.get()) {
+            return super.getVisibleElements(launcher);
+        }
+        // Hide shelf content (e.g. QSB) because we fade it in when swiping up.
+        return ALL_APPS_HEADER_EXTRA;
     }
 }
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 76%
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 3602508..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,13 @@
  * 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;
 
 import android.graphics.Rect;
@@ -25,13 +27,14 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.quickstep.RecentsModel;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
@@ -50,11 +53,15 @@
     }
 
     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
-    public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
+    public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
         RecentsView recentsView = launcher.getOverviewPanel();
         Workspace workspace = launcher.getWorkspace();
         View workspacePage = workspace.getPageAt(workspace.getCurrentPage());
@@ -63,12 +70,12 @@
         recentsView.getTaskSize(sTempRect);
         float scale = (float) sTempRect.width() / workspacePageWidth;
         float parallaxFactor = 0.5f;
-        return new float[]{scale, 0, -getDefaultSwipeHeight(launcher) * parallaxFactor};
+        return new ScaleAndTranslation(scale, 0, -getDefaultSwipeHeight(launcher) * parallaxFactor);
     }
 
     @Override
-    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
-        return new float[] {1f, 0f};
+    public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
+        return new ScaleAndTranslation(1f, 0f, 0f);
     }
 
     @Override
@@ -90,6 +97,7 @@
         DiscoveryBounce.showForOverviewIfNeeded(launcher);
     }
 
+    @Override
     public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
         return new PageAlphaProvider(DEACCEL_2) {
             @Override
@@ -131,7 +139,10 @@
     }
 
     public static float getDefaultSwipeHeight(Launcher launcher) {
-        DeviceProfile dp = launcher.getDeviceProfile();
+        return getDefaultSwipeHeight(launcher.getDeviceProfile());
+    }
+
+    public static float getDefaultSwipeHeight(DeviceProfile dp) {
         return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
     }
 
@@ -139,9 +150,23 @@
     public void onBackPressed(Launcher launcher) {
         TaskView taskView = launcher.<RecentsView>getOverviewPanel().getRunningTaskView();
         if (taskView != null) {
+            launcher.getUserEventDispatcher().logActionCommand(Action.Command.BACK,
+                    newContainerTarget(ContainerType.OVERVIEW));
             taskView.launchTask(true);
         } else {
             super.onBackPressed(launcher);
         }
     }
+
+    public static OverviewState newBackgroundState(int id) {
+        return new BackgroundAppState(id);
+    }
+
+    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/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
new file mode 100644
index 0000000..6dd5e21
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -0,0 +1,137 @@
+/*
+ * 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.OVERVIEW;
+import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.quickstep.util.MotionPauseDetector;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Touch controller which handles swipe and hold to go to Overview
+ */
+public class FlingAndHoldTouchController extends PortraitStatesTouchController {
+
+    private static final long PEEK_ANIM_DURATION = 100;
+
+    private final MotionPauseDetector mMotionPauseDetector;
+
+    private AnimatorSet mPeekAnim;
+
+    public FlingAndHoldTouchController(Launcher l) {
+        super(l, false /* allowDragToOverview */);
+        mMotionPauseDetector = new MotionPauseDetector(l);
+    }
+
+    @Override
+    protected long getAtomicDuration() {
+        return 300;
+    }
+
+    @Override
+    public void onDragStart(boolean start) {
+        mMotionPauseDetector.clear();
+
+        super.onDragStart(start);
+
+        if (handlingOverviewAnim()) {
+            mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
+                RecentsView recentsView = mLauncher.getOverviewPanel();
+                recentsView.setOverviewStateEnabled(isPaused);
+                if (mPeekAnim != null) {
+                    mPeekAnim.cancel();
+                }
+                LauncherState fromState = isPaused ? NORMAL : OVERVIEW_PEEK;
+                LauncherState toState = isPaused ? OVERVIEW_PEEK : NORMAL;
+                mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(fromState, toState,
+                        new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT,
+                        PEEK_ANIM_DURATION);
+                mPeekAnim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mPeekAnim = null;
+                    }
+                });
+                mPeekAnim.start();
+                recentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
+                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+            });
+        }
+    }
+
+    /**
+     * @return Whether we are handling the overview animation, rather than
+     * having it as part of the existing animation to the target state.
+     */
+    private boolean handlingOverviewAnim() {
+        return mStartState == NORMAL;
+    }
+
+    @Override
+    public boolean onDrag(float displacement, MotionEvent event) {
+        mMotionPauseDetector.addPosition(displacement, 0, event.getEventTime());
+        return super.onDrag(displacement, event);
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        if (mMotionPauseDetector.isPaused() && handlingOverviewAnim()) {
+            if (mPeekAnim != null) {
+                mPeekAnim.cancel();
+            }
+
+            AnimatorSetBuilder builder = new AnimatorSetBuilder();
+            builder.setInterpolator(AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
+            AnimatorSet overviewAnim = mLauncher.getStateManager().createAtomicAnimation(
+                    NORMAL, OVERVIEW, builder, ANIM_ALL, ATOMIC_DURATION);
+            overviewAnim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
+                }
+            });
+            overviewAnim.start();
+        } else {
+            super.onDragEnd(velocity, fling);
+        }
+        mMotionPauseDetector.clear();
+    }
+
+    @Override
+    protected void updateAnimatorBuilderOnReinit(AnimatorSetBuilder builder) {
+        if (handlingOverviewAnim()) {
+            // We don't want the state transition to all apps to animate overview,
+            // as that will cause a jump after our atomic animation.
+            builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
new file mode 100644
index 0000000..673beff
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -0,0 +1,141 @@
+/*
+ * 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.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.Interpolators;
+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.Touch;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Handles swiping up on the nav bar to go home from overview or all apps.
+ */
+public class NavBarToHomeTouchController extends AbstractStateChangeTouchController {
+
+    private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3;
+
+    public NavBarToHomeTouchController(Launcher launcher) {
+        super(launcher, SwipeDetector.VERTICAL);
+    }
+
+    @Override
+    protected boolean canInterceptTouch(MotionEvent ev) {
+        boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0;
+        return cameFromNavBar && (mLauncher.isInState(OVERVIEW) || mLauncher.isInState(ALL_APPS));
+    }
+
+    @Override
+    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        return isDragTowardPositive ? NORMAL : fromState;
+    }
+
+    @Override
+    protected float initCurrentAnimation(int animComponents) {
+        long accuracy = (long) (getShiftRange() * 2);
+        final AnimatorSet anim;
+        if (mFromState == OVERVIEW) {
+            anim = new AnimatorSet();
+            RecentsView recentsView = mLauncher.getOverviewPanel();
+            float pullbackDistance = recentsView.getPaddingStart() / 2;
+            if (!recentsView.isRtl()) {
+                pullbackDistance = -pullbackDistance;
+            }
+            anim.play(ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_X, pullbackDistance));
+            anim.setInterpolator(PULLBACK_INTERPOLATOR);
+        } else { // if (mFromState == ALL_APPS)
+            AnimatorSetBuilder builder = new AnimatorSetBuilder();
+            AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
+            final float pullbackDistance = mLauncher.getDeviceProfile().allAppsIconSizePx / 2;
+            Animator allAppsProgress = ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS,
+                    -pullbackDistance / allAppsController.getShiftRange());
+            allAppsProgress.setInterpolator(PULLBACK_INTERPOLATOR);
+            builder.play(allAppsProgress);
+            // Slightly fade out all apps content to further distinguish from scrolling.
+            builder.setInterpolator(AnimatorSetBuilder.ANIM_ALL_APPS_FADE, Interpolators
+                    .mapToProgress(PULLBACK_INTERPOLATOR, 0, 0.5f));
+            AnimationConfig config = new AnimationConfig();
+            config.duration = accuracy;
+            allAppsController.setAlphas(mToState.getVisibleElements(mLauncher), config, builder);
+            anim = builder.build();
+        }
+        anim.setDuration(accuracy);
+        mCurrentAnimation = AnimatorPlaybackController.wrap(anim, accuracy, this::clearState);
+        return -1 / getShiftRange();
+    }
+
+    @Override
+    public void onDragStart(boolean start) {
+        super.onDragStart(start);
+        mStartContainerType = LauncherLogProto.ContainerType.NAVBAR;
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        final int logAction = fling ? Touch.FLING : Touch.SWIPE;
+        float interpolatedProgress = PULLBACK_INTERPOLATOR.getInterpolation(
+                mCurrentAnimation.getProgressFraction());
+        if (interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS || velocity < 0 && fling) {
+            mLauncher.getStateManager().goToState(mToState, true,
+                    () -> onSwipeInteractionCompleted(mToState, logAction));
+        } else {
+            // Quickly return to the state we came from (we didn't move far).
+            AnimatorPlaybackController anim = mLauncher.getStateManager()
+                    .createAnimationToNewWorkspace(mFromState, 80);
+            anim.setEndAction(() -> onSwipeInteractionCompleted(mFromState, logAction));
+            anim.start();
+        }
+        mCurrentAnimation.dispatchOnCancel();
+    }
+
+    @Override
+    protected int getDirectionForLog() {
+        return LauncherLogProto.Action.Direction.UP;
+    }
+
+    @Override
+    protected boolean goingBetweenNormalAndOverview(LauncherState fromState,
+            LauncherState toState) {
+        // We don't want to create an atomic animation to/from overview.
+        return false;
+    }
+
+    @Override
+    protected int getLogContainerTypeForNormalState() {
+        return LauncherLogProto.ContainerType.NAVBAR;
+    }
+}
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 97%
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..62d954b 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;
@@ -38,7 +38,7 @@
 import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
@@ -123,8 +123,7 @@
                     if (mRecentsView.isTaskViewVisible(view) && mActivity.getDragLayer()
                             .isEventOverView(view, ev)) {
                         mTaskBeingDragged = view;
-                        if (!OverviewInteractionState.INSTANCE.get(mActivity)
-                                .isSwipeUpGestureEnabled()) {
+                        if (!SysUINavigationMode.INSTANCE.get(mActivity).getMode().hasGestures) {
                             // Don't allow swipe down to open if we don't support swipe up
                             // to enter overview.
                             directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
index 109a4c5..7c0791e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
@@ -21,60 +21,80 @@
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.SWIPE;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.SWIPE_NOOP;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction.UPLEFT;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction.UPRIGHT;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.NAVBAR;
 
+import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.PointF;
-import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
-import android.view.Display;
 import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.ViewConfiguration;
-import android.view.WindowManager;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.quickstep.util.MotionPauseDetector;
 import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.shared.system.NavigationBarCompat;
-import com.android.systemui.shared.system.WindowManagerWrapper;
 import com.android.launcher3.R;
+import com.android.systemui.shared.system.NavigationBarCompat;
 
 /**
  * Touch consumer for handling events to launch assistant from launcher
  */
 public class AssistantTouchConsumer implements InputConsumer {
     private static final String TAG = "AssistantTouchConsumer";
+    private static final long RETRACT_ANIMATION_DURATION_MS = 300;
+
+    /* The assistant touch consume competes with quick switch InputConsumer gesture. The delegate
+     * can be chosen to run if the angle passing the slop is lower than the threshold angle. When
+     * this occurs, the state changes to {@link #STATE_DELEGATE_ACTIVE} where the next incoming
+     * motion events are handled by the delegate instead of the assistant touch consumer. If the
+     * angle is higher than the threshold, the state will change to {@link #STATE_ASSISTANT_ACTIVE}.
+     */
+    private static final int STATE_INACTIVE = 0;
+    private static final int STATE_ASSISTANT_ACTIVE = 1;
+    private static final int STATE_DELEGATE_ACTIVE = 2;
 
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
+    private final PointF mStartDragPos = new PointF();
+
     private int mActivePointerId = -1;
-
-    private final int mDisplayRotation;
-    private final Rect mStableInsets = new Rect();
-
-    private final float mDragSlop;
-    private final float mTouchSlop;
-    private final float mThreshold;
-
-    private float mStartDisplacement;
-    private boolean mPassedDragSlop;
-    private boolean mPassedTouchSlop;
-    private long mPassedTouchSlopTime;
+    private boolean mPassedSlop;
     private boolean mLaunchedAssistant;
+    private float mDistance;
+    private float mTimeFraction;
+    private long mDragTime;
     private float mLastProgress;
+    private int mState;
+    private int mDirection;
 
+    private final float mDistThreshold;
+    private final long mTimeThreshold;
+    private final int mAngleThreshold;
+    private final float mSlop;
+    private final MotionPauseDetector mMotionPauseDetector;
     private final ISystemUiProxy mSysUiProxy;
+    private final InputConsumer mConsumerDelegate;
+    private final Context mContext;
 
-    public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy) {
+    public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy,
+            InputConsumer delegate) {
+        final Resources res = context.getResources();
+        mContext = context;
         mSysUiProxy = systemUiProxy;
-
-        mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
-        mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx();
-        mThreshold = context.getResources().getDimension(R.dimen.gestures_assistant_threshold);
-
-        Display display = context.getSystemService(WindowManager.class).getDefaultDisplay();
-        mDisplayRotation = display.getRotation();
-        WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
+        mConsumerDelegate = delegate;
+        mMotionPauseDetector = new MotionPauseDetector(context);
+        mDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
+        mTimeThreshold = res.getInteger(R.integer.assistant_gesture_min_time_threshold);
+        mAngleThreshold = res.getInteger(R.integer.assistant_gesture_corner_deg_threshold);
+        mSlop = NavigationBarCompat.getQuickScrubTouchSlopPx();
+        mState = STATE_INACTIVE;
     }
 
     @Override
@@ -83,14 +103,28 @@
     }
 
     @Override
+    public boolean isActive() {
+        return mState != STATE_INACTIVE;
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent ev) {
         // TODO add logging
+
         switch (ev.getActionMasked()) {
             case ACTION_DOWN: {
                 mActivePointerId = ev.getPointerId(0);
                 mDownPos.set(ev.getX(), ev.getY());
                 mLastPos.set(mDownPos);
-                mLastProgress = -1;
+                mTimeFraction = 0;
+
+                // Detect when the gesture decelerates to start the assistant
+                mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
+                    if (isPaused && mState == STATE_ASSISTANT_ACTIVE) {
+                        mTimeFraction = 1;
+                        updateAssistantProgress();
+                    }
+                });
                 break;
             }
             case ACTION_POINTER_UP: {
@@ -107,94 +141,106 @@
                 break;
             }
             case ACTION_MOVE: {
+                if (mState == STATE_DELEGATE_ACTIVE) {
+                    break;
+                }
                 int pointerIndex = ev.findPointerIndex(mActivePointerId);
                 if (pointerIndex == -1) {
                     break;
                 }
                 mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
-                float displacement = getDisplacement(ev);
 
-                if (!mPassedDragSlop) {
-                    // Normal gesture, ensure we pass the drag slop before we start tracking
-                    // the gesture
-                    if (Math.abs(displacement) > mDragSlop) {
-                        mPassedDragSlop = true;
-                        mStartDisplacement = displacement;
-                        mPassedTouchSlopTime = SystemClock.uptimeMillis();
-                    }
-                }
+                if (!mPassedSlop) {
+                    // Normal gesture, ensure we pass the slop before we start tracking the gesture
+                    if (Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) > mSlop) {
+                        mPassedSlop = true;
+                        mStartDragPos.set(mLastPos.x, mLastPos.y);
+                        mDragTime = SystemClock.uptimeMillis();
 
-                if (!mPassedTouchSlop) {
-                    if (Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) >=
-                        mTouchSlop) {
-                        mPassedTouchSlop = true;
-                        if (!mPassedDragSlop) {
-                            mPassedDragSlop = true;
-                            mStartDisplacement = displacement;
-                            mPassedTouchSlopTime = SystemClock.uptimeMillis();
+                        // Determine if angle is larger than threshold for assistant detection
+                        float angle = (float) Math.toDegrees(
+                                Math.atan2(mDownPos.y - mLastPos.y, mDownPos.x - mLastPos.x));
+                        mDirection = angle > 90 ? UPLEFT : UPRIGHT;
+                        angle = angle > 90 ? 180 - angle : angle;
+                        if (angle > mAngleThreshold) {
+                            mState = STATE_ASSISTANT_ACTIVE;
+
+                            if (mConsumerDelegate != null) {
+                                // Send cancel event
+                                MotionEvent event = MotionEvent.obtain(ev);
+                                event.setAction(MotionEvent.ACTION_CANCEL);
+                                mConsumerDelegate.onMotionEvent(event);
+                                event.recycle();
+                            }
+                        } else {
+                            mState = STATE_DELEGATE_ACTIVE;
                         }
                     }
-                }
-
-                if (mPassedDragSlop) {
-                    // Move
-                    float distance = mStartDisplacement - displacement;
-                    if (distance >= 0) {
-                        onAssistantProgress(distance / mThreshold);
+                } else {
+                    // Movement
+                    mDistance = (float) Math.hypot(mLastPos.x - mStartDragPos.x,
+                            mLastPos.y - mStartDragPos.y);
+                    mMotionPauseDetector.addPosition(mDistance, 0, ev.getEventTime());
+                    if (mDistance >= 0) {
+                        final long diff = SystemClock.uptimeMillis() - mDragTime;
+                        mTimeFraction = Math.min(diff * 1f / mTimeThreshold, 1);
+                        updateAssistantProgress();
                     }
                 }
                 break;
             }
             case ACTION_CANCEL:
-                break;
-            case ACTION_UP: {
-                if (ev.getEventTime() - mPassedTouchSlopTime < ViewConfiguration.getTapTimeout()) {
-                    onAssistantProgress(1);
+            case ACTION_UP:
+                if (mState != STATE_DELEGATE_ACTIVE && !mLaunchedAssistant) {
+                    ValueAnimator animator = ValueAnimator.ofFloat(mLastProgress, 0)
+                            .setDuration(RETRACT_ANIMATION_DURATION_MS);
+                    UserEventDispatcher.newInstance(mContext).logActionOnContainer(
+                            SWIPE_NOOP, mDirection, NAVBAR);
+                    animator.addUpdateListener(valueAnimator -> {
+                            float progress = (float) valueAnimator.getAnimatedValue();
+                            try {
+
+                                mSysUiProxy.onAssistantProgress(progress);
+                            } catch (RemoteException e) {
+                                Log.w(TAG, "Failed to send SysUI start/send assistant progress: "
+                                        + progress, e);
+                            }
+                    });
+                    animator.setInterpolator(Interpolators.DEACCEL_2);
+                    animator.start();
                 }
-
+                mMotionPauseDetector.clear();
                 break;
-            }
+        }
+
+        if (mState != STATE_ASSISTANT_ACTIVE && mConsumerDelegate != null) {
+            mConsumerDelegate.onMotionEvent(ev);
         }
     }
 
-    private void onAssistantProgress(float progress) {
-        if (mLastProgress == progress) {
-            return;
-        }
-        try {
-            mSysUiProxy.onAssistantProgress(Math.max(0, Math.min(1, progress)));
-            if (progress >= 1 && !mLaunchedAssistant) {
-                mSysUiProxy.startAssistant(new Bundle());
-                mLaunchedAssistant = true;
-            }
+    private void updateAssistantProgress() {
+        if (!mLaunchedAssistant) {
+            float progress = Math.min(mDistance * 1f / mDistThreshold, 1) * mTimeFraction;
             mLastProgress = progress;
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to notify SysUI to start/send assistant progress: " + progress, e);
+            try {
+                mSysUiProxy.onAssistantProgress(progress);
+                if (mDistance >= mDistThreshold && mTimeFraction >= 1) {
+                    UserEventDispatcher.newInstance(mContext).logActionOnContainer(
+                            SWIPE, mDirection, NAVBAR);
+                    mSysUiProxy.startAssistant(new Bundle());
+                    mLaunchedAssistant = true;
+                }
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to send SysUI start/send assistant progress: " + progress, e);
+            }
         }
     }
 
-    private boolean isNavBarOnRight() {
-        return mDisplayRotation == Surface.ROTATION_90 && mStableInsets.right > 0;
-    }
-
-    private boolean isNavBarOnLeft() {
-        return mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0;
-    }
-
-    private float getDisplacement(MotionEvent ev) {
-        float eventX = ev.getX();
-        float eventY = ev.getY();
-        float displacement = eventY - mDownPos.y;
-        if (isNavBarOnRight()) {
-            displacement = eventX - mDownPos.x;
-        } else if (isNavBarOnLeft()) {
-            displacement = mDownPos.x - eventX;
-        }
-        return displacement;
-    }
-
-    static boolean withinTouchRegion(Context context, float x) {
-        return x > context.getResources().getDisplayMetrics().widthPixels
-                - context.getResources().getDimension(R.dimen.gestures_assistant_width);
+    static boolean withinTouchRegion(Context context, MotionEvent ev) {
+        final Resources res = context.getResources();
+        final int width = res.getDisplayMetrics().widthPixels;
+        final int height = res.getDisplayMetrics().heightPixels;
+        final int size = res.getDimensionPixelSize(R.dimen.gestures_assistant_size);
+        return (ev.getX() > width - size || ev.getX() < size) && ev.getY() > height - size;
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
index 1ed1353..21e98f2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -16,12 +16,12 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -60,7 +60,8 @@
     @Override
     public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
         LayoutUtils.calculateFallbackTaskSize(context, dp, outRect);
-        if (dp.isVerticalBarLayout()) {
+        if (dp.isVerticalBarLayout()
+                && SysUINavigationMode.INSTANCE.get(context).getMode() != NO_BUTTON) {
             Rect targetInsets = dp.getInsets();
             int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
             return dp.hotseatBarSizePx + hotseatInset;
@@ -74,6 +75,11 @@
         // TODO:
     }
 
+    @Override
+    public void onAssistantVisibilityChanged(float visibility) {
+        // TODO:
+    }
+
     @NonNull
     @Override
     public HomeAnimationFactory prepareHomeUI(RecentsActivity activity) {
@@ -90,7 +96,7 @@
 
             @NonNull
             @Override
-            public Animator createActivityAnimationToHome() {
+            public AnimatorPlaybackController createActivityAnimationToHome() {
                 Animator anim = ObjectAnimator.ofFloat(recentsView, CONTENT_ALPHA, 0);
                 anim.addListener(new AnimationSuccessListener() {
                     @Override
@@ -98,7 +104,10 @@
                         recentsView.startHome();
                     }
                 });
-                return anim;
+                AnimatorSet animatorSet = new AnimatorSet();
+                animatorSet.play(anim);
+                long accuracy = 2 * Math.max(recentsView.getWidth(), recentsView.getHeight());
+                return AnimatorPlaybackController.wrap(animatorSet, accuracy);
             }
         };
     }
@@ -182,13 +191,12 @@
     }
 
     @Override
-    public AlphaProperty getAlphaProperty(RecentsActivity activity) {
-        return activity.getDragLayer().getAlphaProperty(0);
-    }
-
-    @Override
     public int getContainerType() {
-        return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
+        RecentsActivity activity = getCreatedActivity();
+        boolean visible = activity != null && activity.isStarted() && activity.hasWindowFocus();
+        return visible
+                ? LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER
+                : LauncherLogProto.ContainerType.APP;
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index 971987a..766f484 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -16,14 +16,15 @@
 package com.android.quickstep;
 
 import static android.view.View.TRANSLATION_Y;
-
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_DAMPING_RATIO;
 import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_STIFFNESS;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -43,14 +44,16 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherInitListener;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.SpringObjectAnimator;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.LayoutUtils;
@@ -73,7 +76,8 @@
     @Override
     public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
         LayoutUtils.calculateLauncherTaskSize(context, dp, outRect);
-        if (dp.isVerticalBarLayout()) {
+        if (dp.isVerticalBarLayout()
+                && SysUINavigationMode.INSTANCE.get(context).getMode() != NO_BUTTON) {
             Rect targetInsets = dp.getInsets();
             int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
             return dp.hotseatBarSizePx + hotseatInset;
@@ -95,6 +99,14 @@
         DiscoveryBounce.showForOverviewIfNeeded(activity);
     }
 
+    @Override
+    public void onAssistantVisibilityChanged(float visibility) {
+        Launcher launcher = getCreatedActivity();
+        if (launcher != null) {
+            launcher.setQuickSearchBarAlpha(1f - visibility);
+        }
+    }
+
     @NonNull
     @Override
     public HomeAnimationFactory prepareHomeUI(Launcher activity) {
@@ -112,8 +124,7 @@
         final Rect iconLocation = new Rect();
         final FloatingIconView floatingView = workspaceView == null ? null
                 : FloatingIconView.getFloatingIconView(activity, workspaceView,
-                true /* hideOriginal */, false /* useDrawableAsIs */,
-                activity.getDeviceProfile().getAspectRatioWithInsets(), iconLocation, null);
+                true /* hideOriginal */, iconLocation, false /* isOpening */, null /* recycle */);
 
         return new HomeAnimationFactory() {
             @Nullable
@@ -140,10 +151,9 @@
 
             @NonNull
             @Override
-            public Animator createActivityAnimationToHome() {
+            public AnimatorPlaybackController createActivityAnimationToHome() {
                 long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
-                return activity.getStateManager().createAnimationToNewWorkspace(
-                        NORMAL, accuracy).getTarget();
+                return activity.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy);
             }
         };
     }
@@ -212,7 +222,7 @@
                         : mShelfState == ShelfAnimState.PEEK
                                 ? shelfPeekingProgress
                                 : shelfOverviewProgress;
-                mShelfAnim = createShelfAnim(activity, toProgress);
+                mShelfAnim = createShelfProgressAnim(activity, toProgress);
                 mShelfAnim.addListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
@@ -230,10 +240,10 @@
             LauncherState fromState, long transitionLength,
             Consumer<AnimatorPlaybackController> callback) {
         LauncherState endState = OVERVIEW;
+        DeviceProfile dp = activity.getDeviceProfile();
+        long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
         if (wasVisible && fromState != BACKGROUND_APP) {
             // If a translucent app was launched fom launcher, animate launcher states.
-            DeviceProfile dp = activity.getDeviceProfile();
-            long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
             callback.accept(activity.getStateManager()
                     .createAnimationToNewWorkspace(fromState, endState, accuracy));
             return;
@@ -246,10 +256,11 @@
         if (!activity.getDeviceProfile().isVerticalBarLayout()
                 && !FeatureFlags.SWIPE_HOME.get()) {
             // Don't animate the shelf when SWIPE_HOME is true, because we update it atomically.
-            Animator shiftAnim = createShelfAnim(activity,
+            Animator shiftAnim = createShelfProgressAnim(activity,
                     fromState.getVerticalProgress(activity),
                     endState.getVerticalProgress(activity));
             anim.play(shiftAnim);
+            anim.play(createShelfAlphaAnim(activity, endState, accuracy));
         }
         playScaleDownAnim(anim, activity, endState);
 
@@ -266,7 +277,7 @@
         callback.accept(controller);
     }
 
-    private Animator createShelfAnim(Launcher activity, float ... progressValues) {
+    private Animator createShelfProgressAnim(Launcher activity, float ... progressValues) {
         Animator shiftAnim = new SpringObjectAnimator<>(activity.getAllAppsController(),
                 "allAppsSpringFromACH", activity.getAllAppsController().getShiftRange(),
                 SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
@@ -275,6 +286,19 @@
     }
 
     /**
+     * Very quickly fade the alpha of shelf content.
+     */
+    private Animator createShelfAlphaAnim(Launcher activity, LauncherState toState, long accuracy) {
+        AllAppsTransitionController allAppsController = activity.getAllAppsController();
+        AnimatorSetBuilder animBuilder = new AnimatorSetBuilder();
+        animBuilder.setInterpolator(AnimatorSetBuilder.ANIM_ALL_APPS_FADE, DEACCEL_3);
+        LauncherStateManager.AnimationConfig config = new LauncherStateManager.AnimationConfig();
+        config.duration = accuracy;
+        allAppsController.setAlphas(toState.getVisibleElements(activity), config, animBuilder);
+        return animBuilder.build();
+    }
+
+    /**
      * Scale down recents from the center task being full screen to being in overview.
      */
     private void playScaleDownAnim(AnimatorSet anim, Launcher launcher,
@@ -290,7 +314,7 @@
         // starting to line up the side pages during swipe up)
         float prevRvScale = recentsView.getScaleX();
         float prevRvTransY = recentsView.getTranslationY();
-        float targetRvScale = endState.getOverviewScaleAndTranslationYFactor(launcher)[0];
+        float targetRvScale = endState.getOverviewScaleAndTranslation(launcher).scale;
         SCALE_PROPERTY.set(recentsView, targetRvScale);
         recentsView.setTranslationY(0);
         ClipAnimationHelper clipHelper = new ClipAnimationHelper(launcher);
@@ -376,11 +400,6 @@
     }
 
     @Override
-    public AlphaProperty getAlphaProperty(Launcher activity) {
-        return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP);
-    }
-
-    @Override
     public int getContainerType() {
         final Launcher launcher = getVisibleLauncher();
         return launcher != null ? launcher.getStateManager().getState().containerType
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java
index 9fceab4..357c9fc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java
@@ -18,6 +18,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.launcher3.config.FeatureFlags;
+
 import java.util.StringJoiner;
 import java.util.function.Consumer;
 
@@ -101,6 +103,9 @@
      * The callback is only run once.
      */
     public void addCallback(int stateMask, Runnable callback) {
+        if (FeatureFlags.IS_DOGFOOD_BUILD && mCallbacks.get(stateMask) != null) {
+            throw new IllegalStateException("Multiple callbacks on same state");
+        }
         mCallbacks.put(stateMask, callback);
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
index e3afb92..aada84f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
@@ -21,10 +21,10 @@
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.MotionEvent.INVALID_POINTER_ID;
-
 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
-import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
+import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
@@ -45,8 +45,6 @@
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
 
-import androidx.annotation.UiThread;
-
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RaceConditionTracker;
@@ -58,11 +56,14 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.BackgroundExecutor;
 import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.NavigationBarCompat;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
 import java.util.function.Consumer;
 
+import androidx.annotation.UiThread;
+
 /**
  * Input consumer for handling events originating from an activity other than Launcher
  */
@@ -81,6 +82,7 @@
     private final TaskOverlayFactory mTaskOverlayFactory;
     private final InputConsumerController mInputConsumer;
     private final SwipeSharedState mSwipeSharedState;
+    private final InputMonitorCompat mInputMonitorCompat;
 
     private final int mDisplayRotation;
     private final Rect mStableInsets = new Rect();
@@ -118,7 +120,7 @@
             boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks,
             TaskOverlayFactory taskOverlayFactory, InputConsumerController inputConsumer,
             Consumer<OtherActivityInputConsumer> onCompleteCallback,
-            SwipeSharedState swipeSharedState) {
+            SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat) {
         super(base);
 
         mMainThreadHandler = new Handler(Looper.getMainLooper());
@@ -129,9 +131,11 @@
         mMotionPauseDetector = new MotionPauseDetector(base);
         mOnCompleteCallback = onCompleteCallback;
         mVelocityTracker = VelocityTracker.obtain();
+        mInputMonitorCompat = inputMonitorCompat;
 
         mActivityControlHelper = activityControl;
-        mIsDeferredDownTarget = isDeferredDownTarget;
+        boolean continuingPreviousGesture = swipeSharedState.getActiveListener() != null;
+        mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
         mOverviewCallbacks = overviewCallbacks;
         mTaskOverlayFactory = taskOverlayFactory;
         mInputConsumer = inputConsumer;
@@ -143,8 +147,7 @@
 
         mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
         mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx();
-        // If active listener isn't null, we are continuing the previous gesture.
-        mPassedTouchSlop = mPassedDragSlop = mSwipeSharedState.getActiveListener() != null;
+        mPassedTouchSlop = mPassedDragSlop = continuingPreviousGesture;
     }
 
     @Override
@@ -230,7 +233,6 @@
                             mTouchSlop) {
                         mPassedTouchSlop = true;
 
-                        TOUCH_INTERACTION_LOG.addLog("startQuickstep");
                         if (mIsDeferredDownTarget) {
                             // Deferred gesture, start the animation and gesture tracking once
                             // we pass the actual touch slop
@@ -253,7 +255,8 @@
                         float orthogonalDisplacement = !isLandscape
                                 ? ev.getX() - mDownPos.x
                                 : ev.getY() - mDownPos.y;
-                        mMotionPauseDetector.addPosition(displacement, orthogonalDisplacement);
+                        mMotionPauseDetector.addPosition(displacement, orthogonalDisplacement,
+                                ev.getEventTime());
                     }
                 }
                 break;
@@ -272,9 +275,11 @@
     }
 
     private void notifyGestureStarted() {
+        TOUCH_INTERACTION_LOG.addLog("startQuickstep");
         if (mInteractionHandler == null) {
             return;
         }
+        mInputMonitorCompat.pilferPointers();
 
         mOverviewCallbacks.closeAllWindows();
         ActivityManagerWrapper.getInstance().closeSystemWindows(
@@ -285,11 +290,13 @@
     }
 
     private boolean isNavBarOnRight() {
-        return mDisplayRotation == Surface.ROTATION_90 && mStableInsets.right > 0;
+        return SysUINavigationMode.INSTANCE.get(getBaseContext()).getMode() != NO_BUTTON
+                && mDisplayRotation == Surface.ROTATION_90 && mStableInsets.right > 0;
     }
 
     private boolean isNavBarOnLeft() {
-        return mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0;
+        return SysUINavigationMode.INSTANCE.get(getBaseContext()).getMode() != NO_BUTTON
+                && mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0;
     }
 
     private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
@@ -310,6 +317,7 @@
         if (listenerSet != null) {
             listenerSet.addListener(handler);
             mSwipeSharedState.applyActiveRecentsAnimationState(handler);
+            notifyGestureStarted();
         } else {
             RecentsAnimationListenerSet newListenerSet =
                     mSwipeSharedState.newRecentsAnimationListenerSet();
@@ -330,12 +338,13 @@
             mVelocityTracker.computeCurrentVelocity(1000,
                     ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
             float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
+            float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
             float velocity = isNavBarOnRight() ? velocityX
                     : isNavBarOnLeft() ? -velocityX
-                            : mVelocityTracker.getYVelocity(mActivePointerId);
+                            : velocityY;
 
             mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
-            mInteractionHandler.onGestureEnded(velocity, velocityX);
+            mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY));
         } else {
             // Since we start touch tracking on DOWN, we may reach this state without actually
             // starting the gesture. In that case, just cleanup immediately.
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 d7898b4..3ebe968 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;
@@ -30,6 +29,7 @@
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.util.CachedEventDispatcher;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -45,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;
 
@@ -55,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;
     }
 
@@ -87,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 */);
                     }
@@ -139,7 +141,7 @@
             return;
         }
         int flags = ev.getEdgeFlags();
-        ev.setEdgeFlags(flags | TouchInteractionService.EDGE_NAV_BAR);
+        ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
         ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
         if (ev.getAction() == ACTION_DOWN) {
             mTarget.onInterceptTouchEvent(ev);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
index f0bc223..0924f38 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -173,6 +173,12 @@
         return true;
     }
 
+    public void setCancelWithDeferredScreenshot(boolean deferredWithScreenshot) {
+        if (targetSet != null) {
+            targetSet.controller.setCancelWithDeferredScreenshot(deferredWithScreenshot);
+        }
+    }
+
     public SwipeAnimationTargetSet getController() {
         return targetSet;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 6d608ee..87ae091 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -18,7 +18,7 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_CHANNEL;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 
 import android.annotation.TargetApi;
@@ -29,32 +29,41 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.RectF;
 import android.graphics.Region;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Process;
+import android.os.RemoteException;
 import android.util.Log;
-import android.util.Pair;
 import android.view.Choreographer;
+import android.view.Display;
 import android.view.InputEvent;
 import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.WindowManager;
 
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.EventLogArray;
+import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.UiThreadHelper;
+import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.InputChannelCompat;
-import com.android.systemui.shared.system.InputChannelCompat.InputEventDispatcher;
 import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
 import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.InputMonitorCompat;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -65,17 +74,19 @@
  * Service connected by system-UI for handling touch interaction.
  */
 @TargetApi(Build.VERSION_CODES.O)
-public class TouchInteractionService extends Service {
+public class TouchInteractionService extends Service implements
+        NavigationModeChangeListener, DisplayListener {
 
     public static final MainThreadExecutor MAIN_THREAD_EXECUTOR = new MainThreadExecutor();
     public static final LooperExecutor BACKGROUND_EXECUTOR =
             new LooperExecutor(UiThreadHelper.getBackgroundLooper());
 
+    private static final String NAVBAR_VERTICAL_SIZE = "navigation_bar_frame_height";
+    private static final String NAVBAR_HORIZONTAL_SIZE = "navigation_bar_frame_width";
+
     public static final EventLogArray TOUCH_INTERACTION_LOG =
             new EventLogArray("touch_interaction_log", 40);
 
-    public static final int EDGE_NAV_BAR = 1 << 8;
-
     private static final String TAG = "TouchInteractionService";
 
     private final IBinder mMyBinder = new IOverviewProxy.Stub() {
@@ -87,16 +98,12 @@
         public void onInitialize(Bundle bundle) {
             mISystemUiProxy = ISystemUiProxy.Stub
                     .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
+            MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
             runWhenUserUnlocked(() -> {
                 mRecentsModel.setSystemUiProxy(mISystemUiProxy);
                 mRecentsModel.onInitializeSystemUI(bundle);
                 mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
             });
-
-            disposeEventHandlers();
-            mInputEventReceiver = InputChannelCompat.fromBundle(bundle, KEY_EXTRA_INPUT_CHANNEL,
-                    Looper.getMainLooper(), mMainChoreographer,
-                    TouchInteractionService.this::onInputEvent);
         }
 
         @Override
@@ -127,6 +134,22 @@
             mAssistantAvailable = available;
         }
 
+        @Override
+        public void onAssistantVisibilityChanged(float visibility) {
+            MAIN_THREAD_EXECUTOR.execute(() -> {
+                mOverviewComponentObserver.getActivityControlHelper()
+                        .onAssistantVisibilityChanged(visibility);
+            });
+        }
+
+        public void onBackAction(boolean completed, int downX, int downY, boolean isButton,
+                boolean gestureSwipeLeft) {
+            final ActivityControlHelper activityControl =
+                    mOverviewComponentObserver.getActivityControlHelper();
+            UserEventDispatcher.newInstance(getBaseContext()).logActionBack(completed, downX, downY,
+                    isButton, gestureSwipeLeft, activityControl.getContainerType());
+        }
+
         /** Deprecated methods **/
         public void onQuickStep(MotionEvent motionEvent) { }
 
@@ -139,28 +162,10 @@
         public void onPreMotionEvent(int downHitTarget) { }
 
         public void onMotionEvent(MotionEvent ev) {
-            if (mDeprecatedDispatcher == null) {
-                ev.recycle();
-            } else {
-                mDeprecatedDispatcher.dispatch(ev);
-            }
+            ev.recycle();
         }
 
-        public void onBind(ISystemUiProxy iSystemUiProxy) {
-            mISystemUiProxy = iSystemUiProxy;
-            runWhenUserUnlocked(() -> {
-                mRecentsModel.setSystemUiProxy(mISystemUiProxy);
-                mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
-            });
-
-            // On Bind is received before onInitialize which will dispose these handlers
-            disposeEventHandlers();
-            Pair<InputEventDispatcher, InputEventReceiver> pair = InputChannelCompat.createPair(
-                    "sysui-callbacks", Looper.getMainLooper(), mMainChoreographer,
-                    TouchInteractionService.this::onInputEvent);
-            mDeprecatedDispatcher = pair.first;
-            mInputEventReceiver = pair.second;
-        }
+        public void onBind(ISystemUiProxy iSystemUiProxy) { }
     };
 
     private static boolean sConnected = false;
@@ -193,13 +198,17 @@
         }
     };
 
+    private InputConsumer mUncheckedConsumer = InputConsumer.NO_OP;
     private InputConsumer mConsumer = InputConsumer.NO_OP;
     private Choreographer mMainChoreographer;
 
-    private InputEventReceiver mInputEventReceiver;
     private Region mActiveNavBarRegion = new Region();
 
-    private InputEventDispatcher mDeprecatedDispatcher;
+    private InputMonitorCompat mInputMonitorCompat;
+    private InputEventReceiver mInputEventReceiver;
+    private Mode mMode = Mode.THREE_BUTTONS;
+    private int mDefaultDisplayId;
+    private final RectF mSwipeTouchRegion = new RectF();
 
     @Override
     public void onCreate() {
@@ -217,10 +226,112 @@
             mIsUserUnlocked = false;
             registerReceiver(mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
         }
+        onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this));
 
+        mDefaultDisplayId = getSystemService(WindowManager.class).getDefaultDisplay()
+                .getDisplayId();
         sConnected = true;
     }
 
+    private void disposeEventHandlers() {
+        if (mInputEventReceiver != null) {
+            mInputEventReceiver.dispose();
+            mInputEventReceiver = null;
+        }
+        if (mInputMonitorCompat != null) {
+            mInputMonitorCompat.dispose();
+            mInputMonitorCompat = null;
+        }
+    }
+
+    private void initInputMonitor() {
+        if (!mMode.hasGestures || mISystemUiProxy == null) {
+            return;
+        }
+        disposeEventHandlers();
+
+        try {
+            mInputMonitorCompat = InputMonitorCompat.fromBundle(mISystemUiProxy
+                    .monitorGestureInput("swipe-up", mDefaultDisplayId), KEY_EXTRA_INPUT_MONITOR);
+            mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
+                    mMainChoreographer, this::onInputEvent);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to create input monitor", e);
+        }
+        initTouchBounds();
+    }
+
+    private int getNavbarSize(String resName) {
+        int frameSize;
+        Resources res = getResources();
+        int frameSizeResID = res.getIdentifier(resName, "dimen", "android");
+        if (frameSizeResID != 0) {
+            frameSize = res.getDimensionPixelSize(frameSizeResID);
+        } else {
+            frameSize = Utilities.pxFromDp(48, res.getDisplayMetrics());
+        }
+        return frameSize;
+    }
+
+    private void initTouchBounds() {
+        if (!mMode.hasGestures) {
+            return;
+        }
+
+        Display defaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
+        Point realSize = new Point();
+        defaultDisplay.getRealSize(realSize);
+        mSwipeTouchRegion.set(0, 0, realSize.x, realSize.y);
+        if (mMode == Mode.NO_BUTTON) {
+            mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - getNavbarSize(NAVBAR_VERTICAL_SIZE);
+        } else {
+            switch (defaultDisplay.getRotation()) {
+                case Surface.ROTATION_90:
+                    mSwipeTouchRegion.left = mSwipeTouchRegion.right
+                            - getNavbarSize(NAVBAR_HORIZONTAL_SIZE);
+                    break;
+                case Surface.ROTATION_270:
+                    mSwipeTouchRegion.right = mSwipeTouchRegion.left
+                            + getNavbarSize(NAVBAR_HORIZONTAL_SIZE);
+                    break;
+                default:
+                    mSwipeTouchRegion.top = mSwipeTouchRegion.bottom
+                            - getNavbarSize(NAVBAR_VERTICAL_SIZE);
+            }
+        }
+    }
+
+    @Override
+    public void onNavigationModeChanged(Mode newMode) {
+        if (mMode.hasGestures != newMode.hasGestures) {
+            if (newMode.hasGestures) {
+                getSystemService(DisplayManager.class).registerDisplayListener(
+                        this, MAIN_THREAD_EXECUTOR.getHandler());
+            } else {
+                getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+            }
+        }
+        mMode = newMode;
+
+        disposeEventHandlers();
+        initInputMonitor();
+    }
+
+    @Override
+    public void onDisplayAdded(int i) { }
+
+    @Override
+    public void onDisplayRemoved(int i) { }
+
+    @Override
+    public void onDisplayChanged(int displayId) {
+        if (displayId != mDefaultDisplayId) {
+            return;
+        }
+
+        initTouchBounds();
+    }
+
     private void initWhenUserUnlocked() {
         mIsUserUnlocked = true;
 
@@ -262,20 +373,15 @@
             mOverviewComponentObserver.onDestroy();
         }
         disposeEventHandlers();
+        if (mMode.hasGestures) {
+            getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+        }
+
         sConnected = false;
         Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
-        super.onDestroy();
-    }
+        SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
 
-    private void disposeEventHandlers() {
-        if (mInputEventReceiver != null) {
-            mInputEventReceiver.dispose();
-            mInputEventReceiver = null;
-        }
-        if (mDeprecatedDispatcher != null) {
-            mDeprecatedDispatcher.dispose();
-            mDeprecatedDispatcher = null;
-        }
+        super.onDestroy();
     }
 
     @Override
@@ -292,12 +398,17 @@
         MotionEvent event = (MotionEvent) ev;
         TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked());
         if (event.getAction() == ACTION_DOWN) {
-            boolean useSharedState = mConsumer.isActive();
-            mConsumer.onConsumerAboutToBeSwitched();
-            mConsumer = newConsumer(useSharedState, event);
-            TOUCH_INTERACTION_LOG.addLog("setInputConsumer", mConsumer.getType());
+            if (mSwipeTouchRegion.contains(event.getX(), event.getY())) {
+                boolean useSharedState = mConsumer.isActive();
+                mConsumer.onConsumerAboutToBeSwitched();
+                mConsumer = newConsumer(useSharedState, event);
+                TOUCH_INTERACTION_LOG.addLog("setInputConsumer", mConsumer.getType());
+                mUncheckedConsumer = mConsumer;
+            } else {
+                mUncheckedConsumer = InputConsumer.NO_OP;
+            }
         }
-        mConsumer.onMotionEvent(event);
+        mUncheckedConsumer.onMotionEvent(event);
     }
 
     private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) {
@@ -313,31 +424,36 @@
             mSwipeSharedState.clearAllState();
         }
 
+        final ActivityControlHelper activityControl =
+                mOverviewComponentObserver.getActivityControlHelper();
         if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
             return InputConsumer.NO_OP;
-        } else if (mAssistantAvailable && mOverviewInteractionState.isSwipeUpGestureEnabled()
-                && FeatureFlags.ENABLE_ASSISTANT_GESTURE.get()
-                && AssistantTouchConsumer.withinTouchRegion(this, event.getX())) {
-            return new AssistantTouchConsumer(this, mRecentsModel.getSystemUiProxy());
-        } else if (mSwipeSharedState.goingToLauncher ||
-                mOverviewComponentObserver.getActivityControlHelper().isResumed()) {
-            return OverviewInputConsumer.newInstance(
-                    mOverviewComponentObserver.getActivityControlHelper(), false);
+        } else if (mAssistantAvailable
+                && SysUINavigationMode.INSTANCE.get(this).getMode() == Mode.NO_BUTTON
+                && AssistantTouchConsumer.withinTouchRegion(this, event)) {
+            return new AssistantTouchConsumer(this, mISystemUiProxy, !activityControl.isResumed()
+                            ? createOtherActivityInputConsumer(event, runningTaskInfo) : null);
+        } else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
+            return OverviewInputConsumer.newInstance(activityControl, false);
         } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
-                mOverviewComponentObserver.getActivityControlHelper().isInLiveTileMode()) {
-            return OverviewInputConsumer.newInstance(
-                    mOverviewComponentObserver.getActivityControlHelper(), false);
+                activityControl.isInLiveTileMode()) {
+            return OverviewInputConsumer.newInstance(activityControl, false);
         } else {
-            ActivityControlHelper activityControl =
-                    mOverviewComponentObserver.getActivityControlHelper();
-            boolean shouldDefer = activityControl.deferStartingActivity(mActiveNavBarRegion, event);
-            return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel,
-                    mOverviewComponentObserver.getOverviewIntent(), activityControl,
-                    shouldDefer, mOverviewCallbacks, mTaskOverlayFactory, mInputConsumer,
-                    this::onConsumerInactive, mSwipeSharedState);
+            return createOtherActivityInputConsumer(event, runningTaskInfo);
         }
     }
 
+    private OtherActivityInputConsumer createOtherActivityInputConsumer(MotionEvent event,
+            RunningTaskInfo runningTaskInfo) {
+        final ActivityControlHelper activityControl =
+                mOverviewComponentObserver.getActivityControlHelper();
+        boolean shouldDefer = activityControl.deferStartingActivity(mActiveNavBarRegion, event);
+        return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel,
+                mOverviewComponentObserver.getOverviewIntent(), activityControl,
+                shouldDefer, mOverviewCallbacks, mTaskOverlayFactory, mInputConsumer,
+                this::onConsumerInactive, mSwipeSharedState, mInputMonitorCompat);
+    }
+
     /**
      * To be called by the consumer when it's no longer active.
      */
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 7dc58a5..b1db780 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -28,6 +28,7 @@
 import static com.android.launcher3.config.FeatureFlags.SWIPE_HOME;
 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
+import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.HIDE;
 import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.PEEK;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
@@ -40,16 +41,15 @@
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
@@ -78,8 +78,6 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.util.MultiValueAlpha;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.RaceConditionTracker;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.views.FloatingIconView;
@@ -88,6 +86,7 @@
 import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
 import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
 import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
@@ -95,7 +94,6 @@
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.recents.utilities.RectFEvaluator;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -113,7 +111,7 @@
         implements SwipeAnimationListener, OnApplyWindowInsetsListener {
     private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
 
-    private static final String[] STATE_NAMES = DEBUG_STATES ? new String[19] : null;
+    private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
 
     private static int getFlagForIndex(int index, String name) {
         if (DEBUG_STATES) {
@@ -126,49 +124,42 @@
     private static final int STATE_LAUNCHER_PRESENT = getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
     private static final int STATE_LAUNCHER_STARTED = getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
     private static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
-    private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE =
-            getFlagForIndex(3, "STATE_ACTIVITY_MULTIPLIER_COMPLETE");
 
     // Internal initialization states
     private static final int STATE_APP_CONTROLLER_RECEIVED =
-            getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
+            getFlagForIndex(3, "STATE_APP_CONTROLLER_RECEIVED");
 
     // Interaction finish states
     private static final int STATE_SCALED_CONTROLLER_HOME =
-            getFlagForIndex(5, "STATE_SCALED_CONTROLLER_HOME");
+            getFlagForIndex(4, "STATE_SCALED_CONTROLLER_HOME");
     private static final int STATE_SCALED_CONTROLLER_RECENTS =
-            getFlagForIndex(6, "STATE_SCALED_CONTROLLER_RECENTS");
-    private static final int STATE_SCALED_CONTROLLER_LAST_TASK =
-            getFlagForIndex(7, "STATE_SCALED_CONTROLLER_LAST_TASK");
+            getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
 
     private static final int STATE_HANDLER_INVALIDATED =
-            getFlagForIndex(8, "STATE_HANDLER_INVALIDATED");
+            getFlagForIndex(6, "STATE_HANDLER_INVALIDATED");
     private static final int STATE_GESTURE_STARTED =
-            getFlagForIndex(9, "STATE_GESTURE_STARTED");
+            getFlagForIndex(7, "STATE_GESTURE_STARTED");
     private static final int STATE_GESTURE_CANCELLED =
-            getFlagForIndex(10, "STATE_GESTURE_CANCELLED");
+            getFlagForIndex(8, "STATE_GESTURE_CANCELLED");
     private static final int STATE_GESTURE_COMPLETED =
-            getFlagForIndex(11, "STATE_GESTURE_COMPLETED");
+            getFlagForIndex(9, "STATE_GESTURE_COMPLETED");
 
     private static final int STATE_CAPTURE_SCREENSHOT =
-            getFlagForIndex(12, "STATE_CAPTURE_SCREENSHOT");
+            getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT");
     private static final int STATE_SCREENSHOT_CAPTURED =
-            getFlagForIndex(13, "STATE_SCREENSHOT_CAPTURED");
+            getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED");
     private static final int STATE_SCREENSHOT_VIEW_SHOWN =
-            getFlagForIndex(14, "STATE_SCREENSHOT_VIEW_SHOWN");
+            getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN");
 
     private static final int STATE_RESUME_LAST_TASK =
-            getFlagForIndex(15, "STATE_RESUME_LAST_TASK");
+            getFlagForIndex(13, "STATE_RESUME_LAST_TASK");
     private static final int STATE_START_NEW_TASK =
-            getFlagForIndex(16, "STATE_START_NEW_TASK");
+            getFlagForIndex(14, "STATE_START_NEW_TASK");
     private static final int STATE_CURRENT_TASK_FINISHED =
-            getFlagForIndex(17, "STATE_CURRENT_TASK_FINISHED");
+            getFlagForIndex(15, "STATE_CURRENT_TASK_FINISHED");
 
     private static final int LAUNCHER_UI_STATES =
-            STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_ACTIVITY_MULTIPLIER_COMPLETE
-            | STATE_LAUNCHER_STARTED;
-
-    // For debugging, keep in sync with above states
+            STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
 
     enum GestureEndTarget {
         HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE),
@@ -178,7 +169,7 @@
 
         NEW_TASK(0, STATE_START_NEW_TASK, false, true, ContainerType.APP),
 
-        LAST_TASK(0, STATE_SCALED_CONTROLLER_LAST_TASK, false, false, ContainerType.APP);
+        LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP);
 
         GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued,
                 int containerType) {
@@ -212,6 +203,11 @@
 
     private static final long SHELF_ANIM_DURATION = 120;
 
+    /**
+     * Used as the page index for logging when we return to the last task at the end of the gesture.
+     */
+    private static final int LOG_NO_OP_PAGE_INDEX = -1;
+
     private final ClipAnimationHelper mClipAnimationHelper;
     private final ClipAnimationHelper.TransformParams mTransformParams;
 
@@ -226,7 +222,6 @@
     // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
     // visible.
     private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
-    private boolean mDispatchedDownEvent;
     private boolean mContinuingLastGesture;
     // To avoid UI jump when gesture is started, we offset the animation by the threshold.
     private float mShiftAtGestureStart = 0;
@@ -238,10 +233,10 @@
     private final ActivityInitListener mActivityInitListener;
 
     private final int mRunningTaskId;
-    private final RunningTaskInfo mRunningTaskInfo;
     private ThumbnailData mTaskSnapshot;
 
     private MultiStateCallback mStateCallback;
+    // Used to control launcher components throughout the swipe gesture.
     private AnimatorPlaybackController mLauncherTransitionController;
 
     private T mActivity;
@@ -255,6 +250,7 @@
     private boolean mPassedOverviewThreshold;
     private boolean mGestureStarted;
     private int mLogAction = Touch.SWIPE;
+    private int mLogDirection = Direction.UP;
 
     private final RecentsAnimationWrapper mRecentsAnimationWrapper;
 
@@ -265,7 +261,6 @@
             long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
             InputConsumerController inputConsumer) {
         mContext = context;
-        mRunningTaskInfo = runningTaskInfo;
         mRunningTaskId = runningTaskInfo.id;
         mTouchTimeMs = touchTimeMs;
         mActivityControlHelper = controller;
@@ -283,10 +278,8 @@
     private void initStateCallbacks() {
         mStateCallback = new MultiStateCallback(STATE_NAMES);
 
-        // Re-setup the recents UI when gesture starts, as the state could have been changed during
-        // that time by a previous window transition.
-        mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_GESTURE_STARTED,
-                this::setupRecentsViewUi);
+        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
+                this::onLauncherPresentAndGestureStarted);
 
         mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
                 this::initializeLauncherAnimationController);
@@ -294,9 +287,6 @@
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
                 this::launcherFrameDrawn);
 
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
-                this::notifyGestureStartedAsync);
-
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
                         | STATE_GESTURE_CANCELLED,
                 this::resetStateForAnimationCancel);
@@ -304,16 +294,13 @@
         mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
                 this::sendRemoteAnimationsToAnimationFactory);
 
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SCALED_CONTROLLER_LAST_TASK,
-                this::resumeLastTaskForQuickstep);
         mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
                 this::resumeLastTask);
         mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_APP_CONTROLLER_RECEIVED,
                 this::startNewTask);
 
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
-                        | STATE_ACTIVITY_MULTIPLIER_COMPLETE
-                        | STATE_CAPTURE_SCREENSHOT,
+                        | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT,
                 this::switchToScreenshot);
 
         mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
@@ -322,13 +309,13 @@
 
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_COMPLETED
                         | STATE_SCALED_CONTROLLER_HOME | STATE_APP_CONTROLLER_RECEIVED
-                        | STATE_ACTIVITY_MULTIPLIER_COMPLETE,
+                        | STATE_LAUNCHER_DRAWN,
                 this::finishCurrentTransitionToHome);
         mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
                 this::reset);
 
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
-                        | STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_SCALED_CONTROLLER_RECENTS
+                        | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
                         | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
                         | STATE_GESTURE_STARTED,
                 this::setupLauncherUiAfterSwipeUpAnimation);
@@ -336,8 +323,7 @@
         mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
                 this::invalidateHandlerWithLauncher);
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED
-                | STATE_SCALED_CONTROLLER_LAST_TASK,
+        mStateCallback.addCallback(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
                 this::notifyTransitionCancelled);
 
         mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
@@ -415,6 +401,7 @@
         });
         mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
         mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
+        mRecentsView.setLiveTileOverlay(mLiveTileOverlay);
         mActivity.getRootView().getOverlay().add(mLiveTileOverlay);
 
         mStateCallback.setState(STATE_LAUNCHER_PRESENT);
@@ -423,6 +410,8 @@
         } else {
             activity.setOnStartCallback(this::onLauncherStart);
         }
+
+        setupRecentsViewUi();
         return true;
     }
 
@@ -434,16 +423,19 @@
             return;
         }
 
-        mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
-                mWasLauncherAlreadyVisible, true, this::onAnimatorPlaybackControllerCreated);
+        // If we've already ended the gesture and are going home, don't prepare recents UI,
+        // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
+        if (mGestureEndTarget != HOME) {
+            mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
+                    mWasLauncherAlreadyVisible, true, this::onAnimatorPlaybackControllerCreated);
+        }
         AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible);
 
         if (mWasLauncherAlreadyVisible) {
-            mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_LAUNCHER_DRAWN);
+            mStateCallback.setState(STATE_LAUNCHER_DRAWN);
         } else {
             TraceHelper.beginSection("WTS-init");
             View dragLayer = activity.getDragLayer();
-            mActivityControlHelper.getAlphaProperty(activity).setValue(0);
             dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
 
                 @Override
@@ -460,11 +452,18 @@
             });
         }
 
-        setupRecentsViewUi();
         activity.getRootView().setOnApplyWindowInsetsListener(this);
         mStateCallback.setState(STATE_LAUNCHER_STARTED);
     }
 
+    private void onLauncherPresentAndGestureStarted() {
+        // Re-setup the recents UI when gesture starts, as the state could have been changed during
+        // that time by a previous window transition.
+        setupRecentsViewUi();
+
+        notifyGestureStartedAsync();
+    }
+
     private void setupRecentsViewUi() {
         if (mContinuingLastGesture) {
             return;
@@ -476,25 +475,6 @@
     }
 
     private void launcherFrameDrawn() {
-        AlphaProperty property = mActivityControlHelper.getAlphaProperty(mActivity);
-        if (property.getValue() < 1) {
-            if (mGestureStarted) {
-                final MultiStateCallback callback = mStateCallback;
-                ObjectAnimator animator = ObjectAnimator.ofFloat(
-                        property, MultiValueAlpha.VALUE, 1);
-                animator.setDuration(getFadeInDuration()).addListener(
-                        new AnimatorListenerAdapter() {
-                            @Override
-                            public void onAnimationEnd(Animator animation) {
-                                callback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
-                            }
-                        });
-                animator.start();
-            } else {
-                property.setValue(1);
-                mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
-            }
-        }
         mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
     }
 
@@ -706,15 +686,25 @@
         }
     }
 
+    /**
+     * @param endVelocity The velocity in the direction of the nav bar to the middle of the screen.
+     * @param velocity The x and y components of the velocity when the gesture ends.
+     */
     @UiThread
-    public void onGestureEnded(float endVelocity, float velocityX) {
+    public void onGestureEnded(float endVelocity, PointF velocity) {
         float flingThreshold = mContext.getResources()
                 .getDimension(R.dimen.quickstep_fling_threshold_velocity);
         boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
         setStateOnUiThread(STATE_GESTURE_COMPLETED);
 
         mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
-        handleNormalGestureEnd(endVelocity, isFling, velocityX);
+        boolean isVelocityVertical = Math.abs(velocity.y) > Math.abs(velocity.x);
+        if (isVelocityVertical) {
+            mLogDirection = velocity.y < 0 ? Direction.UP : Direction.DOWN;
+        } else {
+            mLogDirection = velocity.x < 0 ? Direction.LEFT : Direction.RIGHT;
+        }
+        handleNormalGestureEnd(endVelocity, isFling, velocity);
     }
 
     @UiThread
@@ -732,9 +722,8 @@
     }
 
     @UiThread
-    private void handleNormalGestureEnd(float endVelocity, boolean isFling, float velocityX) {
-        float velocityPxPerMs = endVelocity / 1000;
-        float velocityXPxPerMs = velocityX / 1000;
+    private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity) {
+        PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000);
         long duration = MAX_SWIPE_DURATION;
         float currentShift = mCurrentShift.value;
         final GestureEndTarget endTarget;
@@ -749,7 +738,7 @@
             final int lastTaskIndex = mRecentsView.getTaskViewCount() - 1;
             final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
             taskToLaunch = nextPage <= lastTaskIndex ? nextPage : lastTaskIndex;
-            goingToNewTask = mRecentsView != null && taskToLaunch != runningTaskIndex;
+            goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
         } else {
             goingToNewTask = false;
         }
@@ -779,7 +768,7 @@
         } else {
             if (SWIPE_HOME.get() && endVelocity < 0 && !mIsShelfPeeking) {
                 // If swiping at a diagonal, base end target on the faster velocity.
-                endTarget = goingToNewTask && Math.abs(velocityX) > Math.abs(endVelocity)
+                endTarget = goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity)
                         ? NEW_TASK : HOME;
             } else if (endVelocity < 0 && (!goingToNewTask || reachedOverviewThreshold)) {
                 // If user scrolled to a new task, only go to recents if they already passed
@@ -789,14 +778,15 @@
                 endTarget = goingToNewTask ? NEW_TASK : LAST_TASK;
             }
             endShift = endTarget.endShift;
-            startShift = Utilities.boundToRange(currentShift - velocityPxPerMs
+            startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
                     * SINGLE_FRAME_MS / mTransitionDragLength, 0, 1);
             float minFlingVelocity = mContext.getResources()
                     .getDimension(R.dimen.quickstep_fling_min_velocity);
             if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
                 if (endTarget == RECENTS) {
                     Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
-                            startShift, endShift, endShift, velocityPxPerMs, mTransitionDragLength);
+                            startShift, endShift, endShift, velocityPxPerMs.y,
+                            mTransitionDragLength);
                     endShift = overshoot.end;
                     interpolator = overshoot.interpolator;
                     duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
@@ -807,7 +797,7 @@
                     // we want the page's snap velocity to approximately match the velocity at
                     // which the user flings, so we scale the duration by a value near to the
                     // derivative of the scroll interpolator at zero, ie. 2.
-                    long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs));
+                    long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
                     duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
                 }
             }
@@ -822,6 +812,7 @@
             setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
             duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
         } else if (endTarget == RECENTS) {
+            mLiveTileOverlay.startIconAnimation();
             mRecentsAnimationWrapper.enableInputProxy();
             if (mRecentsView != null) {
                 duration = Math.max(duration, mRecentsView.getScroller().getDuration());
@@ -831,7 +822,7 @@
             }
         } else if (endTarget == NEW_TASK || endTarget == LAST_TASK) {
             // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
-            // or resumeLastTaskForQuickstep().
+            // or resumeLastTask().
             if (mRecentsView != null) {
                 duration = Math.max(duration, mRecentsView.getScroller().getDuration());
             }
@@ -845,47 +836,32 @@
             // We probably never received an animation controller, skip logging.
             return;
         }
-        boolean toLauncher = endTarget.isLauncher;
-        final int direction;
-        if (dp.isVerticalBarLayout()) {
-            direction = (dp.isSeascape() ^ toLauncher) ? Direction.LEFT : Direction.RIGHT;
-        } else {
-            direction = toLauncher ? Direction.UP : Direction.DOWN;
-        }
 
+        int pageIndex = endTarget == LAST_TASK
+                ? LOG_NO_OP_PAGE_INDEX
+                : mRecentsView.getNextPage();
         UserEventDispatcher.newInstance(mContext).logStateChangeAction(
-                mLogAction, direction,
+                mLogAction, mLogDirection,
                 ContainerType.NAVBAR, ContainerType.APP,
                 endTarget.containerType,
-                0);
+                pageIndex);
     }
 
     /** Animates to the given progress, where 0 is the current app and 1 is overview. */
     @UiThread
     private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
-            GestureEndTarget target, float velocityPxPerMs) {
+            GestureEndTarget target, PointF velocityPxPerMs) {
         mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration,
                 interpolator, target, velocityPxPerMs));
     }
 
     @UiThread
     private void animateToProgressInternal(float start, float end, long duration,
-            Interpolator interpolator, GestureEndTarget target, float velocityPxPerMs) {
+            Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
         mGestureEndTarget = target;
 
-        if (mGestureEndTarget.canBeContinued) {
-            // Because we might continue this gesture, e.g. for consecutive quick switch, we need to
-            // stabilize the task list so that tasks don't rearrange in the middle of the gesture.
-            RecentsModel.INSTANCE.get(mContext).startStabilizationSession();
-        } else if (mGestureEndTarget.isLauncher) {
-            // Otherwise, if we're going to home or overview,
-            // we reset the tasks to a consistent start state.
-            RecentsModel.INSTANCE.get(mContext).endStabilizationSession();
-        }
-
-        HomeAnimationFactory homeAnimFactory;
-        Animator windowAnim;
         if (mGestureEndTarget == HOME) {
+            HomeAnimationFactory homeAnimFactory;
             if (mActivity != null) {
                 homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity);
             } else {
@@ -900,27 +876,33 @@
 
                     @NonNull
                     @Override
-                    public Animator createActivityAnimationToHome() {
-                        return new AnimatorSet();
+                    public AnimatorPlaybackController createActivityAnimationToHome() {
+                        return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
                     }
                 };
                 mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
                         isPresent -> mRecentsView.startHome());
             }
-            windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
+            RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
+            windowAnim.addAnimatorListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    setStateOnUiThread(target.endState);
+                }
+            });
+            windowAnim.start(velocityPxPerMs);
             mLauncherTransitionController = null;
         } else {
-            windowAnim = mCurrentShift.animateToValue(start, end);
-            homeAnimFactory = null;
+            Animator windowAnim = mCurrentShift.animateToValue(start, end);
+            windowAnim.setDuration(duration).setInterpolator(interpolator);
+            windowAnim.addListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    setStateOnUiThread(target.endState);
+                }
+            });
+            windowAnim.start();
         }
-        windowAnim.setDuration(duration).setInterpolator(interpolator);
-        windowAnim.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                setStateOnUiThread(target.endState);
-            }
-        });
-        windowAnim.start();
         // Always play the entire launcher animation when going home, since it is separate from
         // the animation that has been controlled thus far.
         if (mGestureEndTarget == HOME) {
@@ -931,12 +913,6 @@
         // interpolate over the remaining progress (end - start).
         TimeInterpolator adjustedInterpolator = Interpolators.mapToProgress(
                 interpolator, start, end);
-        if (homeAnimFactory != null) {
-            Animator homeAnim = homeAnimFactory.createActivityAnimationToHome();
-            homeAnim.setDuration(duration).setInterpolator(adjustedInterpolator);
-            homeAnim.start();
-            mLauncherTransitionController = null;
-        }
         if (mLauncherTransitionController == null) {
             return;
         }
@@ -948,65 +924,65 @@
             mLauncherTransitionController.getAnimationPlayer().setDuration(duration);
 
             if (QUICKSTEP_SPRINGS.get()) {
-                mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs);
+                mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs.y);
             }
             mLauncherTransitionController.getAnimationPlayer().start();
         }
     }
 
     /**
-     * Creates an Animator that transforms the current app window into the home app.
+     * Creates an animation that transforms the current app window into the home app.
      * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
      * @param homeAnimationFactory The home animation factory.
      */
-    private Animator createWindowAnimationToHome(float startProgress,
+    private RectFSpringAnim createWindowAnimationToHome(float startProgress,
             HomeAnimationFactory homeAnimationFactory) {
         final RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet;
-        RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet,
-                mTransformParams.setProgress(startProgress)));
-        RectF originalTarget = new RectF(mClipAnimationHelper.getTargetRect());
-        final RectF finalTarget = homeAnimationFactory.getWindowTargetRect();
-
-        final RectFEvaluator rectFEvaluator = new RectFEvaluator();
-        final RectF targetRect = new RectF();
-        final RectF currentRect = new RectF();
+        final RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet,
+                mTransformParams.setProgress(startProgress), false /* launcherOnTop */));
+        final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
 
         final View floatingView = homeAnimationFactory.getFloatingView();
         final boolean isFloatingIconView = floatingView instanceof FloatingIconView;
 
-        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+        RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect);
         if (isFloatingIconView) {
-            anim.addListener((FloatingIconView) floatingView);
+            anim.addAnimatorListener((FloatingIconView) floatingView);
         }
 
+        AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome();
+
         // We want the window alpha to be 0 once this threshold is met, so that the
         // FolderIconView can be seen morphing into the icon shape.
-        final float windowAlphaThreshold = isFloatingIconView ? 0.75f : 1f;
-        anim.addUpdateListener(animation -> {
-            float progress = animation.getAnimatedFraction();
+        final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f;
+        anim.addOnUpdateListener((currentRect, progress) -> {
             float interpolatedProgress = Interpolators.ACCEL_1_5.getInterpolation(progress);
-            // Initially go towards original target (task view in recents),
-            // but accelerate towards the final target.
-            // TODO: This is technically not correct. Instead, motion should continue at
-            // the released velocity but accelerate towards the target.
-            targetRect.set(rectFEvaluator.evaluate(interpolatedProgress,
-                    originalTarget, finalTarget));
-            currentRect.set(rectFEvaluator.evaluate(interpolatedProgress, startRect, targetRect));
+
+            homeAnim.setPlayFraction(progress);
 
             float iconAlpha = Utilities.mapToRange(interpolatedProgress, 0,
                     windowAlphaThreshold, 0f, 1f, Interpolators.LINEAR);
             mTransformParams.setCurrentRectAndTargetAlpha(currentRect, 1f - iconAlpha)
                     .setSyncTransactionApplier(mSyncTransactionApplier);
-            mClipAnimationHelper.applyTransform(targetSet, mTransformParams);
+            mClipAnimationHelper.applyTransform(targetSet, mTransformParams,
+                    false /* launcherOnTop */);
 
             if (isFloatingIconView) {
                 ((FloatingIconView) floatingView).update(currentRect, iconAlpha, progress,
-                        windowAlphaThreshold);
+                        windowAlphaThreshold, mClipAnimationHelper.getCurrentCornerRadius(), false);
             }
+
         });
-        anim.addListener(new AnimationSuccessListener() {
+        anim.addAnimatorListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                homeAnim.dispatchOnStart();
+                mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
+            }
+
             @Override
             public void onAnimationSuccess(Animator animator) {
+                homeAnim.getAnimationPlayer().end();
                 if (mRecentsView != null) {
                     mRecentsView.post(mRecentsView::resetTaskVisuals);
                 }
@@ -1016,16 +992,11 @@
     }
 
     @UiThread
-    private void resumeLastTaskForQuickstep() {
-        setStateOnUiThread(STATE_RESUME_LAST_TASK);
-        doLogGesture(LAST_TASK);
-        reset();
-    }
-
-    @UiThread
     private void resumeLastTask() {
         mRecentsAnimationWrapper.finish(false /* toRecents */, null);
         TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
+        doLogGesture(LAST_TASK);
+        reset();
     }
 
     @UiThread
@@ -1033,18 +1004,17 @@
         // Launch the task user scrolled to (mRecentsView.getNextPage()).
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             // We finish recents animation inside launchTask() when live tile is enabled.
-            mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false,
-                    result -> setStateOnUiThread(STATE_HANDLER_INVALIDATED),
-                    mMainThreadHandler);
+            mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false /* animate */,
+                    true /* freezeTaskList */);
         } else {
             mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
-                mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false,
-                        result -> setStateOnUiThread(STATE_HANDLER_INVALIDATED),
-                        mMainThreadHandler);
+                mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(
+                        false /* animate */, true /* freezeTaskList */);
             });
         }
         TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
         doLogGesture(NEW_TASK);
+        reset();
     }
 
     public void reset() {
@@ -1072,7 +1042,6 @@
 
     private void invalidateHandlerWithLauncher() {
         mLauncherTransitionController = null;
-        mActivityControlHelper.getAlphaProperty(mActivity).setValue(1);
 
         mRecentsView.setEnableFreeScroll(true);
         mRecentsView.setRunningTaskIconScaledDown(false);
@@ -1170,9 +1139,10 @@
             mLauncherTransitionController = null;
         }
         mActivityControlHelper.onSwipeUpComplete(mActivity);
+        mRecentsAnimationWrapper.setCancelWithDeferredScreenshot(true);
 
         // Animate the first icon.
-        mRecentsView.animateUpRunningTaskIconScale();
+        mRecentsView.animateUpRunningTaskIconScale(mLiveTileOverlay.cancelIconAnimation());
         mRecentsView.setSwipeDownShouldLaunchApp(true);
 
         RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
index 6b3f028..777e592 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
@@ -17,12 +17,18 @@
 
 import android.annotation.TargetApi;
 import android.content.Context;
+import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ViewDebug;
+import android.view.WindowInsets;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
@@ -33,6 +39,9 @@
     private static final int MIN_SIZE = 10;
     private final RecentsActivity mActivity;
 
+    @ViewDebug.ExportedProperty(category = "launcher")
+    private final RectF mTouchExcludeRegion = new RectF();
+
     private final Point mLastKnownSize = new Point(MIN_SIZE, MIN_SIZE);
 
     public RecentsRootView(Context context, AttributeSet attrs) {
@@ -88,4 +97,29 @@
         mActivity.getDeviceProfile().updateInsets(mInsets);
         super.setInsets(mInsets);
     }
+
+    @Override
+    public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
+        if (Utilities.ATLEAST_Q) {
+            Insets gestureInsets = insets.getMandatorySystemGestureInsets();
+            mTouchExcludeRegion.set(gestureInsets.left, gestureInsets.top,
+                    gestureInsets.right, gestureInsets.bottom);
+        }
+        return super.dispatchApplyWindowInsets(insets);
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            float x = ev.getX();
+            float y = ev.getY();
+            if (y < mTouchExcludeRegion.top
+                    || x < mTouchExcludeRegion.left
+                    || x > (getWidth() - mTouchExcludeRegion.right)
+                    || y > (getHeight() - mTouchExcludeRegion.bottom)) {
+                return false;
+            }
+        }
+        return super.dispatchTouchEvent(ev);
+    }
 }
\ No newline at end of file
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/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
index fe5a675..e1a39c6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -149,6 +149,11 @@
     }
 
     public RectF applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params) {
+        return applyTransform(targetSet, params, true /* launcherOnTop */);
+    }
+
+    public RectF applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params,
+            boolean launcherOnTop) {
         if (params.currentRect == null) {
             RectF currentRect;
             mTmpRectF.set(mTargetRect);
@@ -189,7 +194,7 @@
                     }
                 }
                 alpha = mTaskAlphaCallback.apply(app, params.targetAlpha);
-            } else if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && launcherOnTop) {
                 crop = null;
                 layer = Integer.MAX_VALUE;
             }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
index 62f2183..94e704a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
@@ -38,9 +38,16 @@
  */
 public class RecentsAnimationListenerSet implements RecentsAnimationListener {
 
+    // The actual app surface is replaced by a screenshot upon recents animation cancelation when
+    // deferredWithScreenshot is true. Launcher takes the responsibility to clean up this screenshot
+    // after app transition is finished. This delay is introduced to cover the app transition
+    // period of time.
+    private final int TRANSITION_DELAY = 100;
+
     private final Set<SwipeAnimationListener> mListeners = new ArraySet<>();
     private final boolean mShouldMinimizeSplitScreen;
     private final Consumer<SwipeAnimationTargetSet> mOnFinishListener;
+    private RecentsAnimationControllerCompat mController;
 
     public RecentsAnimationListenerSet(boolean shouldMinimizeSplitScreen,
             Consumer<SwipeAnimationTargetSet> onFinishListener) {
@@ -64,6 +71,7 @@
     public final void onAnimationStart(RecentsAnimationControllerCompat controller,
             RemoteAnimationTargetCompat[] targets, Rect homeContentInsets,
             Rect minimizedHomeBounds) {
+        mController = controller;
         SwipeAnimationTargetSet targetSet = new SwipeAnimationTargetSet(controller, targets,
                 homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen,
                 mOnFinishListener);
@@ -75,12 +83,17 @@
     }
 
     @Override
-    public final void onAnimationCanceled() {
+    public final void onAnimationCanceled(boolean deferredWithScreenshot) {
         Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
             for (SwipeAnimationListener listener : getListeners()) {
                 listener.onRecentsAnimationCanceled();
             }
         });
+        // TODO: handle the transition better instead of simply using a transition delay.
+        if (deferredWithScreenshot) {
+            MAIN_THREAD_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(),
+                    TRANSITION_DELAY);
+        }
     }
 
     private SwipeAnimationListener[] getListeners() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
new file mode 100644
index 0000000..2edeb3a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.FloatProperty;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.FlingSpringAnim;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+
+/**
+ * Applies spring forces to animate from a starting rect to a target rect,
+ * while providing update callbacks to the caller.
+ */
+public class RectFSpringAnim {
+
+    /**
+     * Although the rect position animation takes an indefinite amount of time since it depends on
+     * the initial velocity and applied forces, scaling from the starting rect to the target rect
+     * can be done in parallel at a fixed duration. Update callbacks are sent based on the progress
+     * of this animation, while the end callback is sent after all animations finish.
+     */
+    private static final long RECT_SCALE_DURATION = 180;
+
+    private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_X =
+            new FloatPropertyCompat<RectFSpringAnim>("rectCenterXSpring") {
+                @Override
+                public float getValue(RectFSpringAnim anim) {
+                    return anim.mCurrentCenterX;
+                }
+
+                @Override
+                public void setValue(RectFSpringAnim anim, float currentCenterX) {
+                    anim.mCurrentCenterX = currentCenterX;
+                    anim.onUpdate();
+                }
+            };
+
+    private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_Y =
+            new FloatPropertyCompat<RectFSpringAnim>("rectCenterYSpring") {
+                @Override
+                public float getValue(RectFSpringAnim anim) {
+                    return anim.mCurrentCenterY;
+                }
+
+                @Override
+                public void setValue(RectFSpringAnim anim, float currentCenterY) {
+                    anim.mCurrentCenterY = currentCenterY;
+                    anim.onUpdate();
+                }
+            };
+
+    private static final FloatProperty<RectFSpringAnim> RECT_SCALE_PROGRESS =
+            new FloatProperty<RectFSpringAnim>("rectScaleProgress") {
+                @Override
+                public Float get(RectFSpringAnim anim) {
+                    return anim.mCurrentScaleProgress;
+                }
+
+                @Override
+                public void setValue(RectFSpringAnim anim, float currentScaleProgress) {
+                    anim.mCurrentScaleProgress = currentScaleProgress;
+                    anim.onUpdate();
+                }
+            };
+
+    private final RectF mStartRect;
+    private final RectF mTargetRect;
+    private final RectF mCurrentRect = new RectF();
+    private final List<OnUpdateListener> mOnUpdateListeners = new ArrayList<>();
+    private final List<Animator.AnimatorListener> mAnimatorListeners = new ArrayList<>();
+
+    private float mCurrentCenterX;
+    private float mCurrentCenterY;
+    private float mCurrentScaleProgress;
+    private boolean mRectXAnimEnded;
+    private boolean mRectYAnimEnded;
+    private boolean mRectScaleAnimEnded;
+
+    public RectFSpringAnim(RectF startRect, RectF targetRect) {
+        mStartRect = startRect;
+        mTargetRect = targetRect;
+        mCurrentCenterX = mStartRect.centerX();
+        mCurrentCenterY = mStartRect.centerY();
+    }
+
+    public void addOnUpdateListener(OnUpdateListener onUpdateListener) {
+        mOnUpdateListeners.add(onUpdateListener);
+    }
+
+    public void addAnimatorListener(Animator.AnimatorListener animatorListener) {
+        mAnimatorListeners.add(animatorListener);
+    }
+
+    public void start(PointF velocityPxPerMs) {
+        // Only tell caller that we ended if both x and y animations have ended.
+        OnAnimationEndListener onXEndListener = ((animation, canceled, centerX, velocityX) -> {
+            mRectXAnimEnded = true;
+            maybeOnEnd();
+        });
+        OnAnimationEndListener onYEndListener = ((animation, canceled, centerY, velocityY) -> {
+            mRectYAnimEnded = true;
+            maybeOnEnd();
+        });
+        FlingSpringAnim rectXAnim = new FlingSpringAnim(this, RECT_CENTER_X, mCurrentCenterX,
+                mTargetRect.centerX(), velocityPxPerMs.x * 1000, onXEndListener);
+        FlingSpringAnim rectYAnim = new FlingSpringAnim(this, RECT_CENTER_Y, mCurrentCenterY,
+                mTargetRect.centerY(), velocityPxPerMs.y * 1000, onYEndListener);
+
+        ValueAnimator rectScaleAnim = ObjectAnimator.ofPropertyValuesHolder(this,
+                PropertyValuesHolder.ofFloat(RECT_SCALE_PROGRESS, 1))
+                .setDuration(RECT_SCALE_DURATION);
+        rectScaleAnim.addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                mRectScaleAnimEnded = true;
+                maybeOnEnd();
+            }
+        });
+
+        rectXAnim.start();
+        rectYAnim.start();
+        rectScaleAnim.start();
+        for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
+            animatorListener.onAnimationStart(null);
+        }
+    }
+
+    private void onUpdate() {
+        if (!mOnUpdateListeners.isEmpty()) {
+            float currentWidth = Utilities.mapRange(mCurrentScaleProgress, mStartRect.width(),
+                    mTargetRect.width());
+            float currentHeight = Utilities.mapRange(mCurrentScaleProgress, mStartRect.height(),
+                    mTargetRect.height());
+            mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentCenterY - currentHeight / 2,
+                    mCurrentCenterX + currentWidth / 2, mCurrentCenterY + currentHeight / 2);
+            for (OnUpdateListener onUpdateListener : mOnUpdateListeners) {
+                onUpdateListener.onUpdate(mCurrentRect, mCurrentScaleProgress);
+            }
+        }
+    }
+
+    private void maybeOnEnd() {
+        if (mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded) {
+            for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
+                animatorListener.onAnimationEnd(null);
+            }
+        }
+    }
+
+    public interface OnUpdateListener {
+        void onUpdate(RectF currentRect, float progress);
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 0aa1beb..446fb39 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -23,7 +23,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherApps;
-import android.content.res.Resources;
+import android.content.pm.LauncherApps.AppUsageLimit;
 import android.icu.text.MeasureFormat;
 import android.icu.text.MeasureFormat.FormatWidth;
 import android.icu.util.Measure;
@@ -44,16 +44,16 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.systemui.shared.recents.model.Task;
 
-import java.lang.reflect.Method;
 import java.time.Duration;
 import java.util.Locale;
 
 public final class DigitalWellBeingToast extends LinearLayout {
     static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS);
     static final int MINUTE_MS = 60000;
+    private final LauncherApps mLauncherApps;
 
     public interface InitializeCallback {
-        void call(float saturation, String contentDescription);
+        void call(String contentDescription);
     }
 
     private static final String TAG = DigitalWellBeingToast.class.getSimpleName();
@@ -67,6 +67,7 @@
         setLayoutDirection(Utilities.isRtl(getResources()) ?
                 View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
         setOnClickListener((view) -> openAppUsageSettings());
+        mLauncherApps = context.getSystemService(LauncherApps.class);
     }
 
     @Override
@@ -82,52 +83,32 @@
 
         if (task.key.userId != UserHandle.myUserId()) {
             setVisibility(GONE);
-            callback.call(1, task.titleDescription);
+            callback.call(task.titleDescription);
             return;
         }
 
         Utilities.THREAD_POOL_EXECUTOR.execute(() -> {
-            long appUsageLimitTimeMs = -1;
-            long appRemainingTimeMs = -1;
+            final AppUsageLimit usageLimit = mLauncherApps.getAppUsageLimit(
+                    task.getTopComponent().getPackageName(),
+                    UserHandle.of(task.key.userId));
 
-            try {
-                final Method getAppUsageLimit = LauncherApps.class.getMethod(
-                        "getAppUsageLimit",
-                        String.class,
-                        UserHandle.class);
-                final Object usageLimit = getAppUsageLimit.invoke(
-                        getContext().getSystemService(LauncherApps.class),
-                        task.getTopComponent().getPackageName(),
-                        UserHandle.of(task.key.userId));
-
-                if (usageLimit != null) {
-                    final Class appUsageLimitClass = usageLimit.getClass();
-                    appUsageLimitTimeMs = (long) appUsageLimitClass.getMethod("getTotalUsageLimit").
-                            invoke(usageLimit);
-                    appRemainingTimeMs = (long) appUsageLimitClass.getMethod("getUsageRemaining").
-                            invoke(usageLimit);
-                }
-            } catch (Exception e) {
-                // Do nothing
-            }
-
-            final long appUsageLimitTimeMsFinal = appUsageLimitTimeMs;
-            final long appRemainingTimeMsFinal = appRemainingTimeMs;
+            final long appUsageLimitTimeMs =
+                    usageLimit != null ? usageLimit.getTotalUsageLimit() : -1;
+            final long appRemainingTimeMs =
+                    usageLimit != null ? usageLimit.getUsageRemaining() : -1;
 
             post(() -> {
-                if (appUsageLimitTimeMsFinal < 0) {
+                if (appUsageLimitTimeMs < 0 || appRemainingTimeMs < 0) {
                     setVisibility(GONE);
                 } else {
                     setVisibility(VISIBLE);
-                    mText.setText(getText(appRemainingTimeMsFinal));
-                    mImage.setImageResource(appRemainingTimeMsFinal > 0 ?
+                    mText.setText(getText(appRemainingTimeMs));
+                    mImage.setImageResource(appRemainingTimeMs > 0 ?
                             R.drawable.hourglass_top : R.drawable.hourglass_bottom);
                 }
 
-                callback.call(
-                        appUsageLimitTimeMsFinal >= 0 && appRemainingTimeMsFinal <= 0 ? 0 : 1,
-                        getContentDescriptionForTask(
-                                task, appUsageLimitTimeMsFinal, appRemainingTimeMsFinal));
+                callback.call(getContentDescriptionForTask(
+                        task, appUsageLimitTimeMs, appRemainingTimeMs));
             });
         });
     }
@@ -196,12 +177,9 @@
     }
 
     private String getText(long remainingTime) {
-        final Resources resources = getResources();
-        return (remainingTime <= 0) ?
-                resources.getString(R.string.app_in_grayscale) :
-                resources.getString(
-                        R.string.time_left_for_app,
-                        getRoundedUpToMinuteReadableDuration(remainingTime));
+        return getResources().getString(
+                R.string.time_left_for_app,
+                getRoundedUpToMinuteReadableDuration(remainingTime));
     }
 
     public void openAppUsageSettings() {
@@ -225,7 +203,7 @@
 
     private String getContentDescriptionForTask(
             Task task, long appUsageLimitTimeMs, long appRemainingTimeMs) {
-        return appUsageLimitTimeMs >= 0 ?
+        return appUsageLimitTimeMs >= 0 && appRemainingTimeMs >= 0 ?
                 getResources().getString(
                         R.string.task_contents_description_with_remaining_time,
                         task.titleDescription,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 97bce5e..329436b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -32,9 +32,7 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.util.AttributeSet;
-import android.util.FloatProperty;
 import android.view.View;
-import android.view.ViewDebug;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
@@ -44,7 +42,7 @@
 import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.launcher3.views.ScrimView;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.hints.ChipsContainer;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
@@ -56,27 +54,6 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class LauncherRecentsView extends RecentsView<Launcher> {
 
-    public static final FloatProperty<LauncherRecentsView> TRANSLATION_Y_FACTOR =
-            new FloatProperty<LauncherRecentsView>("translationYFactor") {
-
-                @Override
-                public void setValue(LauncherRecentsView view, float v) {
-                    view.setTranslationYFactor(v);
-                }
-
-                @Override
-                public Float get(LauncherRecentsView view) {
-                    return view.mTranslationYFactor;
-                }
-            };
-
-    /**
-     * A ratio representing the view's relative placement within its padded space. For example, 0
-     * is top aligned and 0.5 is centered vertically.
-     */
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private float mTranslationYFactor;
-
     private final TransformParams mTransformParams = new TransformParams();
     private ChipsContainer mChipsContainer;
 
@@ -95,18 +72,7 @@
 
     @Override
     public void startHome() {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            takeScreenshotAndFinishRecentsAnimation(true,
-                    () -> mActivity.getStateManager().goToState(NORMAL));
-        } else {
-            mActivity.getStateManager().goToState(NORMAL);
-        }
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        setTranslationYFactor(mTranslationYFactor);
+        mActivity.getStateManager().goToState(NORMAL);
     }
 
     @Override
@@ -117,9 +83,9 @@
         params.bottomMargin = mActivity.getDeviceProfile().chipHintBottomMarginPx;
     }
 
-    public void setTranslationYFactor(float translationFactor) {
-        mTranslationYFactor = translationFactor;
-        setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
+    @Override
+    public void setTranslationY(float translationY) {
+        super.setTranslationY(translationY);
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             LauncherState state = mActivity.getStateManager().getState();
             if (state == OVERVIEW || state == ALL_APPS) {
@@ -128,10 +94,6 @@
         }
     }
 
-    public float computeTranslationYForFactor(float translationYFactor) {
-        return translationYFactor * (getPaddingBottom() - getPaddingTop());
-    }
-
     public void setHintVisibility(float v) {
         if (mChipsContainer != null && ENABLE_HINTS_IN_OVERVIEW.get()) {
             mChipsContainer.setHintVisibility(v);
@@ -168,7 +130,7 @@
             ClipAnimationHelper helper) {
         AnimatorSet anim = super.createAdjacentPageAnimForTaskLaunch(tv, helper);
 
-        if (!OverviewInteractionState.INSTANCE.get(mActivity).isSwipeUpGestureEnabled()) {
+        if (!SysUINavigationMode.INSTANCE.get(mActivity).getMode().hasGestures) {
             // Hotseat doesn't move when opening recents with the button,
             // so don't animate it here either.
             return anim;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
index ab2b90f..a838797 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
@@ -1,5 +1,11 @@
 package com.android.quickstep.views;
 
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
@@ -9,16 +15,37 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
+import android.util.FloatProperty;
+
+import com.android.launcher3.anim.Interpolators;
 
 public class LiveTileOverlay extends Drawable {
 
+    private static final long ICON_ANIM_DURATION = 120;
+
+    private static final FloatProperty<LiveTileOverlay> PROGRESS =
+            new FloatProperty<LiveTileOverlay>("progress") {
+                @Override
+                public void setValue(LiveTileOverlay liveTileOverlay, float progress) {
+                    liveTileOverlay.setIconAnimationProgress(progress);
+                }
+
+                @Override
+                public Float get(LiveTileOverlay liveTileOverlay) {
+                    return liveTileOverlay.mIconAnimationProgress;
+                }
+            };
+
     private final Paint mPaint = new Paint();
 
     private Rect mBoundsRect = new Rect();
     private RectF mCurrentRect;
     private float mCornerRadius;
+    private Drawable mIcon;
+    private Animator mIconAnimator;
 
     private boolean mDrawEnabled = true;
+    private float mIconAnimationProgress = 0f;
 
     public LiveTileOverlay() {
         mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
@@ -35,6 +62,33 @@
         invalidateSelf();
     }
 
+    public void setIcon(Drawable icon) {
+        mIcon = icon;
+    }
+
+    public void startIconAnimation() {
+        if (mIconAnimator != null) {
+            mIconAnimator.cancel();
+        }
+        // This animator must match the icon part of {@link TaskView#FOCUS_TRANSITION} animation.
+        mIconAnimator = ObjectAnimator.ofFloat(this, PROGRESS, 1);
+        mIconAnimator.setDuration(ICON_ANIM_DURATION).setInterpolator(LINEAR);
+        mIconAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mIconAnimator = null;
+            }
+        });
+        mIconAnimator.start();
+    }
+
+    public float cancelIconAnimation() {
+        if (mIconAnimator != null) {
+            mIconAnimator.cancel();
+        }
+        return mIconAnimationProgress;
+    }
+
     public void setDrawEnabled(boolean drawEnabled) {
         if (mDrawEnabled != drawEnabled) {
             mDrawEnabled = drawEnabled;
@@ -46,6 +100,16 @@
     public void draw(Canvas canvas) {
         if (mCurrentRect != null && mDrawEnabled) {
             canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
+            if (mIcon != null && mIconAnimationProgress > 0f) {
+                canvas.save();
+                float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, 0f,
+                        1f).getInterpolation(mIconAnimationProgress);
+                canvas.translate(mCurrentRect.centerX() - mIcon.getBounds().width() / 2 * scale,
+                        mCurrentRect.top - mIcon.getBounds().height() / 2 * scale);
+                canvas.scale(scale, scale);
+                mIcon.draw(canvas);
+                canvas.restore();
+            }
         }
     }
 
@@ -59,4 +123,9 @@
     public int getOpacity() {
         return PixelFormat.TRANSLUCENT;
     }
+
+    private void setIconAnimationProgress(float progress) {
+        mIconAnimationProgress = progress;
+        invalidateSelf();
+    }
 }
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 3e0e8ae..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
@@ -22,13 +22,14 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.uioverrides.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
-import static com.android.quickstep.util.ClipAnimationHelper.TransformParams;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+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.quickstep.TouchInteractionService.EDGE_NAV_BAR;
+import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
+import static com.android.quickstep.util.ClipAnimationHelper.TransformParams;
+
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
@@ -81,6 +82,7 @@
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.anim.SpringObjectAnimator;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.OverScroller;
@@ -94,16 +96,15 @@
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.SwipeAnimationTargetSet;
 import com.android.quickstep.util.TaskViewDrawable;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.LauncherEventUtil;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.WindowCallbacksCompat;
 
 import java.util.ArrayList;
 import java.util.function.Consumer;
@@ -167,6 +168,8 @@
 
     private final ViewPool<TaskView> mTaskViewPool;
 
+    private boolean mDwbToastShown;
+
     /**
      * TODO: Call reloadIdNeeded in onTaskStackChanged.
      */
@@ -278,6 +281,7 @@
     private final int mEmptyMessagePadding;
     private boolean mShowEmptyMessage;
     private Layout mEmptyTextLayout;
+    private LiveTileOverlay mLiveTileOverlay;
 
     private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
             (inMultiWindowMode) -> {
@@ -426,6 +430,16 @@
     public void setOverviewStateEnabled(boolean enabled) {
         mOverviewStateEnabled = enabled;
         updateTaskStackListenerState();
+        if (!enabled) mDwbToastShown = false;
+    }
+
+    public void onDigitalWellbeingToastShown() {
+        if (!mDwbToastShown) {
+            mDwbToastShown = true;
+            mActivity.getUserEventDispatcher().logActionTip(
+                    LauncherEventUtil.VISIBLE,
+                    LauncherLogProto.TipType.DWB_TOAST);
+        }
     }
 
     @Override
@@ -855,10 +869,15 @@
     }
 
     public void animateUpRunningTaskIconScale() {
+        animateUpRunningTaskIconScale(0);
+    }
+
+    public void animateUpRunningTaskIconScale(float startProgress) {
         mRunningTaskIconScaledDown = false;
         TaskView firstTask = getRunningTaskView();
         if (firstTask != null) {
             firstTask.animateIconScaleAndDimIntoView();
+            firstTask.setIconScaleAnimStartProgress(startProgress);
         }
     }
 
@@ -1567,6 +1586,16 @@
         mClipAnimationHelper = clipAnimationHelper;
     }
 
+    public void setLiveTileOverlay(LiveTileOverlay liveTileOverlay) {
+        mLiveTileOverlay = liveTileOverlay;
+    }
+
+    public void updateLiveTileIcon(Drawable icon) {
+        if (mLiveTileOverlay != null) {
+            mLiveTileOverlay.setIcon(icon);
+        }
+    }
+
     public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
         if (mRecentsAnimationWrapper == null) {
             if (onFinishComplete != null) {
@@ -1577,50 +1606,4 @@
 
         mRecentsAnimationWrapper.finish(toRecents, onFinishComplete);
     }
-
-    public void takeScreenshotAndFinishRecentsAnimation(boolean toRecents,
-            Runnable onFinishComplete) {
-        if (mRecentsAnimationWrapper == null || getRunningTaskView() == null) {
-            if (onFinishComplete != null) {
-                onFinishComplete.run();
-            }
-            return;
-        }
-
-        SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
-        if (controller != null) {
-            // Update the screenshot of the task
-            ThumbnailData taskSnapshot = controller.screenshotTask(mRunningTaskId);
-            TaskView taskView = updateThumbnail(mRunningTaskId, taskSnapshot);
-            if (taskView != null) {
-                taskView.setShowScreenshot(true);
-                // Defer finishing the animation until the next launcher frame with the
-                // new thumbnail
-                new WindowCallbacksCompat(taskView) {
-
-                    // The number of frames to defer until we actually finish the animation
-                    private int mDeferFrameCount = 2;
-
-                    @Override
-                    public void onPostDraw(Canvas canvas) {
-                        if (mDeferFrameCount > 0) {
-                            mDeferFrameCount--;
-                            // Workaround, detach and reattach to invalidate the root node for
-                            // another draw
-                            detach();
-                            attach();
-                            taskView.invalidate();
-                            return;
-                        }
-
-                        detach();
-                        mRecentsAnimationWrapper.finish(toRecents, () -> {
-                            onFinishComplete.run();
-                            mRunningTaskId = -1;
-                        });
-                    }
-                }.attach();
-            }
-        }
-    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
index 682152e..d15a392 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
@@ -208,13 +208,7 @@
                 R.layout.task_view_menu_option, this, false);
         menuOption.setIconAndLabelFor(
                 menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            menuOptionView.setOnClickListener(
-                    view -> mTaskView.getRecentsView().takeScreenshotAndFinishRecentsAnimation(true,
-                            () -> onClickListener.onClick(view)));
-        } else {
-            menuOptionView.setOnClickListener(onClickListener);
-        }
+        menuOptionView.setOnClickListener(onClickListener);
         mOptionLayout.addView(menuOptionView);
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 90604ef..a9f6311 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -46,6 +46,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
+import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
 import com.android.systemui.shared.recents.model.Task;
@@ -81,6 +82,7 @@
     private final Paint mBackgroundPaint = new Paint();
     private final Paint mClearPaint = new Paint();
     private final Paint mDimmingPaintAfterClearing = new Paint();
+    private final float mWindowCornerRadius;
 
     private final Matrix mMatrix = new Matrix();
 
@@ -114,6 +116,7 @@
         mDimmingPaintAfterClearing.setColor(Color.BLACK);
         mActivity = BaseActivity.fromContext(context);
         mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText);
+        mWindowCornerRadius = RecentsModel.INSTANCE.get(context).getWindowCornerRadius();
     }
 
     public void bind(Task task) {
@@ -196,19 +199,22 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        float fullscreenProgress = ((TaskView) getParent()).getFullscreenProgress();
+        TaskView taskView = (TaskView) getParent();
+        float fullscreenProgress = taskView.getFullscreenProgress();
         if (mIsRotated) {
             // Don't show insets in the wrong orientation.
             fullscreenProgress = 0;
         }
         if (fullscreenProgress > 0) {
             // Draw the insets if we're being drawn fullscreen (we do this for quick switch).
+            float cornerRadius = Utilities.mapRange(fullscreenProgress, mCornerRadius,
+                    mWindowCornerRadius);
             drawOnCanvas(canvas,
                     -mScaledInsets.left * fullscreenProgress,
                     -mScaledInsets.top * fullscreenProgress,
                     getMeasuredWidth() + mScaledInsets.right * fullscreenProgress,
                     getMeasuredHeight() + mScaledInsets.bottom * fullscreenProgress,
-                    mCornerRadius);
+                    cornerRadius / taskView.getRecentsView().getScaleX());
         } else {
             drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index e5b1a79..747c480 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -155,7 +155,8 @@
     private float mZoomScale;
     private float mFullscreenProgress;
 
-    private Animator mIconAndDimAnimator;
+    private ObjectAnimator mIconAndDimAnimator;
+    private float mIconScaleAnimStartProgress = 0;
     private float mFocusTransitionProgress = 1;
 
     private boolean mShowScreenshot;
@@ -248,7 +249,11 @@
     }
 
     public void launchTask(boolean animate) {
-        launchTask(animate, (result) -> {
+        launchTask(animate, false /* freezeTaskList */);
+    }
+
+    public void launchTask(boolean animate, boolean freezeTaskList) {
+        launchTask(animate, freezeTaskList, (result) -> {
             if (!result) {
                 notifyTaskLaunchFailed(TAG);
             }
@@ -257,26 +262,33 @@
 
     public void launchTask(boolean animate, Consumer<Boolean> resultCallback,
             Handler resultCallbackHandler) {
+        launchTask(animate, false /* freezeTaskList */, resultCallback, resultCallbackHandler);
+    }
+
+    public void launchTask(boolean animate, boolean freezeTaskList, Consumer<Boolean> resultCallback,
+            Handler resultCallbackHandler) {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             if (isRunningTask()) {
-                getRecentsView().finishRecentsAnimation(false,
+                getRecentsView().finishRecentsAnimation(false /* toRecents */,
                         () -> resultCallbackHandler.post(() -> resultCallback.accept(true)));
             } else {
-                getRecentsView().takeScreenshotAndFinishRecentsAnimation(true,
-                        () -> launchTaskInternal(animate, resultCallback, resultCallbackHandler));
+                launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler);
             }
         } else {
-            launchTaskInternal(animate, resultCallback, resultCallbackHandler);
+            launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler);
         }
     }
 
-    private void launchTaskInternal(boolean animate, Consumer<Boolean> resultCallback,
-            Handler resultCallbackHandler) {
+    private void launchTaskInternal(boolean animate, boolean freezeTaskList,
+            Consumer<Boolean> resultCallback, Handler resultCallbackHandler) {
         if (mTask != null) {
             final ActivityOptions opts;
             if (animate) {
                 opts = ((BaseDraggingActivity) fromContext(getContext()))
                         .getActivityLaunchOptions(this);
+                if (freezeTaskList) {
+                    ActivityOptionsCompat.setFreezeRecentTasksList(opts);
+                }
                 ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key,
                         opts, resultCallback, resultCallbackHandler);
             } else {
@@ -287,6 +299,9 @@
                         resultCallbackHandler.post(() -> resultCallback.accept(true));
                     }
                 }, resultCallbackHandler);
+                if (freezeTaskList) {
+                    ActivityOptionsCompat.setFreezeRecentTasksList(opts);
+                }
                 ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key,
                         opts, (success) -> {
                             if (resultCallback != null && !success) {
@@ -316,11 +331,16 @@
             mIconLoadRequest = iconCache.updateIconInBackground(mTask,
                     (task) -> {
                         setIcon(task.icon);
+                        if (isRunningTask()) {
+                            getRecentsView().updateLiveTileIcon(task.icon);
+                        }
                         mDigitalWellBeingToast.initialize(
                                 mTask,
-                                (saturation, contentDescription) -> {
+                                contentDescription -> {
                                     setContentDescription(contentDescription);
-                                    mSnapshotView.setSaturation(saturation);
+                                    if (mDigitalWellBeingToast.getVisibility() == VISIBLE) {
+                                        getRecentsView().onDigitalWellbeingToastShown();
+                                    }
                                 });
                     });
         } else {
@@ -379,11 +399,16 @@
         mIconView.setScaleY(scale);
     }
 
+    public void setIconScaleAnimStartProgress(float startProgress) {
+        mIconScaleAnimStartProgress = startProgress;
+    }
+
     public void animateIconScaleAndDimIntoView() {
         if (mIconAndDimAnimator != null) {
             mIconAndDimAnimator.cancel();
         }
         mIconAndDimAnimator = ObjectAnimator.ofFloat(this, FOCUS_TRANSITION, 1);
+        mIconAndDimAnimator.setCurrentFraction(mIconScaleAnimStartProgress);
         mIconAndDimAnimator.setDuration(DIM_ANIM_DURATION).setInterpolator(LINEAR);
         mIconAndDimAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -406,6 +431,7 @@
     }
 
     private void resetViewTransforms() {
+        setCurveScale(1);
         setZoomScale(1);
         setTranslationX(0f);
         setTranslationY(0f);
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index 336eef8..88954b2 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Onlangse programme"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minuut"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Program in grysskaal"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> oor vandag"</string>
 </resources>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index 8a565e4..34fb3be 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"የቅርብ ጊዜ መተግበሪያዎች"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>፣ <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 ደቂቃ"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"መተግበሪያ በግራጫ"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"ዛሬ <xliff:g id="TIME">%1$s</xliff:g> ቀርቷል"</string>
 </resources>
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index d96397c..ebdcf73 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"التطبيقات التي تمّ استخدامها مؤخرًا"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>، <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"أقل من دقيقة"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"التطبيق بالتدرّج الرمادي"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"يتبقى اليوم <xliff:g id="TIME">%1$s</xliff:g>."</string>
 </resources>
diff --git a/quickstep/res/values-as/strings.xml b/quickstep/res/values-as/strings.xml
index 7216b4f..a33973b 100644
--- a/quickstep/res/values-as/strings.xml
+++ b/quickstep/res/values-as/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"শেহতীয়া এপসমূহ"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; ১ মিনিট"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"এপ্ গ্ৰে’স্কে’লত আছে"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"আজি <xliff:g id="TIME">%1$s</xliff:g> বাকী আছ"</string>
 </resources>
diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml
index c07098e..02312f4 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Son tətbiqlər"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 dəq"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Tətbiq ağ-qara rejimdədir"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Bu gün <xliff:g id="TIME">%1$s</xliff:g> qaldı"</string>
 </resources>
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index 6fea458..baab4a1 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nedavne aplikacije"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Aplikacija je u sivilu"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Još <xliff:g id="TIME">%1$s</xliff:g> danas"</string>
 </resources>
diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml
index 8147031..b28f377 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Нядаўнія праграмы"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 хв"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Праграма ў шэрым рэжыме"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Сёння засталося <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index 00ba582..0475c0d 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Скорошни приложения"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 мин"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Прилож. е в сивата скала"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Оставащо време днес: <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index 9a30adf..e6764e0 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"সম্প্রতি ব্যবহৃত অ্যাপ"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; ১ মি."</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"অ্যাপে গ্রেস্কেল"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"আজকে <xliff:g id="TIME">%1$s</xliff:g> বাকি আছে"</string>
 </resources>
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index e940616..77b4c46 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nedavne aplikacije"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Apl. je u nijansi sive"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Preostalo vrijeme: <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml
index d120a2c..484f445 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplicacions recents"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>; <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minut"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"App en escala de grisos"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"temps restant avui: <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index 435edd9..a698d49 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Poslední aplikace"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minuta"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Aplikace se zešednutím"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"dnes zbývá: <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index 3db6ab3..b3e8524 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Seneste apps"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Nedtonet app"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> tilbage i dag"</string>
 </resources>
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index 48604f1..10e4fd7 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Zuletzt aktive Apps"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"App in Graustufen"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Heute noch <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index fdc7464..6ef1e94 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Πρόσφατες εφαρμογές"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 λ."</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Εφαρ. σε κλίμακα του γκρι"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Απομένουν <xliff:g id="TIME">%1$s</xliff:g> σήμερα"</string>
 </resources>
diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index d625b60..d640b63 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recent apps"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minute"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"App in grayscale"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> left today"</string>
 </resources>
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index d625b60..d640b63 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recent apps"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minute"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"App in grayscale"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> left today"</string>
 </resources>
diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index d625b60..d640b63 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recent apps"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minute"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"App in grayscale"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> left today"</string>
 </resources>
diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index a9d303a..c93e8fc 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Apps recientes"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minuto"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"App en escala de grises"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Tiempo restante: <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index 291512a..3a588e5 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplicaciones recientes"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt;1 minuto"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"App en escala de grises"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"tiempo restante: <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml
index 96c27e7..7032765 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Hiljutised rakendused"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minut"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Rakendus halltoonides"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Tääna jäänud <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index 15b682c..66e08b9 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Azken aplikazioak"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Aplikazioa grisen eskalan"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> gelditzen dira gaur"</string>
 </resources>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index 0a482f8..112d04c 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"برنامه‌های اخیر"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>، <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; ۱ دقیقه"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"برنامه به‌صورت سیاه‌وسفید"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> باقی‌مانده برای امروز"</string>
 </resources>
diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index 69686bb..6a0a359 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Viimeisimmät sovellukset"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Harmaasävyinen sovellus"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> jäljellä tänään"</string>
 </resources>
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index 068fd08..248a5da 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Applications récentes"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> : <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Application en nuances de gris"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Il reste <xliff:g id="TIME">%1$s</xliff:g> aujourd\'hui"</string>
 </resources>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index ea5b0dc..338d9ba 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Applications récentes"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Appli en nuances de gris"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Encore <xliff:g id="TIME">%1$s</xliff:g> aujourd\'hui"</string>
 </resources>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index a6995d0..d6ddf3c 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplicacións recentes"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt;1 min"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"App en escala de grises"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Tempo restante hoxe <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml
index 72b70b0..4493e3b 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"તાજેતરની ઍપ"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 મિનિટ"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"ગ્રેસ્કેલમાં ઍપ"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> આજે બાકી"</string>
 </resources>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index 4d15044..3c53cce 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"हाल ही में इस्तेमाल किए गए एेप्लिकेशन"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt;1 मिनट"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"ग्रेस्केल में ऐप्लिकेशन"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"आज <xliff:g id="TIME">%1$s</xliff:g> और चलेगा"</string>
 </resources>
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index 1cec1fb..103710f 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nedavne aplikacije"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Apl. u nijansama sive"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Još <xliff:g id="TIME">%1$s</xliff:g> danas"</string>
 </resources>
diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index 112c481..22b2380 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Legutóbbi alkalmazások"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 perc"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Szürkeárnyalat aktív"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Ma még <xliff:g id="TIME">%1$s</xliff:g> van hátra"</string>
 </resources>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index f39db5a..910265a 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Վերջին օգտագործած հավելվածները"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 ր"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Մոխրագույն երանգներ"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Այսօր մնացել է՝ <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml
index 48892c9..a7749df 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplikasi baru-baru ini"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 menit"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Aplikasi hitam putih"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> tersisa hari ini"</string>
 </resources>
diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml
index c3ec96c..ba0c672 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nýleg forrit"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 mín."</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Forrit í grátónum"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> eftir í dag"</string>
 </resources>
diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml
index d7c57ee..746443e 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"App recenti"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"App in scala di grigi"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Rimanente oggi: <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index 0b745cd..96a8adc 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"אפליקציות אחרונות"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"‏&lt; דקה"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"האפליקציה בגווני אפור"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"הזמן שנותר להיום: <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml
index c76c4a6..5484ae1 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"最近使ったアプリ"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>、<xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"1 分未満"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"グレースケールのアプリ"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"今日はあと <xliff:g id="TIME">%1$s</xliff:g>です"</string>
 </resources>
diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml
index 700522f..9218fb8 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ბოლოდროინდელი აპები"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 წუთი"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"აპი ნაცრისფერ ტონებშია"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"დღეს დარჩენილია <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index 96f8956..0766150 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Соңғы пайдаланылған қолданбалар"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 мин"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Сұр түстегі қолданба"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Бүгін <xliff:g id="TIME">%1$s</xliff:g> қалды"</string>
 </resources>
diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml
index 323efdf..8737ae8 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"កម្មវិធី​ថ្មីៗ"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 នាទី"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"កម្មវិធី​ស្ថិតក្នុង​មាត្រដ្ឋាន​ពណ៌ប្រផេះ"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"នៅសល់ <xliff:g id="TIME">%1$s</xliff:g> ទៀត​នៅថ្ងៃនេះ"</string>
 </resources>
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index b5e5738..099957c 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ಇತ್ತೀಚಿನ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 ನಿ"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"ಗ್ರೇಸ್ಕೇಲ್‌ನಲ್ಲಿ ಆ್ಯಪ್‌"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"ಇಂದು <xliff:g id="TIME">%1$s</xliff:g> ಸಮಯ ಉಳಿದಿದೆ"</string>
 </resources>
diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index 5daa508..9543e79 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"최근 앱"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1분"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"앱이 그레이 스케일로 전환됨"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"오늘 <xliff:g id="TIME">%1$s</xliff:g> 남음"</string>
 </resources>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index be960ec..d1d2d20 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Акыркы колдонмолор"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 мүнөт"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Колдонмо жигерсиз"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Бүгүн <xliff:g id="TIME">%1$s</xliff:g> мүнөт калды"</string>
 </resources>
diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index ef70ee7..aba4156 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ແອັບຫຼ້າສຸດ"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 ນາທີ"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"ແອັບເປັນສີຂາວດຳ"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"ເຫຼືອ <xliff:g id="TIME">%1$s</xliff:g> ມື້ນີ້"</string>
 </resources>
diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index f10866b..933b3f0 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Naujausios programos"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min."</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Programa su pilkumo tonu"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Šiandien liko: <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml
index 9ae124a..1e2ed00 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Pēdējās izmantotās lietotnes"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt;1 minūte"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Lietotne pelēktoņos"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Šodien atlicis: <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index 688cb93..7a6c094 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Неодамнешни апликации"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 минута"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Апликација во сиви тонови"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Уште <xliff:g id="TIME">%1$s</xliff:g> за денес"</string>
 </resources>
diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index e66116c..b5eac1d 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"സമീപകാല ആപ്പുകൾ"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 മിനിറ്റ്"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"ആപ്പ് ഗ്രേസ്‌കെയിലിൽ"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"ഇന്ന് <xliff:g id="TIME">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
 </resources>
diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index 5c57237..a105ef1 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Саяхны аппууд"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 минут"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Саарал өнгөтэй болсон апп"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Өнөөдөр <xliff:g id="TIME">%1$s</xliff:g> үлдсэн"</string>
 </resources>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index f4f2904..bf725e3 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"अलीकडील अॅप्स"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"१मिहून कमी"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"ग्रेस्केल मधील अ‍ॅप"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"आज <xliff:g id="TIME">%1$s</xliff:g>शिल्लक आहे"</string>
 </resources>
diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml
index d8561eb..2e3f236 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Apl terbaharu"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minit"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Apl dalam skala kelabu"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> lagi hari ini"</string>
 </resources>
diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml
index f609c89..7b93125 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"လတ်တလောသုံး အက်ပ်များ"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>၊ <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; ၁ မိနစ်"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"အဖြူအမည်းနှင့်ပြသော အက်ပ်"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"ယနေ့ <xliff:g id="TIME">%1$s</xliff:g> ခု ကျန်သည်"</string>
 </resources>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index cff48d8..74f43d2 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nylige apper"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minutt"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"App i gråtoner"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> gjenstår i dag"</string>
 </resources>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index cc450c4..6053def 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"हालसालैका अनुप्रयोगहरू"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; १ मिनेट"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"ग्रेस्केल पारिएको एप"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"आज: <xliff:g id="TIME">%1$s</xliff:g> बाँकी"</string>
 </resources>
diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml
index 4a3607d..4e3a34c 100644
--- a/quickstep/res/values-nl/strings.xml
+++ b/quickstep/res/values-nl/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recente apps"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minuut"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"App in grijstinten"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Nog <xliff:g id="TIME">%1$s</xliff:g> vandaag"</string>
 </resources>
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index 4dd5920..bd88671 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ସାମ୍ପ୍ରତିକ ଆପ୍‌"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 ମିନିଟ୍"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"ଗ୍ରେସ୍କେଲ୍‌ରେ ଥିବା ଆପ୍"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"ଆଜି <xliff:g id="TIME">%1$s</xliff:g> ବାକି ଅଛି"</string>
 </resources>
diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index 078d8a0..5aeeae6 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ਹਾਲੀਆ ਐਪਾਂ"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 ਮਿੰਟ"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"ਐਪ ਗ੍ਰੇਸਕੇਲ ਵਿੱਚ ਹੈ"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"ਅੱਜ <xliff:g id="TIME">%1$s</xliff:g> ਬਾਕੀ"</string>
 </resources>
diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index aa7920c..210edcf 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Ostatnie aplikacje"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&gt; 1 min"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Aplikacja wyszarzona"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Na dziś zostało <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index 22dc654..8a129d5 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplicações recentes"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minuto"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Aplic. na Escala de cinz."</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Resta(m) <xliff:g id="TIME">%1$s</xliff:g> hoje."</string>
 </resources>
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index b838c96..e5380d5 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Apps recentes"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"App em escala de cinza"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> restante(s) hoje"</string>
 </resources>
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index a33524e..54452a0 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplicații recente"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minut"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Aplicație în tonuri de gri"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Au mai rămas <xliff:g id="TIME">%1$s</xliff:g> astăzi"</string>
 </resources>
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index 2df583c..8b2016a 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Недавние приложения"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>: <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 мин."</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"В режиме оттенков серого"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Осталось сегодня: <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml
index 010a56e..2163390 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"මෑත යෙදුම්"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 විනාඩියක්"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"අලු පැහැ යෙදුම"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"අද <xliff:g id="TIME">%1$s</xliff:g>ක් ඉතුරුයි"</string>
 </resources>
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index c73a597..12983db 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nedávne aplikácie"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"Menej ako 1 minúta"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Aplikácia je odfarbená"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Dnes ešte zostáva: <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index a7f0704..a940f2b 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nedavne aplikacije"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Črnobela aplikacija"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Danes je ostalo še <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index b70f142..e41bcb5 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplikacionet e fundit"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minutë"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Apl. në shkallën e grisë"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> të mbetura sot"</string>
 </resources>
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index 2cf43f5..8f26c66 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Недавне апликације"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 мин"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Апликација је у сивилу"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Још <xliff:g id="TIME">%1$s</xliff:g> данас"</string>
 </resources>
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index bec4893..70740e5 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Senaste apparna"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"App visas i gråskala"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> kvar i dag"</string>
 </resources>
diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml
index 53d095d..c646b6a 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Programu za hivi karibuni"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; dak 1"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Programu katika kijivu"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Umebakisha <xliff:g id="TIME">%1$s</xliff:g> leo"</string>
 </resources>
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index ecc8f6c..19bfaa9 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"சமீபத்திய ஆப்ஸ்"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 நி"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"கிரேஸ்கேலில் உள்ள ஆப்ஸ்"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"இன்று <xliff:g id="TIME">%1$s</xliff:g> மீதமுள்ளது"</string>
 </resources>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index b6eeb40..071755a 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ఇటీవలి యాప్‌లు"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 నిమిషం"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"యాప్‌ గ్రేస్కేల్లో ఉంది"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"నేటికి <xliff:g id="TIME">%1$s</xliff:g> మిగిలి ఉంది"</string>
 </resources>
diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml
index c2e173a..c0e78ce 100644
--- a/quickstep/res/values-th/strings.xml
+++ b/quickstep/res/values-th/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"แอปล่าสุด"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt;1 นาที"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"แอปที่เป็นโทนสีเทา"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"วันนี้เหลืออีก <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml
index 5588d03..76a0b25 100644
--- a/quickstep/res/values-tl/strings.xml
+++ b/quickstep/res/values-tl/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Mga kamakailang app"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"App na grayscale"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> na lang ngayon"</string>
 </resources>
diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index 40e0d89..8b59c7b 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Son uygulamalar"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 dk."</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Uygulama gri tonlamada"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Bugün <xliff:g id="TIME">%1$s</xliff:g> kaldı"</string>
 </resources>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index 104c82c..39c3848 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Нещодавні додатки"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 хв"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Додаток у відтінку сірого"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Сьогодні залишилося <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml
index 481bda0..4fd9e69 100644
--- a/quickstep/res/values-ur/strings.xml
+++ b/quickstep/res/values-ur/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"حالیہ ایپس"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>،<xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"‏&lt; 1 منٹ"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"ایپ خاکستری کیا گیا"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"آج <xliff:g id="TIME">%1$s</xliff:g> بچا ہے"</string>
 </resources>
diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index 7e9f955..466d79e 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Yaqinda ishlatilgan ilovalar"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 daqiqa"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Ilova kulrang rejimida"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Bugun <xliff:g id="TIME">%1$s</xliff:g> qoldi"</string>
 </resources>
diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index 3d42063..842b22b 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Ứng dụng gần đây"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 phút"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Ứng dụng có thang màu xám"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Hôm nay còn <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index 4a13d14..951489f 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"最近用过的应用"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>(<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"不到 1 分钟"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"灰度模式下的应用"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"今天还可使用 <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index 02d9b8e..361623d 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"最近使用的應用程式"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>,<xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"少於 1 分鐘"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"切換至灰階螢幕的應用程式"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"今天剩餘時間:<xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index 20181fd..6938d3e 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"最近使用的應用程式"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 分鐘"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"切換為灰階模式的應用程式"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"今天還能使用 <xliff:g id="TIME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index abbbc95..98f7b02 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -30,6 +30,5 @@
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Izinhlelo zokusebenza zakamuva"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 iminithi"</string>
-    <string name="app_in_grayscale" msgid="1108706002158384887">"Uhlelo lokusebenza nge-grayscale"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> esele namhlanje"</string>
 </resources>
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 3fbfcdd..a966698 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -26,4 +26,8 @@
          determines how many thumbnails will be fetched in the background. -->
     <integer name="recentsThumbnailCacheSize">3</integer>
     <integer name="recentsIconCacheSize">12</integer>
+
+    <!-- Assistant Gesture -->
+    <integer name="assistant_gesture_min_time_threshold">200</integer>
+    <integer name="assistant_gesture_corner_deg_threshold">30</integer>
 </resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index f5e5dd3..97f2de7 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -24,7 +24,7 @@
     <dimen name="task_corner_radius_small">2dp</dimen>
     <dimen name="recents_page_spacing">10dp</dimen>
     <dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
-    <dimen name="quickscrub_adjacent_visible_width">20dp</dimen>
+    <dimen name="overview_peek_distance">32dp</dimen>
 
     <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
              loading full resolution screenshots. -->
@@ -66,6 +66,6 @@
     <dimen name="shelf_surface_offset">24dp</dimen>
 
     <!-- Assistant Gestures -->
-    <dimen name="gestures_assistant_width">70dp</dimen>
-    <dimen name="gestures_assistant_threshold">200dp</dimen>
+    <dimen name="gestures_assistant_size">28dp</dimen>
+    <dimen name="gestures_assistant_drag_threshold">70dp</dimen>
 </resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index f5e8fa8..81565a5 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -55,10 +55,6 @@
      escaped form of '<'). [CHAR LIMIT=15] -->
     <string name="shorter_duration_less_than_one_minute">&lt; 1 minute</string>
 
-    <!-- Annotation shown on an app card in Recents, telling that the app was switched to a
-    grayscale because it ran over its time limit [CHAR LIMIT=25] -->
-    <string name="app_in_grayscale">App in grayscale</string>
-
     <!-- Annotation shown on an app card in Recents, telling that the app has a usage limit set by
     the user, and a given time is left for it today [CHAR LIMIT=22] -->
     <string name="time_left_for_app"><xliff:g id="time" example="7 minutes">%1$s</xliff:g> left today</string>
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index f8b167b..f77bd65 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -20,15 +20,16 @@
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
 import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
+import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
+import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
@@ -45,6 +46,7 @@
 import android.content.res.Resources;
 import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.CancellationSignal;
@@ -52,10 +54,8 @@
 import android.os.Looper;
 import android.util.Pair;
 import android.view.View;
-import android.view.ViewGroup;
 
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
-import com.android.launcher3.InsettableFrameLayout.LayoutParams;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -103,13 +103,18 @@
     private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
             "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
 
-    private static final int APP_LAUNCH_DURATION = 500;
+    private static final long APP_LAUNCH_DURATION = 500;
     // Use a shorter duration for x or y translation to create a curve effect
-    private static final int APP_LAUNCH_CURVED_DURATION = APP_LAUNCH_DURATION / 2;
+    private static final long APP_LAUNCH_CURVED_DURATION = APP_LAUNCH_DURATION / 2;
+    private static final long APP_LAUNCH_ALPHA_DURATION = 50;
+
     // We scale the durations for the downward app launch animations (minus the scale animation).
     private static final float APP_LAUNCH_DOWN_DUR_SCALE_FACTOR = 0.8f;
-    private static final int APP_LAUNCH_ALPHA_START_DELAY = 32;
-    private static final int APP_LAUNCH_ALPHA_DURATION = 50;
+    private static final long APP_LAUNCH_DOWN_DURATION =
+            (long) (APP_LAUNCH_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
+    private static final long APP_LAUNCH_DOWN_CURVED_DURATION = APP_LAUNCH_DOWN_DURATION / 2;
+    private static final long APP_LAUNCH_ALPHA_DOWN_DURATION =
+            (long) (APP_LAUNCH_ALPHA_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
 
     public static final int RECENTS_LAUNCH_DURATION = 336;
     private static final int LAUNCHER_RESUME_START_DELAY = 100;
@@ -207,11 +212,11 @@
 
             // Note that this duration is a guess as we do not know if the animation will be a
             // recents launch or not for sure until we know the opening app targets.
-            int duration = fromRecents
+            long duration = fromRecents
                     ? RECENTS_LAUNCH_DURATION
                     : APP_LAUNCH_DURATION;
 
-            int statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
+            long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
                     - STATUS_BAR_TRANSITION_PRE_DELAY;
             return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
                     runner, duration, statusBarTransitionDelay));
@@ -266,7 +271,8 @@
             }
             if (!isAllOpeningTargetTrs) break;
         }
-        playIconAnimators(anim, v, windowTargetBounds, !isAllOpeningTargetTrs);
+        anim.play(getOpeningWindowAnimators(v, targets, windowTargetBounds,
+                !isAllOpeningTargetTrs));
         if (launcherClosing) {
             Pair<AnimatorSet, Runnable> launcherContentAnimator =
                     getLauncherContentAnimator(true /* isAppOpening */,
@@ -279,7 +285,6 @@
                 }
             });
         }
-        anim.play(getOpeningWindowAnimators(v, targets, windowTargetBounds));
     }
 
     /**
@@ -398,124 +403,13 @@
             float[] alphas, float[] trans);
 
     /**
-     * Animators for the "floating view" of the view used to launch the target.
-     */
-    private void playIconAnimators(AnimatorSet appOpenAnimator, View v, Rect windowTargetBounds,
-            boolean toggleVisibility) {
-        final boolean isBubbleTextView = v instanceof BubbleTextView;
-        if (mFloatingView != null) {
-            mFloatingView.setTranslationX(0);
-            mFloatingView.setTranslationY(0);
-            mFloatingView.setScaleX(1);
-            mFloatingView.setScaleY(1);
-            mFloatingView.setAlpha(1);
-            mFloatingView.setBackground(null);
-        }
-        Rect rect = new Rect();
-        mFloatingView = FloatingIconView.getFloatingIconView(mLauncher, v, toggleVisibility,
-                true /* useDrawableAsIs */, -1 /* aspectRatio */, rect, mFloatingView);
-
-        int viewLocationStart = mIsRtl ? windowTargetBounds.width() - rect.right : rect.left;
-        LayoutParams lp = (LayoutParams) mFloatingView.getLayoutParams();
-        // Special RTL logic is needed to handle the window target bounds.
-        lp.leftMargin = mIsRtl ? windowTargetBounds.width() - rect.right : rect.left;
-        mFloatingView.setLayoutParams(lp);
-
-        int[] dragLayerBounds = new int[2];
-        mDragLayer.getLocationOnScreen(dragLayerBounds);
-
-        // Animate the app icon to the center of the window bounds in screen coordinates.
-        float centerX = windowTargetBounds.centerX() - dragLayerBounds[0];
-        float centerY = windowTargetBounds.centerY() - dragLayerBounds[1];
-
-        float xPosition = mIsRtl
-                ? windowTargetBounds.width() - lp.getMarginStart() - rect.width()
-                : lp.getMarginStart();
-        float dX = centerX - xPosition - (lp.width / 2f);
-        float dY = centerY - lp.topMargin - (lp.height / 2f);
-
-        ObjectAnimator x = ObjectAnimator.ofFloat(mFloatingView, View.TRANSLATION_X, 0f, dX);
-        ObjectAnimator y = ObjectAnimator.ofFloat(mFloatingView, View.TRANSLATION_Y, 0f, dY);
-
-        // Use upward animation for apps that are either on the bottom half of the screen, or are
-        // relatively close to the center.
-        boolean useUpwardAnimation = lp.topMargin > centerY
-                || Math.abs(dY) < mLauncher.getDeviceProfile().cellHeightPx;
-        if (useUpwardAnimation) {
-            x.setDuration(APP_LAUNCH_CURVED_DURATION);
-            y.setDuration(APP_LAUNCH_DURATION);
-        } else {
-            x.setDuration((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR * APP_LAUNCH_DURATION));
-            y.setDuration((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR * APP_LAUNCH_CURVED_DURATION));
-        }
-        x.setInterpolator(AGGRESSIVE_EASE);
-        y.setInterpolator(AGGRESSIVE_EASE);
-        appOpenAnimator.play(x);
-        appOpenAnimator.play(y);
-
-        // Scale the app icon to take up the entire screen. This simplifies the math when
-        // animating the app window position / scale.
-        float maxScaleX = windowTargetBounds.width() / (float) rect.width();
-        float maxScaleY = windowTargetBounds.height() / (float) rect.height();
-        float scale = Math.max(maxScaleX, maxScaleY);
-        float startScale = 1f;
-        if (isBubbleTextView && !(v.getParent() instanceof DeepShortcutView)) {
-            Drawable dr = ((BubbleTextView) v).getIcon();
-            if (dr instanceof FastBitmapDrawable) {
-                startScale = ((FastBitmapDrawable) dr).getAnimatedScale();
-            }
-        }
-
-        ObjectAnimator scaleAnim = ObjectAnimator
-                .ofFloat(mFloatingView, SCALE_PROPERTY, startScale, scale);
-        scaleAnim.setDuration(APP_LAUNCH_DURATION)
-                .setInterpolator(Interpolators.EXAGGERATED_EASE);
-        appOpenAnimator.play(scaleAnim);
-
-        // Fade out the app icon.
-        ObjectAnimator alpha = ObjectAnimator.ofFloat(mFloatingView, View.ALPHA, 1f, 0f);
-        if (useUpwardAnimation) {
-            alpha.setStartDelay(APP_LAUNCH_ALPHA_START_DELAY);
-            alpha.setDuration(APP_LAUNCH_ALPHA_DURATION);
-        } else {
-            alpha.setStartDelay((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR
-                    * APP_LAUNCH_ALPHA_START_DELAY));
-            alpha.setDuration((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR * APP_LAUNCH_ALPHA_DURATION));
-        }
-        alpha.setInterpolator(LINEAR);
-        appOpenAnimator.play(alpha);
-
-        appOpenAnimator.addListener(mFloatingView);
-        appOpenAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                // Reset launcher to normal state
-                if (isBubbleTextView) {
-                    ((BubbleTextView) v).setStayPressed(false);
-                }
-                v.setVisibility(View.VISIBLE);
-                ((ViewGroup) mDragLayer.getParent()).getOverlay().remove(mFloatingView);
-            }
-        });
-    }
-
-    /**
      * @return Animator that controls the window of the opening targets.
      */
     private ValueAnimator getOpeningWindowAnimators(View v, RemoteAnimationTargetCompat[] targets,
-            Rect windowTargetBounds) {
+            Rect windowTargetBounds, boolean toggleVisibility) {
         Rect bounds = new Rect();
-        if (v.getParent() instanceof DeepShortcutView) {
-            // Deep shortcut views have their icon drawn in a separate view.
-            DeepShortcutView view = (DeepShortcutView) v.getParent();
-            mDragLayer.getDescendantRectRelativeToSelf(view.getIconView(), bounds);
-        } else if (v instanceof BubbleTextView) {
-            ((BubbleTextView) v).getIconBounds(bounds);
-        } else {
-            mDragLayer.getDescendantRectRelativeToSelf(v, bounds);
-        }
-        int[] floatingViewBounds = new int[2];
-
+        mFloatingView = FloatingIconView.getFloatingIconView(mLauncher, v, toggleVisibility,
+                bounds, true /* isOpening */, mFloatingView);
         Rect crop = new Rect();
         Matrix matrix = new Matrix();
 
@@ -526,57 +420,110 @@
         SyncRtSurfaceTransactionApplierCompat surfaceApplier =
                 new SyncRtSurfaceTransactionApplierCompat(mFloatingView);
 
+        // Scale the app icon to take up the entire screen. This simplifies the math when
+        // animating the app window position / scale.
+        float maxScaleX = windowTargetBounds.width() / (float) bounds.width();
+        // We use windowTargetBounds.width for scaleY too since we start off the animation where the
+        // window is clipped to a square.
+        float maxScaleY = windowTargetBounds.width() / (float) bounds.height();
+        float scale = Math.max(maxScaleX, maxScaleY);
+        float startScale = 1f;
+        if (v instanceof BubbleTextView && !(v.getParent() instanceof DeepShortcutView)) {
+            Drawable dr = ((BubbleTextView) v).getIcon();
+            if (dr instanceof FastBitmapDrawable) {
+                startScale = ((FastBitmapDrawable) dr).getAnimatedScale();
+            }
+        }
+        final float initialStartScale = startScale;
+
+        int[] dragLayerBounds = new int[2];
+        mDragLayer.getLocationOnScreen(dragLayerBounds);
+
+        // Animate the app icon to the center of the window bounds in screen coordinates.
+        float centerX = windowTargetBounds.centerX() - dragLayerBounds[0];
+        float centerY = windowTargetBounds.centerY() - dragLayerBounds[1];
+
+        float dX = centerX - bounds.centerX();
+        float dY = centerY - bounds.centerY();
+
+        boolean useUpwardAnimation = bounds.top > centerY
+                || Math.abs(dY) < mLauncher.getDeviceProfile().cellHeightPx;
+        final long xDuration = useUpwardAnimation ? APP_LAUNCH_CURVED_DURATION
+                : APP_LAUNCH_DOWN_DURATION;
+        final long yDuration = useUpwardAnimation ? APP_LAUNCH_DURATION
+                : APP_LAUNCH_DOWN_CURVED_DURATION;
+        final long alphaDuration = useUpwardAnimation ? APP_LAUNCH_ALPHA_DURATION
+                : APP_LAUNCH_ALPHA_DOWN_DURATION;
+
+        RectF targetBounds = new RectF(windowTargetBounds);
+        RectF currentBounds = new RectF();
+        RectF temp = new RectF();
+
         ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
         appAnimator.setDuration(APP_LAUNCH_DURATION);
+        appAnimator.setInterpolator(LINEAR);
+        appAnimator.addListener(mFloatingView);
+        appAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (v instanceof BubbleTextView) {
+                    ((BubbleTextView) v).setStayPressed(false);
+                }
+            }
+        });
+
+        float shapeRevealDuration = APP_LAUNCH_DURATION * SHAPE_PROGRESS_DURATION;
         appAnimator.addUpdateListener(new MultiValueUpdateListener() {
-            // Fade alpha for the app window.
-            FloatProp mAlpha = new FloatProp(0f, 1f, 0, 60, LINEAR);
+            FloatProp mDx = new FloatProp(0, dX, 0, xDuration, AGGRESSIVE_EASE);
+            FloatProp mDy = new FloatProp(0, dY, 0, yDuration, AGGRESSIVE_EASE);
+            FloatProp mIconScale = new FloatProp(initialStartScale, scale, 0, APP_LAUNCH_DURATION,
+                    EXAGGERATED_EASE);
+            FloatProp mIconAlpha = new FloatProp(1f, 0f, shapeRevealDuration, alphaDuration,
+                    LINEAR);
+            FloatProp mCropHeight = new FloatProp(windowTargetBounds.width(),
+                    windowTargetBounds.height(), 0, shapeRevealDuration, AGGRESSIVE_EASE);
 
             @Override
             public void onUpdate(float percent) {
-                final float easePercent = AGGRESSIVE_EASE.getInterpolation(percent);
-
                 // Calculate app icon size.
-                float iconWidth = bounds.width() * mFloatingView.getScaleX();
-                float iconHeight = bounds.height() * mFloatingView.getScaleY();
+                float iconWidth = bounds.width() * mIconScale.value;
+                float iconHeight = bounds.height() * mIconScale.value;
+
+                // Animate the window crop so that it starts off as a square, and then reveals
+                // horizontally.
+                int windowWidth = windowTargetBounds.width();
+                int windowHeight = (int) mCropHeight.value;
+                crop.set(0, 0, windowWidth, windowHeight);
 
                 // Scale the app window to match the icon size.
-                float scaleX = iconWidth / windowTargetBounds.width();
-                float scaleY = iconHeight / windowTargetBounds.height();
-                float scale = Math.min(1f, Math.min(scaleX, scaleY));
+                float scaleX = iconWidth / windowWidth;
+                float scaleY = iconHeight / windowHeight;
+                float scale = Math.min(1f, Math.max(scaleX, scaleY));
 
-                // Position the scaled window on top of the icon
-                int windowWidth = windowTargetBounds.width();
-                int windowHeight = windowTargetBounds.height();
                 float scaledWindowWidth = windowWidth * scale;
                 float scaledWindowHeight = windowHeight * scale;
 
                 float offsetX = (scaledWindowWidth - iconWidth) / 2;
                 float offsetY = (scaledWindowHeight - iconHeight) / 2;
-                mFloatingView.getLocationOnScreen(floatingViewBounds);
 
-                float transX0 = floatingViewBounds[0] - offsetX;
-                float transY0 = floatingViewBounds[1] - offsetY;
+                // Calculate the window position
+                temp.set(bounds);
+                temp.offset(dragLayerBounds[0], dragLayerBounds[1]);
+                temp.offset(mDx.value, mDy.value);
+                Utilities.scaleRectFAboutCenter(temp, mIconScale.value);
+                float transX0 = temp.left - offsetX;
+                float transY0 = temp.top - offsetY;
 
                 float windowRadius = 0;
-                if (RecentsModel.INSTANCE.get(mLauncher).supportsRoundedCornersOnWindows()) {
+                if (!mDeviceProfile.isMultiWindowMode &&
+                        RecentsModel.INSTANCE.get(mLauncher).supportsRoundedCornersOnWindows()) {
                     windowRadius = RecentsModel.INSTANCE.get(mLauncher)
                             .getWindowCornerRadius();
                 }
 
-                // Animate the window crop so that it starts off as a square, and then reveals
-                // horizontally.
-                float cropHeight = windowHeight * easePercent + windowWidth * (1 - easePercent);
-                float initialTop = (windowHeight - windowWidth) / 2f;
-                crop.left = 0;
-                crop.top = (int) (initialTop * (1 - easePercent));
-                crop.right = windowWidth;
-                crop.bottom = (int) (crop.top + cropHeight);
-
                 SurfaceParams[] params = new SurfaceParams[targets.length];
                 for (int i = targets.length - 1; i >= 0; i--) {
                     RemoteAnimationTargetCompat target = targets[i];
-
                     Rect targetCrop;
                     final float alpha;
                     final float cornerRadius;
@@ -584,12 +531,15 @@
                         matrix.setScale(scale, scale);
                         matrix.postTranslate(transX0, transY0);
                         targetCrop = crop;
-                        alpha = mAlpha.value;
+                        alpha = 1f - mIconAlpha.value;
                         cornerRadius = windowRadius;
+                        matrix.mapRect(currentBounds, targetBounds);
+                        mFloatingView.update(currentBounds, mIconAlpha.value, percent, 0f,
+                                cornerRadius * scale, true /* isOpening */);
                     } else {
                         matrix.setTranslate(target.position.x, target.position.y);
-                        alpha = 1f;
                         targetCrop = target.sourceContainerBounds;
+                        alpha = 1f;
                         cornerRadius = 0;
                     }
 
@@ -700,7 +650,8 @@
                 new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
         ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
         unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
-        float cornerRadius = RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius();
+        float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 :
+                RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius();
         unlockAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
@@ -726,7 +677,8 @@
         Matrix matrix = new Matrix();
         ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
         int duration = CLOSING_TRANSITION_DURATION_MS;
-        float windowCornerRadius = RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius();
+        float windowCornerRadius = mDeviceProfile.isMultiWindowMode ? 0 :
+                RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius();
         closingAnimator.setDuration(duration);
         closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
             FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index df9dbe4..f0204b9 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -19,6 +19,9 @@
 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_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;
 
@@ -26,15 +29,16 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
-import androidx.annotation.NonNull;
-
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
 
+import androidx.annotation.NonNull;
+
 /**
  * State handler for recents view. Manages UI changes and animations for recents view based off the
  * current {@link LauncherState}.
@@ -53,19 +57,30 @@
 
     @Override
     public void setState(@NonNull LauncherState state) {
-        float[] scaleTranslationYFactor = state.getOverviewScaleAndTranslationYFactor(mLauncher);
-        SCALE_PROPERTY.set(mRecentsView, scaleTranslationYFactor[0]);
-        getTranslationYFactorProperty().set(mRecentsView, scaleTranslationYFactor[1]);
+        ScaleAndTranslation scaleAndTranslation = state
+                .getOverviewScaleAndTranslation(mLauncher);
+        SCALE_PROPERTY.set(mRecentsView, scaleAndTranslation.scale);
+        float translationX = scaleAndTranslation.translationX;
+        if (mRecentsView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+            translationX = -translationX;
+        }
+        mRecentsView.setTranslationX(translationX);
+        mRecentsView.setTranslationY(scaleAndTranslation.translationY);
         getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
     }
 
     @Override
     public final void setStateWithAnimation(@NonNull final LauncherState toState,
             @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
-        if (!config.playAtomicComponent()) {
+        boolean playAtomicOverviewComponent = config.playAtomicOverviewScaleComponent()
+                || config.playAtomicOverviewPeekComponent();
+        if (!playAtomicOverviewComponent) {
             // The entire recents animation is played atomically.
             return;
         }
+        if (builder.hasFlag(FLAG_DONT_ANIMATE_OVERVIEW)) {
+            return;
+        }
         setStateWithAnimationInternal(toState, builder, config);
     }
 
@@ -79,36 +94,25 @@
     void setStateWithAnimationInternal(@NonNull final LauncherState toState,
             @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
         PropertySetter setter = config.getPropertySetter(builder);
-        float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher);
-        Interpolator scaleAndTransYInterpolator = getScaleAndTransYInterpolator(toState, builder);
-        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleTranslationYFactor[0],
-                scaleAndTransYInterpolator);
-        setter.setFloat(mRecentsView, getTranslationYFactorProperty(), scaleTranslationYFactor[1],
-                scaleAndTransYInterpolator);
+        ScaleAndTranslation scaleAndTranslation = toState.getOverviewScaleAndTranslation(mLauncher);
+        Interpolator scaleInterpolator = builder.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR);
+        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndTranslation.scale, scaleInterpolator);
+        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, translateXInterpolator);
+        setter.setFloat(mRecentsView, View.TRANSLATION_Y, scaleAndTranslation.translationY,
+                translateYInterpolator);
         setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
                 builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
     }
 
     /**
-     * Get the interpolator to use for the scale and translation Y animation for the view.
-     *
-     * @param toState state to animate to
-     * @param builder animator set builder
-     * @return interpolator for scale and trans Y recents view animation
-     */
-    Interpolator getScaleAndTransYInterpolator(@NonNull final LauncherState toState,
-            @NonNull AnimatorSetBuilder builder) {
-        return builder.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR);
-    }
-
-    /**
-     * Get property for translation Y factor for the recents view.
-     *
-     * @return the float property for the recents view
-     */
-    abstract FloatProperty getTranslationYFactorProperty();
-
-    /**
      * Get property for content alpha for the recents view.
      *
      * @return the float property for the view's content alpha
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 25e1c89..6d730b6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -41,8 +41,11 @@
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.QuickstepAppTransitionManagerImpl;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.dragndrop.DragLayer;
 import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
 import com.android.systemui.shared.system.ActivityCompat;
 
@@ -52,8 +55,11 @@
 
 public class UiFactory extends RecentsUiFactory {
 
-    public static void setOnTouchControllersChangedListener(Context context, Runnable listener) {
-        OverviewInteractionState.INSTANCE.get(context).setOnSwipeUpSettingChangedListener(listener);
+    public static Runnable enableLiveTouchControllerChanges(DragLayer dl) {
+        NavigationModeChangeListener listener = m -> dl.recreateControllers();
+        SysUINavigationMode mode = SysUINavigationMode.INSTANCE.get(dl.getContext());
+        mode.addModeChangeListener(listener);
+        return () -> mode.removeModeChangeListener(listener);
     }
 
     public static StateHandler[] getStateHandler(Launcher launcher) {
@@ -89,8 +95,8 @@
 
                 @Override
                 public void onStateTransitionComplete(LauncherState finalState) {
-                    boolean swipeUpEnabled = OverviewInteractionState.INSTANCE.get(launcher)
-                            .isSwipeUpGestureEnabled();
+                    boolean swipeUpEnabled = SysUINavigationMode.INSTANCE.get(launcher).getMode()
+                            .hasGestures;
                     LauncherState prevState = launcher.getStateManager().getLastState();
 
                     if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
similarity index 82%
rename from quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
rename to quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 1eaa8bc..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;
@@ -60,10 +60,10 @@
     }
 
     @Override
-    public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
-        float[] scaleAndTranslation = LauncherState.OVERVIEW.getWorkspaceScaleAndTranslation(
-                launcher);
-        scaleAndTranslation[0] = 1;
+    public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
+        ScaleAndTranslation scaleAndTranslation = LauncherState.OVERVIEW
+                .getWorkspaceScaleAndTranslation(launcher);
+        scaleAndTranslation.scale = 1;
         return scaleAndTranslation;
     }
 
@@ -78,8 +78,9 @@
     }
 
     @Override
-    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
-        return new float[] {0.9f, -0.2f};
+    public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
+        float slightParallax = -launcher.getDeviceProfile().allAppsCellHeightPx * 0.3f;
+        return new ScaleAndTranslation(0.9f, 0f, slightParallax);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
similarity index 94%
rename from quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
index fd4bf9b..0605953 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
@@ -1,8 +1,8 @@
-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;
-import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
+import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 
 import android.view.MotionEvent;
 
@@ -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 96%
rename from quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index d20ffbb..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;
@@ -195,6 +197,7 @@
 
         final AnimatorSetBuilder builder = totalShift == 0 ? new AnimatorSetBuilder()
                 : getAnimatorSetBuilderForStates(mFromState, mToState);
+        updateAnimatorBuilderOnReinit(builder);
 
         cancelPendingAnim();
 
@@ -228,6 +231,12 @@
         return 1 / totalShift;
     }
 
+    /**
+     * Give subclasses the chance to update the animation when we re-initialize towards a new state.
+     */
+    protected void updateAnimatorBuilderOnReinit(AnimatorSetBuilder builder) {
+    }
+
     private void cancelPendingAnim() {
         if (mPendingAnimation != null) {
             mPendingAnimation.finish(false, Touch.SWIPE);
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/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index eccef04..a71b7bb 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep;
 
-import android.animation.Animator;
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.Intent;
@@ -31,7 +30,6 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -55,6 +53,8 @@
 
     void onSwipeUpComplete(T activity);
 
+    void onAssistantVisibilityChanged(float visibility);
+
     @NonNull HomeAnimationFactory prepareHomeUI(T activity);
 
     AnimationFactory prepareRecentsUI(T activity, boolean activityVisible,
@@ -85,8 +85,6 @@
         return true;
     }
 
-    AlphaProperty getAlphaProperty(T activity);
-
     /**
      * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
      */
@@ -135,6 +133,6 @@
 
         @NonNull RectF getWindowTargetRect();
 
-        @NonNull Animator createActivityAnimationToHome();
+        @NonNull AnimatorPlaybackController createActivityAnimationToHome();
     }
 }
diff --git a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
index 12757c0..3e9872a 100644
--- a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
+++ b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
@@ -19,16 +19,11 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.InstantAppInfo;
 import android.content.pm.PackageManager;
-import android.util.Log;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.util.InstantAppResolver;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Implementation of InstantAppResolver using platform APIs
  */
@@ -40,8 +35,7 @@
 
     private final PackageManager mPM;
 
-    public InstantAppResolverImpl(Context context)
-            throws NoSuchMethodException, ClassNotFoundException {
+    public InstantAppResolverImpl(Context context) {
         mPM = context.getPackageManager();
     }
 
@@ -55,23 +49,4 @@
         ComponentName cn = info.getTargetComponent();
         return cn != null && cn.getClassName().equals(COMPONENT_CLASS_MARKER);
     }
-
-    @Override
-    public List<ApplicationInfo> getInstantApps() {
-        try {
-            List<ApplicationInfo> result = new ArrayList<>();
-            for (InstantAppInfo iai : mPM.getInstantApps()) {
-                ApplicationInfo info = iai.getApplicationInfo();
-                if (info != null) {
-                    result.add(info);
-                }
-            }
-            return result;
-        } catch (SecurityException se) {
-            Log.w(TAG, "getInstantApps failed. Launcher may not be the default home app.", se);
-        } catch (Exception e) {
-            Log.e(TAG, "Error calling API: getInstantApps", e);
-        }
-        return super.getInstantApps();
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index a0ab301..ce472c6 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep;
 
-import static com.android.quickstep.SwipeUpSetting.newSwipeUpSettingsObserver;
 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB;
 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP;
 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
@@ -28,9 +27,10 @@
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.SecureSettingsObserver;
 import com.android.launcher3.util.UiThreadHelper;
+import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
 import androidx.annotation.WorkerThread;
@@ -56,9 +56,7 @@
 
     private static final int MSG_SET_PROXY = 200;
     private static final int MSG_SET_BACK_BUTTON_ALPHA = 201;
-    private static final int MSG_SET_SWIPE_UP_ENABLED = 202;
-
-    private final SecureSettingsObserver mSwipeUpSettingObserver;
+    private static final int MSG_APPLY_FLAGS = 202;
 
     private final Context mContext;
     private final Handler mUiHandler;
@@ -66,11 +64,9 @@
 
     // These are updated on the background thread
     private ISystemUiProxy mISystemUiProxy;
-    private boolean mSwipeUpEnabled = true;
+    private boolean mSwipeUpEnabled;
     private float mBackButtonAlpha = 1;
 
-    private Runnable mOnSwipeUpSettingChangedListener;
-
     private OverviewInteractionState(Context context) {
         mContext = context;
 
@@ -80,20 +76,8 @@
         mUiHandler = new Handler(this::handleUiMessage);
         mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage);
 
-        if (SwipeUpSetting.isSwipeUpSettingAvailable()) {
-            mSwipeUpSettingObserver =
-                    newSwipeUpSettingsObserver(context, this::notifySwipeUpSettingChanged);
-            mSwipeUpSettingObserver.register();
-            mSwipeUpEnabled = mSwipeUpSettingObserver.getValue();
-            resetHomeBounceSeenOnQuickstepEnabledFirstTime();
-        } else {
-            mSwipeUpSettingObserver = null;
-            mSwipeUpEnabled = SwipeUpSetting.isSwipeUpEnabledDefaultValue();
-        }
-    }
-
-    public boolean isSwipeUpGestureEnabled() {
-        return mSwipeUpEnabled;
+        onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(context)
+                .addModeChangeListener(this::onNavigationModeChanged));
     }
 
     public float getBackButtonAlpha() {
@@ -129,23 +113,13 @@
             case MSG_SET_BACK_BUTTON_ALPHA:
                 applyBackButtonAlpha((float) msg.obj, msg.arg1 == 1);
                 return true;
-            case MSG_SET_SWIPE_UP_ENABLED:
-                mSwipeUpEnabled = msg.arg1 != 0;
-                resetHomeBounceSeenOnQuickstepEnabledFirstTime();
-
-                if (mOnSwipeUpSettingChangedListener != null) {
-                    mOnSwipeUpSettingChangedListener.run();
-                }
+            case MSG_APPLY_FLAGS:
                 break;
         }
         applyFlags();
         return true;
     }
 
-    public void setOnSwipeUpSettingChangedListener(Runnable listener) {
-        mOnSwipeUpSettingChangedListener = listener;
-    }
-
     @WorkerThread
     private void applyFlags() {
         if (mISystemUiProxy == null) {
@@ -175,10 +149,12 @@
         }
     }
 
-    private void notifySwipeUpSettingChanged(boolean swipeUpEnabled) {
-        mUiHandler.removeMessages(MSG_SET_SWIPE_UP_ENABLED);
-        mUiHandler.obtainMessage(MSG_SET_SWIPE_UP_ENABLED, swipeUpEnabled ? 1 : 0, 0).
-                sendToTarget();
+    private void onNavigationModeChanged(SysUINavigationMode.Mode mode) {
+        FeatureFlags.SWIPE_HOME.updateStorage(mContext, mode == Mode.NO_BUTTON);
+
+        mSwipeUpEnabled = mode.hasGestures;
+        resetHomeBounceSeenOnQuickstepEnabledFirstTime();
+        mBgHandler.obtainMessage(MSG_APPLY_FLAGS).sendToTarget();
     }
 
     private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index e15a3f1..06a36c9 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -44,7 +44,6 @@
     private final KeyguardManagerCompat mKeyguardManager;
     private final MainThreadExecutor mMainThreadExecutor;
     private final BackgroundExecutor mBgThreadExecutor;
-    private final TaskListStabilizer mStabilizer = new TaskListStabilizer();
 
     // The list change id, increments as the task list changes in the system
     private int mChangeId;
@@ -74,14 +73,6 @@
         });
     }
 
-    public void startStabilizationSession() {
-        mStabilizer.startStabilizationSession();
-    }
-
-    public void endStabilizationSession() {
-        mStabilizer.endStabilizationSession();
-    }
-
     /**
      * Asynchronously fetches the list of recent tasks, reusing cached list if available.
      *
@@ -93,7 +84,7 @@
         final int requestLoadId = mChangeId;
         Runnable resultCallback = callback == null
                 ? () -> { }
-                : () -> callback.accept(mStabilizer.reorder(mTasks));
+                : () -> callback.accept(mTasks);
 
         if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) {
             // The list is up to date, callback with the same list
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 56bc857..a65bc33 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -90,14 +90,6 @@
         return mThumbnailCache;
     }
 
-    public void startStabilizationSession() {
-        mTaskList.startStabilizationSession();
-    }
-
-    public void endStabilizationSession() {
-        mTaskList.endStabilizationSession();
-    }
-
     /**
      * Fetches the list of recent tasks.
      *
diff --git a/quickstep/src/com/android/quickstep/SwipeUpSetting.java b/quickstep/src/com/android/quickstep/SwipeUpSetting.java
deleted file mode 100644
index 381ab9f..0000000
--- a/quickstep/src/com/android/quickstep/SwipeUpSetting.java
+++ /dev/null
@@ -1,62 +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.quickstep;
-
-import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.util.Log;
-
-import com.android.launcher3.util.SecureSettingsObserver;
-import com.android.launcher3.util.SecureSettingsObserver.OnChangeListener;
-
-public final class SwipeUpSetting {
-    private static final String TAG = "SwipeUpSetting";
-
-    private static final String SWIPE_UP_SETTING_AVAILABLE_RES_NAME =
-            "config_swipe_up_gesture_setting_available";
-
-    private static final String SWIPE_UP_ENABLED_DEFAULT_RES_NAME =
-            "config_swipe_up_gesture_default";
-
-    private static boolean getSystemBooleanRes(String resName) {
-        Resources res = Resources.getSystem();
-        int resId = res.getIdentifier(resName, "bool", "android");
-
-        if (resId != 0) {
-            return res.getBoolean(resId);
-        } else {
-            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
-            return false;
-        }
-    }
-
-    public static boolean isSwipeUpSettingAvailable() {
-        return getSystemBooleanRes(SWIPE_UP_SETTING_AVAILABLE_RES_NAME);
-    }
-
-    public static boolean isSwipeUpEnabledDefaultValue() {
-        return getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME);
-    }
-
-    public static SecureSettingsObserver newSwipeUpSettingsObserver(Context context,
-            OnChangeListener listener) {
-        return new SecureSettingsObserver(context.getContentResolver(), listener,
-                SWIPE_UP_SETTING_NAME, isSwipeUpEnabledDefaultValue() ? 1 : 0);
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
new file mode 100644
index 0000000..1953ecb
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.util.Log;
+
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.systemui.shared.system.QuickStepContract;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Observer for the resource config that specifies the navigation bar mode.
+ */
+public class SysUINavigationMode {
+
+    public enum Mode {
+        THREE_BUTTONS(false, 0),
+        TWO_BUTTONS(true, 1),
+        NO_BUTTON(true, 2);
+
+        public final boolean hasGestures;
+        public final int resValue;
+
+        Mode(boolean hasGestures, int resValue) {
+            this.hasGestures = hasGestures;
+            this.resValue = resValue;
+        }
+    }
+
+    public static MainThreadInitializedObject<SysUINavigationMode> INSTANCE =
+            new MainThreadInitializedObject<>(SysUINavigationMode::new);
+
+    private static final String TAG = "SysUINavigationMode";
+
+    private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
+    private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
+            "config_navBarInteractionMode";
+
+    private final Context mContext;
+    private Mode mMode;
+
+    private final List<NavigationModeChangeListener> mChangeListeners = new ArrayList<>();
+
+    public SysUINavigationMode(Context context) {
+        mContext = context;
+        initializeMode();
+
+        IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+        filter.addDataScheme("package");
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                Mode oldMode = mMode;
+                initializeMode();
+                if (mMode != oldMode) {
+                    dispatchModeChange();
+                }
+            }
+        }, filter);
+    }
+
+    private void initializeMode() {
+        int modeInt = getSystemIntegerRes(mContext, NAV_BAR_INTERACTION_MODE_RES_NAME);
+        for(Mode m : Mode.values()) {
+            if (m.resValue == modeInt) {
+                mMode = m;
+            }
+        }
+    }
+
+    private void dispatchModeChange() {
+        for (NavigationModeChangeListener listener : mChangeListeners) {
+            listener.onNavigationModeChanged(mMode);
+        }
+    }
+
+    public Mode addModeChangeListener(NavigationModeChangeListener listener) {
+        mChangeListeners.add(listener);
+        return mMode;
+    }
+
+    public void removeModeChangeListener(NavigationModeChangeListener listener) {
+        mChangeListeners.remove(listener);
+    }
+
+    public Mode getMode() {
+        return mMode;
+    }
+
+    private static int getSystemIntegerRes(Context context, String resName) {
+        Resources res = context.getResources();
+        int resId = res.getIdentifier(resName, "integer", "android");
+
+        if (resId != 0) {
+            return res.getInteger(resId);
+        } else {
+            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+            return -1;
+        }
+    }
+
+    public interface NavigationModeChangeListener {
+
+        void onNavigationModeChanged(Mode newMode);
+    }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/TaskListStabilizer.java b/quickstep/src/com/android/quickstep/TaskListStabilizer.java
deleted file mode 100644
index 4c63f81..0000000
--- a/quickstep/src/com/android/quickstep/TaskListStabilizer.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import android.app.ActivityManager.RecentTaskInfo;
-import android.content.ComponentName;
-import android.os.Process;
-import android.os.SystemClock;
-
-import com.android.launcher3.util.IntArray;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.Task.TaskKey;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Keeps the task list stable during quick switch gestures. So if you swipe right to switch from app
- * A to B, you can then swipe right again to get to app C or left to get back to A.
- */
-public class TaskListStabilizer {
-
-    private static final long TASK_CACHE_TIMEOUT_MS = 5000;
-
-    private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
-
-        @Override
-        public void onTaskCreated(int taskId, ComponentName componentName) {
-            endStabilizationSession();
-        }
-
-        @Override
-        public void onTaskRemoved(int taskId) {
-            endStabilizationSession();
-        }
-    };
-
-    // Task ids ordered based on recency, 0th index is the least recent task
-    private final IntArray mSystemOrder;
-    private final IntArray mStabilizedOrder;
-
-    // Wrapper objects used for sorting tasks
-    private final ArrayList<TaskWrapper> mTaskWrappers = new ArrayList<>();
-
-    private boolean mInStabilizationSession;
-    private long mSessionStartTime;
-
-    public TaskListStabilizer() {
-        // Initialize the task ids map
-        List<RecentTaskInfo> rawTasks = ActivityManagerWrapper.getInstance().getRecentTasks(
-                Integer.MAX_VALUE, Process.myUserHandle().getIdentifier());
-        mSystemOrder = new IntArray(rawTasks.size());
-        for (RecentTaskInfo info : rawTasks) {
-            mSystemOrder.add(new TaskKey(info).id);
-        }
-        // We will lazily copy the task id's from mSystemOrder when a stabilization session starts.
-        mStabilizedOrder = new IntArray(rawTasks.size());
-
-        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
-    }
-
-    public synchronized void startStabilizationSession() {
-        if (!mInStabilizationSession) {
-            mStabilizedOrder.clear();
-            mStabilizedOrder.addAll(mSystemOrder);
-        }
-        mInStabilizationSession = true;
-        mSessionStartTime = SystemClock.uptimeMillis();
-    }
-
-    public synchronized void endStabilizationSession() {
-        mInStabilizationSession = false;
-    }
-
-    public synchronized ArrayList<Task> reorder(ArrayList<Task> tasks) {
-        mSystemOrder.clear();
-        for (Task task : tasks) {
-            mSystemOrder.add(task.key.id);
-        }
-
-        if ((SystemClock.uptimeMillis() - mSessionStartTime) > TASK_CACHE_TIMEOUT_MS) {
-            endStabilizationSession();
-        }
-
-        if (!mInStabilizationSession) {
-            return tasks;
-        }
-
-        // Ensure that we have enough wrappers
-        int taskCount = tasks.size();
-        for (int i = taskCount - mTaskWrappers.size(); i > 0; i--) {
-            mTaskWrappers.add(new TaskWrapper());
-        }
-
-        List<TaskWrapper> listToSort = mTaskWrappers.size() == taskCount
-                ? mTaskWrappers : mTaskWrappers.subList(0, taskCount);
-        int missingTaskIndex = -taskCount;
-
-        for (int i = 0; i < taskCount; i++){
-            TaskWrapper wrapper = listToSort.get(i);
-            wrapper.task = tasks.get(i);
-            wrapper.index = mStabilizedOrder.indexOf(wrapper.task.key.id);
-
-            // Ensure that missing tasks are put in the front, in the order they appear in the
-            // original list
-            if (wrapper.index < 0) {
-                wrapper.index = missingTaskIndex;
-                missingTaskIndex++;
-            }
-        }
-        Collections.sort(listToSort);
-
-        ArrayList<Task> result = new ArrayList<>(taskCount);
-        for (int i = 0; i < taskCount; i++) {
-            result.add(listToSort.get(i).task);
-        }
-        return result;
-    }
-
-    private static class TaskWrapper implements Comparable<TaskWrapper> {
-        Task task;
-        int index;
-
-        @Override
-        public int compareTo(TaskWrapper other) {
-            return Integer.compare(index, other.index);
-        }
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/TestInformationProvider.java b/quickstep/src/com/android/quickstep/TestInformationProvider.java
new file mode 100644
index 0000000..b37ddda
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TestInformationProvider.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.TestProtocol;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.uioverrides.states.OverviewState;
+import com.android.quickstep.util.LayoutUtils;
+
+public class TestInformationProvider extends ContentProvider {
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
+        return 0;
+    }
+
+    @Override
+    public int delete(Uri uri, String s, String[] strings) {
+        return 0;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues contentValues) {
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
+        return null;
+    }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            final Bundle response = new Bundle();
+            final Context context = getContext();
+            final DeviceProfile deviceProfile = InvariantDeviceProfile.INSTANCE.
+                    get(context).getDeviceProfile(context);
+            final LauncherAppState launcherAppState = LauncherAppState.getInstanceNoCreate();
+            final Launcher launcher = launcherAppState != null ?
+                    (Launcher) launcherAppState.getModel().getCallback() : null;
+
+            switch (method) {
+                case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
+                    final float swipeHeight =
+                            OverviewState.getDefaultSwipeHeight(deviceProfile);
+                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
+                    break;
+                }
+
+                case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
+                    final float swipeHeight =
+                            LayoutUtils.getShelfTrackingDistance(context, deviceProfile);
+                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
+                    break;
+                }
+
+                case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
+                    if (launcher == null) return null;
+
+                    final float progress = LauncherState.OVERVIEW.getVerticalProgress(launcher)
+                            - LauncherState.ALL_APPS.getVerticalProgress(launcher);
+                    final float distance =
+                            launcher.getAllAppsController().getShiftRange() * progress;
+                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
+                    break;
+                }
+
+                case TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT: {
+                    if (launcher == null) return null;
+
+                    final float progress = LauncherState.NORMAL.getVerticalProgress(launcher)
+                            - LauncherState.ALL_APPS.getVerticalProgress(launcher);
+                    final float distance =
+                            launcher.getAllAppsController().getShiftRange() * progress;
+                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
+                    break;
+                }
+            }
+            return response;
+        }
+        return null;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java b/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
index 4392851..6dff187 100644
--- a/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
+++ b/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
@@ -34,7 +34,6 @@
  * quickstep interactions.
  */
 @SuppressWarnings("unused")
-@Deprecated
 public class UserEventDispatcherExtension extends UserEventDispatcher {
 
     private static final String TAG = "UserEventDispatcher";
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index 8a117c8..ae5f390 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -17,7 +17,6 @@
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.os.SystemClock;
 import android.view.MotionEvent;
 
 import com.android.launcher3.Alarm;
@@ -71,9 +70,6 @@
      */
     public void setOnMotionPauseListener(OnMotionPauseListener listener) {
         mOnMotionPauseListener = listener;
-        if (mOnMotionPauseListener != null) {
-            mOnMotionPauseListener.onMotionPauseChanged(mIsPaused);
-        }
     }
 
     /**
@@ -83,7 +79,7 @@
      *
      * TODO: Use historical positions as well, e.g. {@link MotionEvent#getHistoricalY(int, int)}.
      */
-    public void addPosition(float position, float orthogonalPosition) {
+    public void addPosition(float position, float orthogonalPosition, long time) {
         if (mFirstPosition == null) {
             mFirstPosition = position;
         }
@@ -91,7 +87,6 @@
             mFirstOrthogonalPosition = orthogonalPosition;
         }
         mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT);
-        long time = SystemClock.uptimeMillis();
         if (mPreviousTime != null && mPreviousPosition != null) {
             long changeInTime = Math.max(1, time - mPreviousTime);
             float changeInPosition = position - mPreviousPosition;
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/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
new file mode 100644
index 0000000..f5ac9c9
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -0,0 +1,147 @@
+/*
+ * 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.quickstep;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+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;
+
+import android.content.Context;
+import android.util.Log;
+import androidx.test.uiautomator.UiDevice;
+import com.android.launcher3.tapl.LauncherInstrumentation;
+import com.android.launcher3.tapl.TestHelpers;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Assert;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Test rule that allows executing a test with Quickstep on and then Quickstep off.
+ * The test should be annotated with @QuickstepOnOff.
+ */
+public class NavigationModeSwitchRule implements TestRule {
+
+    static final String TAG = "QuickStepOnOffRule";
+
+    public enum Mode {
+        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 NavigationModeSwitch {
+        Mode mode() default ALL;
+    }
+
+    private final LauncherInstrumentation mLauncher;
+
+    public NavigationModeSwitchRule(LauncherInstrumentation launcher) {
+        mLauncher = launcher;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        if (TestHelpers.isInLauncherProcess() &&
+                description.getAnnotation(NavigationModeSwitch.class) != null) {
+            Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode();
+            return new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                    final Context context = getInstrumentation().getContext();
+                    final String prevOverlayPkg = LauncherInstrumentation.isGesturalMode(context)
+                            ? NAV_BAR_MODE_GESTURAL_OVERLAY
+                            : LauncherInstrumentation.isSwipeUpMode(context)
+                                    ? NAV_BAR_MODE_2BUTTON_OVERLAY
+                                    : NAV_BAR_MODE_3BUTTON_OVERLAY;
+                    final LauncherInstrumentation.NavigationModel originalMode =
+                            mLauncher.getNavigationModel();
+                    try {
+//                        if (mode == ZERO_BUTTON || mode == ALL) {
+//                            evaluateWithZeroButtons();
+//                        }
+                        if (mode == TWO_BUTTON || mode == ALL) {
+                            evaluateWithTwoButtons();
+                        }
+                        if (mode == THREE_BUTTON || mode == ALL) {
+                            evaluateWithThreeButtons();
+                        }
+                    } finally {
+                        setActiveOverlay(prevOverlayPkg, originalMode);
+                    }
+                }
+
+                public void evaluateWithoutChangingSetting(Statement base) throws Throwable {
+                    base.evaluate();
+                }
+
+                private void evaluateWithThreeButtons() throws Throwable {
+                    setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.THREE_BUTTON);
+                    evaluateWithoutChangingSetting(base);
+                }
+
+                private void evaluateWithTwoButtons() throws Throwable {
+                    setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.TWO_BUTTON);
+                    base.evaluate();
+                }
+
+                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,
+                            overlayPackage == NAV_BAR_MODE_2BUTTON_OVERLAY);
+                    setOverlayPackageEnabled(NAV_BAR_MODE_GESTURAL_OVERLAY,
+                            overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
+
+                    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)
+                        throws Exception {
+                    Log.d(TAG, "setOverlayPackageEnabled: " + overlayPackage + " " + enable);
+                    final String action = enable ? "enable" : "disable";
+                    UiDevice.getInstance(getInstrumentation()).executeShellCommand(
+                            "cmd overlay " + action + " " + overlayPackage);
+                }
+            };
+        } else {
+            return base;
+        }
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java b/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java
deleted file mode 100644
index f89842a..0000000
--- a/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java
+++ /dev/null
@@ -1,131 +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.quickstep;
-
-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.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
-
-import static org.junit.Assert.assertTrue;
-
-import android.provider.Settings;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.launcher3.tapl.LauncherInstrumentation;
-import com.android.launcher3.tapl.TestHelpers;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-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 {
-
-    static final String TAG = "QuickStepOnOffRule";
-
-    public enum Mode {
-        ON, OFF, BOTH
-    }
-
-    // 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;
-    }
-
-    private final Executor mMainThreadExecutor;
-    private final LauncherInstrumentation mLauncher;
-
-    public QuickStepOnOffRule(Executor mainThreadExecutor, 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();
-            return new Statement() {
-                @Override
-                public void evaluate() throws Throwable {
-                    if (SwipeUpSetting.isSwipeUpSettingAvailable()) {
-                        try {
-                            if (mode == ON || mode == BOTH) {
-                                evaluateWithQuickstepOn();
-                            }
-                            if (mode == OFF || mode == BOTH) {
-                                evaluateWithQuickstepOff();
-                            }
-                        } finally {
-                            setSwipeUpSetting(null);
-                        }
-                    } else {
-                        // Execute without changing the setting, if the requested mode is
-                        // compatible.
-                        final boolean swipeUpEnabledDefaultValue =
-                                SwipeUpSetting.isSwipeUpEnabledDefaultValue();
-                        if (mode == BOTH ||
-                                mode == ON && swipeUpEnabledDefaultValue ||
-                                mode == OFF && !swipeUpEnabledDefaultValue) {
-                            evaluateWithoutChangingSetting(base);
-                        }
-                    }
-                }
-
-                public void setSwipeUpSetting(String value) {
-                    Log.d(TAG, "setSwipeUpSetting: " + value);
-                    assertTrue("Couldn't change Quickstep mode",
-                            Settings.Secure.putString(
-                                    InstrumentationRegistry.getInstrumentation().getTargetContext().
-                                            getContentResolver(),
-                                    SWIPE_UP_SETTING_NAME,
-                                    value));
-                }
-
-                public void evaluateWithoutChangingSetting(Statement base) throws Throwable {
-                    base.evaluate();
-                }
-
-                private void evaluateWithQuickstepOff() throws Throwable {
-                    setSwipeUpSetting("0");
-                    evaluateWithoutChangingSetting(base);
-                }
-
-                private void evaluateWithQuickstepOn() throws Throwable {
-                    setSwipeUpSetting("1");
-                    base.evaluate();
-                }
-            };
-        } else {
-            return base;
-        }
-    }
-}
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index fe789aa..dc83e87 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;
@@ -37,6 +37,9 @@
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class StartLauncherViaGestureTests extends AbstractQuickStepTest {
+
+    static final int STRESS_REPEAT_COUNT = 10;
+
     @Override
     @Before
     public void setUp() throws Exception {
@@ -59,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),
@@ -74,9 +77,34 @@
 
     @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
+    @NavigationModeSwitch
+    public void testStressPressHome() {
+        for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
+            // Destroy Launcher activity.
+            closeLauncherActivity();
+
+            // The test action.
+            mLauncher.pressHome();
+        }
+    }
+
+    @Test
+    @Ignore // b/129723135
+    @NavigationModeSwitch
+    public void testStressSwipeToOverview() {
+        for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
+            // Destroy Launcher activity.
+            closeLauncherActivity();
+
+            // The test action.
+            mLauncher.getBackground().switchToOverview();
+        }
+    }
 }
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index c60cf45..4112ccf 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -37,10 +37,11 @@
 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;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestWatcher;
@@ -59,7 +60,7 @@
     }
 
     private void startTestApps() throws Exception {
-        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_MESSAGING));
+        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_MARKET));
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CONTACTS));
 
@@ -71,6 +72,7 @@
 
     @Test
     @PortraitLandscape
+    @Ignore
     public void testPressRecentAppsLauncherAndGetOverview() throws RemoteException {
         mDevice.pressRecentApps();
         waitForState("Launcher internal state didn't switch to Overview", LauncherState.OVERVIEW);
@@ -79,7 +81,7 @@
     }
 
     @Test
-    @QuickstepOnOff
+    @NavigationModeSwitch
     @PortraitLandscape
     public void testWorkspaceSwitchToAllApps() {
         assertNotNull("switchToAllApps() returned null",
@@ -198,7 +200,7 @@
     }
 
     @Test
-    @QuickstepOnOff
+    @NavigationModeSwitch
     @PortraitLandscape
     public void testSwitchToOverview() throws Exception {
         assertNotNull("Workspace.switchToOverview() returned null",
@@ -208,7 +210,7 @@
     }
 
     @Test
-    @QuickstepOnOff
+    @NavigationModeSwitch
     @PortraitLandscape
     public void testBackground() throws Exception {
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 56ef414..19f537f 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -33,7 +33,7 @@
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Fes doble toc i mantén premut per seleccionar un widget o per utilitzar les accions personalitzades."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d d\'amplada per %2$d d\'alçada"</string>
-    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Toca i mantén premut l\'element per col·locar-lo manualment"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Mantén premut l\'element per afegir-lo manualment"</string>
     <string name="place_automatically" msgid="8064208734425456485">"Afegeix automàticament"</string>
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Cerca aplicacions"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"S\'estan carregant les aplicacions…"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index c58620e..58b65da 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -33,7 +33,7 @@
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"위젯을 선택하려면 두 번 탭한 다음 길게 터치하거나 맞춤 액션을 사용합니다."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"너비 %1$d, 높이 %2$d"</string>
-    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"길게 터치하여 직접 장소 추가"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"길게 터치하여 직접 추가"</string>
     <string name="place_automatically" msgid="8064208734425456485">"자동으로 추가"</string>
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"앱 검색"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"앱 로드 중…"</string>
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index b2f3575..bc658e4 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -27,9 +27,6 @@
     <!-- Dynamic grid -->
     <dimen name="dynamic_grid_icon_drawable_padding">4dp</dimen>
 
-    <dimen name="dynamic_grid_cell_layout_padding">0dp</dimen>
-    <dimen name="dynamic_grid_cell_layout_bottom_padding">5.5dp</dimen>
-
     <!-- Hotseat -->
     <dimen name="dynamic_grid_hotseat_side_padding">16dp</dimen>
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index b26b812..68a08a9 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -104,7 +104,7 @@
     <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets de <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list" msgid="796804551140113767">"Lista de widgets"</string>
     <string name="widgets_list_closed" msgid="6141506579418771922">"Lista de widgets fechada."</string>
-    <string name="action_add_to_workspace" msgid="8902165848117513641">"Adicionar ao Ecrã principal"</string>
+    <string name="action_add_to_workspace" msgid="8902165848117513641">"Adicionar ao ecrã principal"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Mover o item para aqui"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Item adicionado ao ecrã principal"</string>
     <string name="item_removed" msgid="851119963877842327">"Item removido"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index 6bd01b5..4b68b50 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -70,6 +70,7 @@
     <string name="instant_app_resolver_class" translatable="false"></string>
     <string name="main_process_initializer_class" translatable="false"></string>
     <string name="system_shortcut_factory_class" translatable="false"></string>
+    <string name="app_launch_tracker_class" translatable="false"></string>
 
     <!-- Package name of the default wallpaper picker. -->
     <string name="wallpaper_picker_package" translatable="false"></string>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 04e4591..7822e05 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -22,13 +22,10 @@
     <dimen name="dynamic_grid_edge_margin">8dp</dimen>
     <dimen name="dynamic_grid_page_indicator_line_height">1dp</dimen>
     <dimen name="dynamic_grid_icon_drawable_padding">8dp</dimen>
-    <dimen name="dynamic_grid_workspace_top_padding">8dp</dimen>
-    <dimen name="dynamic_grid_workspace_page_spacing">8dp</dimen>
     <!-- Minimum space between workspace and hotseat in spring loaded mode -->
     <dimen name="dynamic_grid_min_spring_loaded_space">8dp</dimen>
 
     <dimen name="dynamic_grid_cell_layout_padding">5.5dp</dimen>
-    <dimen name="dynamic_grid_cell_layout_bottom_padding">0dp</dimen>
     <dimen name="dynamic_grid_cell_padding_x">8dp</dimen>
 
     <!-- Hotseat -->
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 8e2ffe9..f9a8d1b 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -24,6 +24,9 @@
 import com.android.launcher3.util.FocusLogic;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class AppWidgetResizeFrame extends AbstractFloatingView implements View.OnKeyListener {
     private static final int SNAP_DURATION = 150;
     private static final float DIMMED_HANDLE_ALPHA = 0f;
@@ -45,6 +48,7 @@
     private final FirstFrameAnimatorHelper mFirstFrameAnimatorHelper;
 
     private final View[] mDragHandles = new View[HANDLE_COUNT];
+    private final List<Rect> mSystemGestureExclusionRects = new ArrayList<>(HANDLE_COUNT);
 
     private LauncherAppWidgetHostView mWidgetView;
     private CellLayout mCellLayout;
@@ -106,6 +110,10 @@
                 .getDimensionPixelSize(R.dimen.resize_frame_background_padding);
         mTouchTargetWidth = 2 * mBackgroundPadding;
         mFirstFrameAnimatorHelper = new FirstFrameAnimatorHelper(this);
+
+        for (int i = 0; i < HANDLE_COUNT; i++) {
+            mSystemGestureExclusionRects.add(new Rect());
+        }
     }
 
     @Override
@@ -118,6 +126,19 @@
         }
     }
 
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        if (Utilities.ATLEAST_Q) {
+            for (int i = 0; i < HANDLE_COUNT; i++) {
+                View dragHandle = mDragHandles[i];
+                mSystemGestureExclusionRects.get(i).set(dragHandle.getLeft(), dragHandle.getTop(),
+                        dragHandle.getRight(), dragHandle.getBottom());
+            }
+            setSystemGestureExclusionRects(mSystemGestureExclusionRects);
+        }
+    }
+
     public static void showForWidget(LauncherAppWidgetHostView widget, CellLayout cellLayout) {
         Launcher launcher = Launcher.getLauncher(cellLayout.getContext());
         AbstractFloatingView.closeAllOpenViews(launcher);
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index ff9dd13..18599ac 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_SEARCH;
+
 import android.app.ActivityOptions;
 import android.content.ActivityNotFoundException;
 import android.content.Intent;
@@ -32,10 +34,13 @@
 
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.uioverrides.DisplayRotationListener;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 
+import androidx.annotation.Nullable;
+
 /**
  * Extension of BaseActivity allowing support for drag-n-drop
  */
@@ -148,7 +153,8 @@
 
     public abstract ActivityOptions getActivityLaunchOptions(View v);
 
-    public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
+    public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item,
+            @Nullable String sourceContainer) {
         if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
             Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
             return false;
@@ -169,13 +175,17 @@
                     && !((ShortcutInfo) item).isPromise();
             if (isShortcut) {
                 // Shortcuts need some special checks due to legacy reasons.
-                startShortcutIntentSafely(intent, optsBundle, item);
+                startShortcutIntentSafely(intent, optsBundle, item, sourceContainer);
             } else if (user == null || user.equals(Process.myUserHandle())) {
                 // Could be launching some bookkeeping activity
                 startActivity(intent, optsBundle);
+                AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(),
+                        Process.myUserHandle(), sourceContainer);
             } else {
                 LauncherAppsCompat.getInstance(this).startActivityForProfile(
                         intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
+                AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(), user,
+                        sourceContainer);
             }
             getUserEventDispatcher().logAppLaunch(v, intent);
             getStatsLogManager().logAppLaunch(v, intent);
@@ -187,7 +197,8 @@
         return false;
     }
 
-    private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) {
+    private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info,
+            @Nullable String sourceContainer) {
         try {
             StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy();
             try {
@@ -202,6 +213,8 @@
                     String packageName = intent.getPackage();
                     DeepShortcutManager.getInstance(this).startShortcut(
                             packageName, id, intent.getSourceBounds(), optsBundle, info.user);
+                    AppLaunchTracker.INSTANCE.get(this).onStartShortcut(packageName, id, info.user,
+                            sourceContainer);
                 } else {
                     // Could be launching some bookkeeping activity
                     startActivity(intent, optsBundle);
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index d96855e..4dd2e0a 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -276,14 +276,6 @@
     }
 
     @Override
-    public void setTag(Object tag) {
-        if (tag != null) {
-            LauncherModel.checkItemInfo((ItemInfo) tag);
-        }
-        super.setTag(tag);
-    }
-
-    @Override
     public void refreshDrawableState() {
         if (!mIgnorePressedStateChange) {
             super.refreshDrawableState();
@@ -488,7 +480,8 @@
      */
     public ObjectAnimator createTextAlphaAnimator(boolean fadeIn) {
         float toAlpha = shouldTextBeVisible() && fadeIn ? 1 : 0;
-        return ObjectAnimator.ofFloat(this, TEXT_ALPHA_PROPERTY, toAlpha);
+        float fromAlpha = toAlpha == 1 ? 0 : 1f;
+        return ObjectAnimator.ofFloat(this, TEXT_ALPHA_PROPERTY, fromAlpha, toAlpha);
     }
 
     @Override
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 3b054c2..6a3a26f 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3;
 
-import android.appwidget.AppWidgetHostView;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -53,6 +51,9 @@
     public final int heightPx;
     public final int availableWidthPx;
     public final int availableHeightPx;
+
+    public final float aspectRatio;
+
     /**
      * The maximum amount of left/right workspace padding as a percentage of the screen width.
      * To be clear, this means that up to 7% of the screen width can be used as left padding, and
@@ -70,9 +71,6 @@
     public final int cellLayoutPaddingLeftRightPx;
     public final int cellLayoutBottomPaddingPx;
     public final int edgeMarginPx;
-    public final Rect defaultWidgetPadding;
-    public final int defaultPageSpacingPx;
-    private final int topWorkspacePadding;
     public float workspaceSpringLoadShrinkFactor;
     public final int workspaceSpringLoadedBottomSpace;
 
@@ -133,6 +131,7 @@
     private final Rect mInsets = new Rect();
     public final Rect workspacePadding = new Rect();
     private final Rect mHotseatPadding = new Rect();
+    // When true, nav bar is on the left side of the screen.
     private boolean mIsSeascape;
 
     // Notification dots
@@ -164,7 +163,7 @@
         isTablet = res.getBoolean(R.bool.is_tablet);
         isLargeTablet = res.getBoolean(R.bool.is_large_tablet);
         isPhone = !isTablet && !isLargeTablet;
-        float aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
+        aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
         boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
 
         // Some more constants
@@ -176,26 +175,25 @@
                 : Configuration.ORIENTATION_PORTRAIT);
         res = context.getResources();
 
-
-        ComponentName cn = new ComponentName(context.getPackageName(),
-                this.getClass().getName());
-        defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
         desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx;
+
         int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet
                 ? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1;
-        cellLayoutPaddingLeftRightPx = cellLayoutPaddingLeftRightMultiplier *
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
-        cellLayoutBottomPaddingPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_bottom_padding);
+        int cellLayoutPadding = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
+        if (isLandscape) {
+            cellLayoutPaddingLeftRightPx = 0;
+            cellLayoutBottomPaddingPx = cellLayoutPadding;
+        } else {
+            cellLayoutPaddingLeftRightPx = cellLayoutPaddingLeftRightMultiplier * cellLayoutPadding;
+            cellLayoutBottomPaddingPx = 0;
+        }
+
         verticalDragHandleSizePx = res.getDimensionPixelSize(
                 R.dimen.vertical_drag_handle_size);
         verticalDragHandleOverlapWorkspace =
                 res.getDimensionPixelSize(R.dimen.vertical_drag_handle_overlap_workspace);
-        defaultPageSpacingPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
-        topWorkspacePadding =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_top_padding);
+
         iconDrawablePaddingOriginalPx =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
         dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
@@ -359,7 +357,7 @@
 
         if (!isVerticalLayout) {
             int expectedWorkspaceHeight = availableHeightPx - hotseatBarSizePx
-                    - verticalDragHandleSizePx - topWorkspacePadding;
+                    - verticalDragHandleSizePx - edgeMarginPx;
             float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace;
             workspaceSpringLoadShrinkFactor = Math.min(
                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f,
@@ -470,15 +468,15 @@
                         ((inv.numColumns - 1) * cellWidthPx)));
                 availablePaddingX = (int) Math.min(availablePaddingX,
                         widthPx * MAX_HORIZONTAL_PADDING_PERCENT);
-                int availablePaddingY = Math.max(0, heightPx - topWorkspacePadding - paddingBottom
+                int availablePaddingY = Math.max(0, heightPx - edgeMarginPx - paddingBottom
                         - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx
                         - hotseatBarBottomPaddingPx);
-                padding.set(availablePaddingX / 2, topWorkspacePadding + availablePaddingY / 2,
+                padding.set(availablePaddingX / 2, edgeMarginPx + availablePaddingY / 2,
                         availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
             } else {
                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
                 padding.set(desiredWorkspaceLeftRightMarginPx,
-                        topWorkspacePadding,
+                        edgeMarginPx,
                         desiredWorkspaceLeftRightMarginPx,
                         paddingBottom);
             }
@@ -587,34 +585,40 @@
     /**
      * Gets an item's location on the home screen. This is useful if the home screen
      * is animating, otherwise use {@link View#getLocationOnScreen(int[])}.
-     *
-     * TODO(b/123900446): Handle landscape mode
      * @param pageDiff The page difference relative to the current page.
      */
     public void getItemLocation(int cellX, int cellY, int spanX, int spanY, int container,
             int pageDiff, Rect outBounds) {
         outBounds.setEmpty();
-        outBounds.left = mInsets.left
-                + workspacePadding.left + cellLayoutPaddingLeftRightPx + (cellX * getCellSize().x);
-        outBounds.top = mInsets.top;
         if (container == CONTAINER_HOTSEAT) {
-            outBounds.top += workspacePadding.top
-                    + (inv.numRows * getCellSize().y)
-                    + verticalDragHandleSizePx
-                    - verticalDragHandleOverlapWorkspace;
-            outBounds.bottom = outBounds.top + hotseatBarSizePx - hotseatBarBottomPaddingPx;
+            final int actualHotseatCellHeight;
+            if (isVerticalBarLayout()) {
+                actualHotseatCellHeight = availableHeightPx / inv.numRows;
+                if (mIsSeascape) {
+                    outBounds.left = mHotseatPadding.left;
+                } else {
+                    outBounds.left = availableWidthPx - hotseatBarSizePx + mHotseatPadding.left;
+                }
+                outBounds.right = outBounds.left + iconSizePx;
+                outBounds.top = mHotseatPadding.top
+                        + actualHotseatCellHeight * (inv.numRows - cellX - 1);
+                outBounds.bottom = outBounds.top + actualHotseatCellHeight;
+            } else {
+                actualHotseatCellHeight = hotseatBarSizePx - hotseatBarBottomPaddingPx
+                        - hotseatBarTopPaddingPx;
+                outBounds.left = mInsets.left + workspacePadding.left + cellLayoutPaddingLeftRightPx
+                        + (cellX * getCellSize().x);
+                outBounds.right = outBounds.left + getCellSize().x;
+                outBounds.top = mInsets.top + availableHeightPx - hotseatBarSizePx;
+                outBounds.bottom = outBounds.top + actualHotseatCellHeight;
+            }
         } else {
-            outBounds.top += workspacePadding.top + (cellY * getCellSize().y);
+            outBounds.left = mInsets.left + workspacePadding.left + cellLayoutPaddingLeftRightPx
+                    + (cellX * getCellSize().x) + (pageDiff * availableWidthPx);
+            outBounds.right = outBounds.left + (getCellSize().x * spanX);
+            outBounds.top = mInsets.top + workspacePadding.top + (cellY * getCellSize().y);
             outBounds.bottom = outBounds.top + (getCellSize().y * spanY);
-            outBounds.left += (pageDiff) * availableWidthPx;
         }
-        outBounds.right = outBounds.left + (getCellSize().x * spanX);
-    }
-
-    public float getAspectRatioWithInsets() {
-        int w = widthPx - mInsets.left - mInsets.right;
-        int h = heightPx - mInsets.top - mInsets.bottom;
-        return ((float) Math.max(w, h)) / Math.min(w, h);
     }
 
     private static Context getContext(Context c, int orientation) {
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index d025a9b..0543e8d 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -104,7 +104,7 @@
                         / (2 * (grid.inv.numColumns + 1)))
                         + grid.edgeMarginPx;
             } else {
-                gap = grid.desiredWorkspaceLeftRightMarginPx - grid.defaultWidgetPadding.right;
+                gap = grid.desiredWorkspaceLeftRightMarginPx - grid.inv.defaultWidgetPadding.right;
             }
             lp.width = grid.availableWidthPx - 2 * gap;
 
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 964e8b6..7ab88a0 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -103,9 +103,14 @@
     }
 
     protected FastBitmapDrawable(Bitmap b, int iconColor) {
+        this(b, iconColor, false);
+    }
+
+    protected FastBitmapDrawable(Bitmap b, int iconColor, boolean isDisabled) {
         mBitmap = b;
         mIconColor = iconColor;
         setFilterBitmap(true);
+        setIsDisabled(isDisabled);
     }
 
     @Override
@@ -249,6 +254,10 @@
         }
     }
 
+    protected boolean isDisabled() {
+        return mIsDisabled;
+    }
+
     /**
      * Sets the saturation of this icon, 0 [full color] -> 1 [desaturated]
      */
@@ -338,21 +347,23 @@
 
     @Override
     public ConstantState getConstantState() {
-        return new MyConstantState(mBitmap, mIconColor);
+        return new MyConstantState(mBitmap, mIconColor, mIsDisabled);
     }
 
     protected static class MyConstantState extends ConstantState {
         protected final Bitmap mBitmap;
         protected final int mIconColor;
+        protected final boolean mIsDisabled;
 
-        public MyConstantState(Bitmap bitmap, int color) {
+        public MyConstantState(Bitmap bitmap, int color, boolean isDisabled) {
             mBitmap = bitmap;
             mIconColor = color;
+            mIsDisabled = isDisabled;
         }
 
         @Override
         public Drawable newDrawable() {
-            return new FastBitmapDrawable(mBitmap, mIconColor);
+            return new FastBitmapDrawable(mBitmap, mIconColor, mIsDisabled);
         }
 
         @Override
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 6582df2..c4495c7 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -20,12 +20,18 @@
 import static com.android.launcher3.Utilities.getDevicePrefs;
 
 import android.annotation.TargetApi;
+import android.appwidget.AppWidgetHostView;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
@@ -111,9 +117,11 @@
     public DeviceProfile portraitProfile;
 
     public Point defaultWallpaperSize;
+    public Rect defaultWidgetPadding;
 
     private final ArrayList<OnIDPChangeListener> mChangeListeners = new ArrayList<>();
     private ConfigMonitor mConfigMonitor;
+    private OverlayMonitor mOverlayMonitor;
 
     @VisibleForTesting
     public InvariantDeviceProfile() {}
@@ -131,6 +139,7 @@
         defaultLayoutId = p.defaultLayoutId;
         demoModeLayoutId = p.demoModeLayoutId;
         mExtraAttrs = p.mExtraAttrs;
+        mOverlayMonitor = p.mOverlayMonitor;
     }
 
     @TargetApi(23)
@@ -138,8 +147,12 @@
         initGrid(context, Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null));
         mConfigMonitor = new ConfigMonitor(context,
                 APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
+        mOverlayMonitor = new OverlayMonitor(context);
     }
 
+    /**
+     * This constructor should NOT have any monitors by design.
+     */
     public InvariantDeviceProfile(Context context, String gridName) {
         String newName = initGrid(context, gridName);
         if (newName == null || !newName.equals(gridName)) {
@@ -226,6 +239,10 @@
         } else {
             defaultWallpaperSize = new Point(Math.max(smallSide * 2, largeSide), largeSide);
         }
+
+        ComponentName cn = new ComponentName(context.getPackageName(), getClass().getName());
+        defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
+
         return closestProfile.name;
     }
 
@@ -555,4 +572,20 @@
             return this;
         }
     }
+
+    private class OverlayMonitor extends BroadcastReceiver {
+
+        private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
+
+        OverlayMonitor(Context context) {
+            IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+            filter.addDataScheme("package");
+            context.registerReceiver(this, filter);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            onConfigChanged(context);
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index a130604..134e116 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -114,8 +114,6 @@
 
     ItemInfo(ItemInfo info) {
         copyFrom(info);
-        // tempdebug:
-        LauncherModel.checkItemInfo(this);
     }
 
     public void copyFrom(ItemInfo info) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d820448..f8d9959 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -97,6 +97,7 @@
 import com.android.launcher3.logging.StatsLogUtils;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate;
+import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.popup.PopupContainerWithArrow;
@@ -256,6 +257,7 @@
     public ViewGroupFocusHelper mFocusHandler;
 
     private RotationHelper mRotationHelper;
+    private Runnable mCancelTouchController;
 
     final Handler mHandler = new Handler();
     private final Runnable mHandleDeferredResume = this::handleDeferredResume;
@@ -378,14 +380,7 @@
         }
 
         if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
-            mUserEventDispatcher = null;
-            initDeviceProfile(mDeviceProfile.inv);
-            dispatchDeviceProfileChanged();
-            reapplyUi();
-            mDragLayer.recreateControllers();
-
-            // TODO: We can probably avoid rebind when only screen size changed.
-            rebindModel();
+            onIdpChanged(mDeviceProfile.inv);
         }
 
         mOldConfig.setTo(newConfig);
@@ -410,8 +405,26 @@
 
     @Override
     public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
+        onIdpChanged(idp);
+    }
+
+    public void setQuickSearchBarAlpha(float alpha) {
+        View qsbAllApps = findViewById(R.id.search_container_all_apps);
+        if (qsbAllApps != null) {
+            qsbAllApps.setAlpha(alpha);
+        }
+    }
+
+    private void onIdpChanged(InvariantDeviceProfile idp) {
+        mUserEventDispatcher = null;
+
         initDeviceProfile(idp);
-        getRootView().dispatchInsets();
+        dispatchDeviceProfileChanged();
+        reapplyUi();
+        mDragLayer.recreateControllers();
+
+        // TODO: We can probably avoid rebind when only screen size changed.
+        rebindModel();
     }
 
     private void initDeviceProfile(InvariantDeviceProfile idp) {
@@ -488,11 +501,15 @@
 
     @Override
     public void invalidateParent(ItemInfo info) {
-        FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(getDeviceProfile().inv);
-        if (verifier.isItemInPreview(info.rank) && (info.container >= 0)) {
+        if (info.container >= 0) {
             View folderIcon = getWorkspace().getHomescreenIconByItemId(info.container);
-            if (folderIcon != null) {
-                folderIcon.invalidate();
+            if (folderIcon instanceof FolderIcon && folderIcon.getTag() instanceof FolderInfo) {
+                FolderIconPreviewVerifier verifier =
+                        new FolderIconPreviewVerifier(getDeviceProfile().inv);
+                verifier.setFolderInfo((FolderInfo) folderIcon.getTag());
+                if (verifier.isItemInPreview(info.rank)) {
+                    folderIcon.invalidate();
+                }
             }
         }
     }
@@ -686,7 +703,7 @@
 
             if (grantResults.length > 0
                     && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-                startActivitySafely(v, intent, null);
+                startActivitySafely(v, intent, null, null);
             } else {
                 // TODO: Show a snack bar with link to settings
                 Toast.makeText(this, getString(R.string.msg_no_phone_permission,
@@ -790,6 +807,7 @@
             getUserEventDispatcher().startSession();
 
             UiFactory.onLauncherStateOrResumeChanged(this);
+            AppLaunchTracker.INSTANCE.get(this).onReturnedToHome();
         }
     }
 
@@ -936,7 +954,7 @@
 
         // Setup the drag layer
         mDragLayer.setup(mDragController, mWorkspace);
-        UiFactory.setOnTouchControllersChangedListener(this, mDragLayer::recreateControllers);
+        mCancelTouchController = UiFactory.enableLiveTouchControllerChanges(mDragLayer);
 
         mWorkspace.setup(mDragController);
         // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
@@ -1308,7 +1326,10 @@
         unregisterReceiver(mScreenOffReceiver);
         mWorkspace.removeFolderListeners();
 
-        UiFactory.setOnTouchControllersChangedListener(this, null);
+        if (mCancelTouchController != null) {
+            mCancelTouchController.run();
+            mCancelTouchController = null;
+        }
 
         // Stop callbacks from LauncherModel
         // It's possible to receive onDestroy after a new Launcher activity has
@@ -1644,8 +1665,9 @@
         }
     }
 
-    public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
-        boolean success = super.startActivitySafely(v, intent, item);
+    public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
+            @Nullable String sourceContainer) {
+        boolean success = super.startActivitySafely(v, intent, item, sourceContainer);
         if (success && v instanceof BubbleTextView) {
             // This is set to the view that launched the activity that navigated the user away
             // from launcher. Since there is no callback for when the activity has finished
@@ -1763,12 +1785,11 @@
     @Override
     public void bindScreens(IntArray orderedScreenIds) {
         // Make sure the first screen is always at the start.
-        if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() &&
+        if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
                 orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
             orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);
             orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID);
-        } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get()
-                && orderedScreenIds.isEmpty()) {
+        } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) {
             // If there are no screens, we need to have an empty screen
             mWorkspace.addExtraEmptyScreen();
         }
@@ -1784,7 +1805,7 @@
         int count = orderedScreenIds.size();
         for (int i = 0; i < count; i++) {
             int screenId = orderedScreenIds.get(i);
-            if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get() || screenId != Workspace.FIRST_SCREEN_ID) {
+            if (!FeatureFlags.QSB_ON_FIRST_SCREEN || screenId != Workspace.FIRST_SCREEN_ID) {
                 // No need to bind the first screen, as its always bound.
                 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
             }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 7bb6071..dc27516 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -131,6 +131,7 @@
         if ((changeFlags & CHANGE_FLAG_ICON_PARAMS) != 0) {
             LauncherIcons.clearPool();
             mIconCache.updateIconParams(idp.fillResIconDpi, idp.iconBitmapSize);
+            mWidgetCache.refresh();
         }
 
         mModel.forceReload();
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index b1664f1..c559f2b 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -34,7 +34,6 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.AddWorkspaceItemsTask;
@@ -207,57 +206,6 @@
                 hasVerticalHotseat, verifyChanges);
     }
 
-    static void checkItemInfoLocked(
-            final int itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
-        ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
-        if (modelItem != null && item != modelItem) {
-            // If it is a release build on a release device, check all the data is consistent as
-            // we don't want to crash non-dev users.
-            if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_DOGFOOD_BUILD &&
-                    modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
-                if (modelItem.title.toString().equals(item.title.toString()) &&
-                        modelItem.getIntent().filterEquals(item.getIntent()) &&
-                        modelItem.id == item.id &&
-                        modelItem.itemType == item.itemType &&
-                        modelItem.container == item.container &&
-                        modelItem.screenId == item.screenId &&
-                        modelItem.cellX == item.cellX &&
-                        modelItem.cellY == item.cellY &&
-                        modelItem.spanX == item.spanX &&
-                        modelItem.spanY == item.spanY) {
-                    // For all intents and purposes, this is the same object
-                    return;
-                }
-            }
-
-            // the modelItem needs to match up perfectly with item if our model is
-            // to be consistent with the database-- for now, just require
-            // modelItem == item or the equality check above
-            String msg = "item: " + ((item != null) ? item.toString() : "null") +
-                    "modelItem: " +
-                    ((modelItem != null) ? modelItem.toString() : "null") +
-                    "Error: ItemInfo passed to checkItemInfo doesn't match original";
-            RuntimeException e = new RuntimeException(msg);
-            if (stackTrace != null) {
-                e.setStackTrace(stackTrace);
-            }
-            throw e;
-        }
-    }
-
-    static void checkItemInfo(final ItemInfo item) {
-        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
-        final int itemId = item.id;
-        Runnable r = new Runnable() {
-            public void run() {
-                synchronized (sBgDataModel) {
-                    checkItemInfoLocked(itemId, item, stackTrace);
-                }
-            }
-        };
-        runOnWorkerThread(r);
-    }
-
     /**
      * Set this as the current Launcher activity object for the loader.
      */
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index fb33694..39d93c8 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
 
 import android.annotation.TargetApi;
+import android.app.backup.BackupManager;
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
@@ -150,7 +151,7 @@
             mOpenHelper = new DatabaseHelper(getContext(), mListenerHandler);
 
             if (RestoreDbTask.isPending(getContext())) {
-                if (!RestoreDbTask.performRestore(mOpenHelper)) {
+                if (!RestoreDbTask.performRestore(mOpenHelper, new BackupManager(getContext()))) {
                     mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
                 }
                 // Set is pending to false irrespective of the result, so that it doesn't get
@@ -542,6 +543,7 @@
      * The class is subclassed in tests to create an in-memory db.
      */
     public static class DatabaseHelper extends NoLocaleSQLiteHelper implements LayoutParserCallback {
+        private final BackupManager mBackupManager;
         private final Handler mWidgetHostResetHandler;
         private final Context mContext;
         private int mMaxItemId = -1;
@@ -571,6 +573,7 @@
             super(context, tableName, SCHEMA_VERSION);
             mContext = context;
             mWidgetHostResetHandler = widgetHostResetHandler;
+            mBackupManager = new BackupManager(mContext);
         }
 
         protected void initIds() {
@@ -620,9 +623,12 @@
             Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
         }
 
+        public long getSerialNumberForUser(UserHandle user) {
+            return UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user);
+        }
+
         public long getDefaultUserSerial() {
-            return UserManagerCompat.getInstance(mContext).getSerialNumberForUser(
-                    Process.myUserHandle());
+            return getSerialNumberForUser(Process.myUserHandle());
         }
 
         private void addFavoritesTable(SQLiteDatabase db, boolean optional) {
@@ -723,7 +729,7 @@
                     convertShortcutsToLauncherActivities(db);
                 case 26:
                     // QSB was moved to the grid. Clear the first row on screen 0.
-                    if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() &&
+                    if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
                             !LauncherDbUtils.prepareScreenZeroToHostQsb(mContext, db)) {
                         break;
                     }
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 9f6e5cd..e738eb7 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -8,11 +8,15 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.graphics.Paint;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
+import android.view.WindowInsets;
 
 public class LauncherRootView extends InsettableFrameLayout {
 
@@ -23,6 +27,9 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private final Rect mConsumedInsets = new Rect();
 
+    @ViewDebug.ExportedProperty(category = "launcher")
+    private final RectF mTouchExcludeRegion = new RectF();
+
     private View mAlignedView;
     private WindowStateListener mWindowStateListener;
 
@@ -145,6 +152,31 @@
         }
     }
 
+    @Override
+    public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
+        if (Utilities.ATLEAST_Q) {
+            Insets gestureInsets = insets.getMandatorySystemGestureInsets();
+            mTouchExcludeRegion.set(gestureInsets.left, gestureInsets.top,
+                    gestureInsets.right, gestureInsets.bottom);
+        }
+        return super.dispatchApplyWindowInsets(insets);
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            float x = ev.getX();
+            float y = ev.getY();
+            if (y < mTouchExcludeRegion.top
+                    || x < mTouchExcludeRegion.left
+                    || x > (getWidth() - mTouchExcludeRegion.right)
+                    || y > (getHeight() - mTouchExcludeRegion.bottom)) {
+                return false;
+            }
+        }
+        return super.dispatchTouchEvent(ev);
+    }
+
     public interface WindowStateListener {
 
         void onWindowFocusChanged(boolean hasFocus);
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index cee1c26..c65a871 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -18,22 +18,23 @@
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
-
 import static com.android.launcher3.TestProtocol.ALL_APPS_STATE_ORDINAL;
 import static com.android.launcher3.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
 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;
 
 import android.view.animation.Interpolator;
 
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.states.SpringLoadedState;
-import com.android.launcher3.uioverrides.AllAppsState;
-import com.android.launcher3.uioverrides.BackgroundAppState;
-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.
@@ -92,10 +93,15 @@
      */
     public static final LauncherState SPRING_LOADED = new SpringLoadedState(
             SPRING_LOADED_STATE_ORDINAL);
-    public static final LauncherState OVERVIEW = new OverviewState(OVERVIEW_STATE_ORDINAL);
     public static final LauncherState ALL_APPS = new AllAppsState(ALL_APPS_STATE_ORDINAL);
-    public static final LauncherState BACKGROUND_APP = new BackgroundAppState(
-            BACKGROUND_APP_STATE_ORDINAL);
+
+    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);
 
     public final int ordinal;
 
@@ -183,23 +189,22 @@
         return Arrays.copyOf(sAllStates, sAllStates.length);
     }
 
-    public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
-        return new float[] {1, 0, 0};
+    public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
+        return new ScaleAndTranslation(1, 0, 0);
     }
 
-    public float[] getHotseatScaleAndTranslation(Launcher launcher) {
+    public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
         // For most states, treat the hotseat as if it were part of the workspace.
         return getWorkspaceScaleAndTranslation(launcher);
     }
 
-    /**
-     * Returns 2 floats designating how to transition overview:
-     *   scale for the current and adjacent pages
-     *   translationY factor where 0 is top aligned and 0.5 is centered vertically
-     */
-    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
-        // TODO: Simplify to use a constant value instead of a factor.
-        return new float[] {1.1f, 0f};
+    public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
+        if (FeatureFlags.SWIPE_HOME.get()) {
+            float offscreenTranslationX = launcher.getDragLayer().getWidth()
+                    - launcher.getOverviewPanel().getPaddingStart();
+            return new ScaleAndTranslation(1f, offscreenTranslationX, 0f);
+        }
+        return new ScaleAndTranslation(1.1f, 0f, 0f);
     }
 
     public void onStateEnabled(Launcher launcher) {
@@ -287,4 +292,16 @@
 
         public abstract float getPageAlpha(int pageIndex);
     }
+
+    public static class ScaleAndTranslation {
+        public float scale;
+        public float translationX;
+        public float translationY;
+
+        public ScaleAndTranslation(float scale, float translationX, float translationY) {
+            this.scale = scale;
+            this.translationX = translationX;
+            this.translationY = translationY;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index f6b54f2..5b654d8 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -17,15 +17,21 @@
 package com.android.launcher3;
 
 import static android.view.View.VISIBLE;
+
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+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_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;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 
@@ -35,6 +41,8 @@
 import android.os.Handler;
 import android.os.Looper;
 
+import androidx.annotation.IntDef;
+
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
@@ -46,8 +54,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
-import androidx.annotation.IntDef;
-
 /**
  * TODO: figure out what kind of tests we can write for this
  *
@@ -96,17 +102,21 @@
     // We separate the state animations into "atomic" and "non-atomic" components. The atomic
     // components may be run atomically - that is, all at once, instead of user-controlled. However,
     // atomic components are not restricted to this purpose; they can be user-controlled alongside
-    // non atomic components as well.
+    // non atomic components as well. Note that each gesture model has exactly one atomic component,
+    // ATOMIC_OVERVIEW_SCALE_COMPONENT *or* ATOMIC_OVERVIEW_PEEK_COMPONENT.
     @IntDef(flag = true, value = {
             NON_ATOMIC_COMPONENT,
-            ATOMIC_COMPONENT
+            ATOMIC_OVERVIEW_SCALE_COMPONENT,
+            ATOMIC_OVERVIEW_PEEK_COMPONENT,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AnimationComponents {}
     public static final int NON_ATOMIC_COMPONENT = 1 << 0;
-    public static final int ATOMIC_COMPONENT = 1 << 1;
+    public static final int ATOMIC_OVERVIEW_SCALE_COMPONENT = 1 << 1;
+    public static final int ATOMIC_OVERVIEW_PEEK_COMPONENT = 1 << 2;
 
-    public static final int ANIM_ALL = NON_ATOMIC_COMPONENT | ATOMIC_COMPONENT;
+    public static final int ANIM_ALL = NON_ATOMIC_COMPONENT | ATOMIC_OVERVIEW_SCALE_COMPONENT
+            | ATOMIC_OVERVIEW_PEEK_COMPONENT;
 
     private final AnimationConfig mConfig = new AnimationConfig();
     private final Handler mUiHandler;
@@ -282,18 +292,20 @@
      */
     public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
             AnimatorSetBuilder builder) {
-        if (fromState == NORMAL && toState.overviewUi) {
+        if (fromState == NORMAL && toState == OVERVIEW) {
             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_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.
             UiFactory.prepareToShowOverview(mLauncher);
-        } else if (fromState.overviewUi && toState == NORMAL) {
+        } else if (fromState == OVERVIEW && toState == NORMAL) {
             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_X, ACCEL);
             builder.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
             Workspace workspace = mLauncher.getWorkspace();
 
@@ -311,9 +323,26 @@
                 workspace.getHotseat().setScaleX(0.92f);
                 workspace.getHotseat().setScaleY(0.92f);
             }
+        } else if (fromState == NORMAL && toState == OVERVIEW_PEEK) {
+            builder.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
+        } else if (fromState == OVERVIEW_PEEK && toState == NORMAL) {
+            // Keep fully visible until the very end (when overview is offscreen) to make invisible.
+            builder.setInterpolator(ANIM_OVERVIEW_FADE, t -> t < 1 ? 0 : 1);
         }
     }
 
+    public AnimatorSet createAtomicAnimation(LauncherState fromState, LauncherState toState,
+            AnimatorSetBuilder builder, @AnimationComponents int atomicComponent, long duration) {
+        prepareForAtomicAnimation(fromState, toState, builder);
+        AnimationConfig config = new AnimationConfig();
+        config.animComponents = atomicComponent;
+        config.duration = duration;
+        for (StateHandler handler : mLauncher.getStateManager().getStateHandlers()) {
+            handler.setStateWithAnimation(toState, builder, config);
+        }
+        return builder.build();
+    }
+
     /**
      * Creates a {@link AnimatorPlaybackController} that can be used for a controlled
      * state transition. The UI is force-set to fromState before creating the controller.
@@ -373,7 +402,6 @@
             AnimatorSetBuilder builder, final Runnable onCompleteRunnable) {
 
         for (StateHandler handler : getStateHandlers()) {
-            builder.startTag(handler);
             handler.setStateWithAnimation(state, builder, mConfig);
         }
 
@@ -594,8 +622,12 @@
             mCurrentAnimation.addListener(this);
         }
 
-        public boolean playAtomicComponent() {
-            return (animComponents & ATOMIC_COMPONENT) != 0;
+        public boolean playAtomicOverviewScaleComponent() {
+            return (animComponents & ATOMIC_OVERVIEW_SCALE_COMPONENT) != 0;
+        }
+
+        public boolean playAtomicOverviewPeekComponent() {
+            return (animComponents & ATOMIC_OVERVIEW_PEEK_COMPONENT) != 0;
         }
 
         public boolean playNonAtomicComponent() {
diff --git a/src/com/android/launcher3/TestProtocol.java b/src/com/android/launcher3/TestProtocol.java
index 23df79e..7d3715e 100644
--- a/src/com/android/launcher3/TestProtocol.java
+++ b/src/com/android/launcher3/TestProtocol.java
@@ -28,6 +28,42 @@
     public static final int NORMAL_STATE_ORDINAL = 0;
     public static final int SPRING_LOADED_STATE_ORDINAL = 1;
     public static final int OVERVIEW_STATE_ORDINAL = 2;
-    public static final int ALL_APPS_STATE_ORDINAL = 3;
-    public static final int BACKGROUND_APP_STATE_ORDINAL = 4;
+    public static final int OVERVIEW_PEEK_STATE_ORDINAL = 3;
+    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 String stateOrdinalToString(int ordinal) {
+        switch (ordinal) {
+            case NORMAL_STATE_ORDINAL:
+                return "Normal";
+            case SPRING_LOADED_STATE_ORDINAL:
+                return "SpringLoaded";
+            case OVERVIEW_STATE_ORDINAL:
+                return "Overview";
+            case OVERVIEW_PEEK_STATE_ORDINAL:
+                return "OverviewPeek";
+            case QUICK_SWITCH_STATE_ORDINAL:
+                return "QuickSwitch";
+            case ALL_APPS_STATE_ORDINAL:
+                return "AllApps";
+            case BACKGROUND_APP_STATE_ORDINAL:
+                return "Background";
+            default:
+                return null;
+        }
+    }
+
+    public static final String TEST_INFO_RESPONSE_FIELD = "response";
+    public static final String REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT =
+            "home-to-overview-swipe-height";
+    public static final String REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT =
+            "background-to-overview-swipe-height";
+    public static final String REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT =
+            "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 e699500..fd4b508 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -54,6 +54,7 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.TypedValue;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.Interpolator;
 
@@ -115,6 +116,11 @@
     public static final int SINGLE_FRAME_MS = 16;
 
     /**
+     * Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
+     */
+    public static final int EDGE_NAV_BAR = 1 << 8;
+
+    /**
      * Indicates if the device has a debug build. Should only be used to store additional info or
      * add extra logging and not for changing the app behavior.
      */
@@ -302,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));
     }
@@ -571,9 +581,11 @@
         final boolean isFolderIcon = v instanceof FolderIcon;
         final Rect rect = new Rect();
 
+        // Deep shortcut views have their icon drawn in a separate view.
         final boolean fromDeepShortcutView = v.getParent() instanceof DeepShortcutView;
-        if (fromDeepShortcutView) {
-            // Deep shortcut views have their icon drawn in a separate view.
+        if (v instanceof DeepShortcutView) {
+            dragLayer.getDescendantRectRelativeToSelf(((DeepShortcutView) v).getIconView(), rect);
+        } else if (fromDeepShortcutView) {
             DeepShortcutView view = (DeepShortcutView) v.getParent();
             dragLayer.getDescendantRectRelativeToSelf(view.getIconView(), rect);
         } else if ((isBubbleTextView || isFolderIcon) && v.getTag() instanceof ItemInfo
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 050849c..6d1bc1a 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -24,6 +24,7 @@
 import android.os.AsyncTask;
 import android.os.CancellationSignal;
 import android.os.Handler;
+import android.os.Process;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.LongSparseArray;
@@ -73,7 +74,6 @@
     private final Context mContext;
     private final IconCache mIconCache;
     private final UserManagerCompat mUserManager;
-    private final AppWidgetManagerCompat mWidgetManager;
     private final CacheDb mDb;
 
     private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
@@ -82,7 +82,6 @@
     public WidgetPreviewLoader(Context context, IconCache iconCache) {
         mContext = context;
         mIconCache = iconCache;
-        mWidgetManager = AppWidgetManagerCompat.getInstance(context);
         mUserManager = UserManagerCompat.getInstance(context);
         mDb = new CacheDb(context);
         mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
@@ -107,6 +106,10 @@
         return signal;
     }
 
+    public void refresh() {
+        mDb.clear();
+
+    }
     /**
      * The DB holds the generated previews for various components. Previews can also have different
      * sizes (landscape vs portrait).
@@ -474,8 +477,9 @@
         RectF boxRect = drawBoxWithShadow(c, size, size);
 
         LauncherIcons li = LauncherIcons.obtain(mContext);
-        Bitmap icon = li.createScaledBitmapWithoutShadow(
-                mutateOnMainThread(info.getFullResIcon(mIconCache)), 0);
+        Bitmap icon = li.createBadgedIconBitmap(
+                mutateOnMainThread(info.getFullResIcon(mIconCache)),
+                Process.myUserHandle(), 0).icon;
         li.recycle();
 
         Rect src = new Rect(0, 0, icon.getWidth(), icon.getHeight());
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 9427675..d24a5a6 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -298,12 +298,12 @@
 
         if (grid.shouldFadeAdjacentWorkspaceScreens()) {
             // In landscape mode the page spacing is set to the default.
-            setPageSpacing(grid.defaultPageSpacingPx);
+            setPageSpacing(grid.edgeMarginPx);
         } else {
             // In portrait, we want the pages spaced such that there is no
             // overhang of the previous / next page into the current page viewport.
             // We assume symmetrical padding in portrait mode.
-            setPageSpacing(Math.max(grid.defaultPageSpacingPx, padding.left + 1));
+            setPageSpacing(Math.max(grid.edgeMarginPx, padding.left + 1));
         }
 
         int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
@@ -487,7 +487,7 @@
      * @param qsb an existing qsb to recycle or null.
      */
     public void bindAndInitFirstWorkspaceScreen(View qsb) {
-        if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
+        if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
             return;
         }
         // Add the first page
@@ -794,7 +794,7 @@
             int id = mWorkspaceScreens.keyAt(i);
             CellLayout cl = mWorkspaceScreens.valueAt(i);
             // FIRST_SCREEN_ID can never be removed.
-            if ((!FeatureFlags.QSB_ON_FIRST_SCREEN.get() || id > FIRST_SCREEN_ID)
+            if ((!FeatureFlags.QSB_ON_FIRST_SCREEN || id > FIRST_SCREEN_ID)
                     && cl.getShortcutsAndWidgets().getChildCount() == 0) {
                 removeScreens.add(id);
             }
@@ -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 0507470..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;
@@ -31,6 +32,7 @@
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.LauncherState.PageAlphaProvider;
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
@@ -71,9 +73,10 @@
      */
     private void setWorkspaceProperty(LauncherState state, PropertySetter propertySetter,
             AnimatorSetBuilder builder, AnimationConfig config) {
-        float[] scaleAndTranslation = state.getWorkspaceScaleAndTranslation(mLauncher);
-        float[] hotseatScaleAndTranslation = state.getHotseatScaleAndTranslation(mLauncher);
-        mNewScale = scaleAndTranslation[0];
+        ScaleAndTranslation scaleAndTranslation = state.getWorkspaceScaleAndTranslation(mLauncher);
+        ScaleAndTranslation hotseatScaleAndTranslation = state.getHotseatScaleAndTranslation(
+                mLauncher);
+        mNewScale = scaleAndTranslation.scale;
         PageAlphaProvider pageAlphaProvider = state.getWorkspacePageAlphaProvider(mLauncher);
         final int childCount = mWorkspace.getChildCount();
         for (int i = 0; i < childCount; i++) {
@@ -84,7 +87,7 @@
         int elements = state.getVisibleElements(mLauncher);
         Interpolator fadeInterpolator = builder.getInterpolator(ANIM_WORKSPACE_FADE,
                 pageAlphaProvider.interpolator);
-        boolean playAtomicComponent = config.playAtomicComponent();
+        boolean playAtomicComponent = config.playAtomicOverviewScaleComponent();
         Hotseat hotseat = mWorkspace.getHotseat();
         if (playAtomicComponent) {
             Interpolator scaleInterpolator = builder.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
@@ -98,7 +101,7 @@
             dragLayer.mapCoordInSelfToDescendant(hotseat, workspacePivot);
             hotseat.setPivotX(workspacePivot[0]);
             hotseat.setPivotY(workspacePivot[1]);
-            float hotseatScale = hotseatScaleAndTranslation[0];
+            float hotseatScale = hotseatScaleAndTranslation.scale;
             propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale, scaleInterpolator);
 
             float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
@@ -112,16 +115,18 @@
             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[1], translationInterpolator);
+                scaleAndTranslation.translationX, translationInterpolator);
         propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
-                scaleAndTranslation[2], translationInterpolator);
+                scaleAndTranslation.translationY, translationInterpolator);
 
         propertySetter.setFloat(hotseat, View.TRANSLATION_Y,
-                hotseatScaleAndTranslation[2], translationInterpolator);
+                hotseatScaleAndTranslation.translationY, translationInterpolator);
         propertySetter.setFloat(mWorkspace.getPageIndicator(), View.TRANSLATION_Y,
-                hotseatScaleAndTranslation[2], translationInterpolator);
+                hotseatScaleAndTranslation.translationY, translationInterpolator);
 
         // Set scrim
         WorkspaceAndHotseatScrim scrim = mLauncher.getDragLayer().getScrim();
@@ -145,7 +150,7 @@
             propertySetter.setInt(cl.getScrimBackground(),
                     DRAWABLE_ALPHA, drawableAlpha, ZOOM_OUT);
         }
-        if (config.playAtomicComponent()) {
+        if (config.playAtomicOverviewScaleComponent()) {
             Interpolator fadeInterpolator = builder.getInterpolator(ANIM_WORKSPACE_FADE,
                     pageAlphaProvider.interpolator);
             propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 69b4bdb..3cfa0b1 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -32,6 +32,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.PackageManagerHelper;
@@ -263,12 +264,8 @@
             case VIEW_TYPE_SEARCH_MARKET:
                 View searchMarketView = mLayoutInflater.inflate(R.layout.all_apps_search_market,
                         parent, false);
-                searchMarketView.setOnClickListener(new View.OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        mLauncher.startActivitySafely(v, mMarketSearchIntent, null);
-                    }
-                });
+                searchMarketView.setOnClickListener(v -> mLauncher.startActivitySafely(
+                        v, mMarketSearchIntent, null, AppLaunchTracker.CONTAINER_SEARCH));
                 return new ViewHolder(searchMarketView);
             case VIEW_TYPE_ALL_APPS_DIVIDER:
                 return new ViewHolder(mLayoutInflater.inflate(
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 93bf69d..4a1d432 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -188,9 +188,12 @@
 
     private void setAlphas(LauncherState toState, AnimationConfig config,
             AnimatorSetBuilder builder) {
+        setAlphas(toState.getVisibleElements(mLauncher), config, builder);
+    }
+
+    public void setAlphas(int visibleElements, AnimationConfig config, AnimatorSetBuilder builder) {
         PropertySetter setter = config == null ? NO_ANIM_PROPERTY_SETTER
                 : config.getPropertySetter(builder);
-        int visibleElements = toState.getVisibleElements(mLauncher);
         boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
         boolean hasContent = (visibleElements & ALL_APPS_CONTENT) != 0;
 
@@ -202,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/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 91be504..4515dde 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -28,6 +28,7 @@
 import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageManagerHelper;
 
@@ -111,7 +112,8 @@
             return false;
         }
         return mLauncher.startActivitySafely(v,
-                PackageManagerHelper.getMarketSearchIntent(mLauncher, query), null);
+                PackageManagerHelper.getMarketSearchIntent(mLauncher, query), null,
+                AppLaunchTracker.CONTAINER_SEARCH);
     }
 
     @Override
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index fdac5c8..5c498f8 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -30,20 +30,21 @@
 
     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_FADE = 4;
-    public static final int ANIM_ALL_APPS_FADE = 5;
+    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;
 
     protected final ArrayList<Animator> mAnims = new ArrayList<>();
 
     private final SparseArray<Interpolator> mInterpolators = new SparseArray<>();
     private List<Runnable> mOnFinishRunnables = new ArrayList<>();
-
-    /**
-     * Associates a tag with all the animations added after this call.
-     */
-    public void startTag(Object obj) { }
+    private int mFlags = 0;
 
     public void play(Animator anim) {
         mAnims.add(anim);
@@ -77,4 +78,12 @@
     public void setInterpolator(int animId, Interpolator interpolator) {
         mInterpolators.put(animId, interpolator);
     }
+
+    public void addFlag(int flag) {
+        mFlags |= flag;
+    }
+
+    public boolean hasFlag(int flag) {
+        return (mFlags & flag) != 0;
+    }
 }
diff --git a/src/com/android/launcher3/anim/FlingSpringAnim.java b/src/com/android/launcher3/anim/FlingSpringAnim.java
new file mode 100644
index 0000000..3d21d82
--- /dev/null
+++ b/src/com/android/launcher3/anim/FlingSpringAnim.java
@@ -0,0 +1,60 @@
+/*
+ * 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.anim;
+
+import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
+import androidx.dynamicanimation.animation.FlingAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+/**
+ * Given a property to animate and a target value and starting velocity, first apply friction to
+ * the fling until we pass the target, then apply a spring force to pull towards the target.
+ */
+public class FlingSpringAnim {
+
+    private static final float FLING_FRICTION = 1.5f;
+    // Have the spring pull towards the target if we've slowed down too much before reaching it.
+    private static final float FLING_END_THRESHOLD_PX = 50f;
+    private static final float SPRING_STIFFNESS = 350f;
+    private static final float SPRING_DAMPING = SpringForce.DAMPING_RATIO_LOW_BOUNCY;
+
+    private final FlingAnimation mFlingAnim;
+
+    public <K> FlingSpringAnim(K object, FloatPropertyCompat<K> property, float startPosition,
+            float targetPosition, float startVelocity, OnAnimationEndListener onEndListener) {
+        mFlingAnim = new FlingAnimation(object, property)
+                .setFriction(FLING_FRICTION)
+                .setMinimumVisibleChange(FLING_END_THRESHOLD_PX)
+                .setStartVelocity(startVelocity)
+                .setMinValue(Math.min(startPosition, targetPosition))
+                .setMaxValue(Math.max(startPosition, targetPosition));
+        mFlingAnim.addEndListener(((animation, canceled, value, velocity) -> {
+            SpringAnimation springAnim = new SpringAnimation(object, property)
+                    .setStartVelocity(velocity)
+                    .setSpring(new SpringForce(targetPosition)
+                            .setStiffness(SPRING_STIFFNESS)
+                            .setDampingRatio(SPRING_DAMPING));
+            springAnim.addEndListener(onEndListener);
+            springAnim.start();
+        }));
+    }
+
+    public void start() {
+        mFlingAnim.start();
+    }
+}
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 675e26d..b169cb8 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -57,6 +57,8 @@
 
     public static final Interpolator EXAGGERATED_EASE;
 
+    public static final Interpolator INSTANT = t -> 1;
+
     private static final int MIN_SETTLE_DURATION = 200;
     private static final float OVERSHOOT_FACTOR = 0.9f;
 
@@ -69,6 +71,7 @@
     }
 
     public static final Interpolator OVERSHOOT_1_2 = new OvershootInterpolator(1.2f);
+    public static final Interpolator OVERSHOOT_1_7 = new OvershootInterpolator(1.7f);
 
     public static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
             new PathInterpolator(0.3f, 0f, 0.1f, 1f);
diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java
index cc70e32..f74590b 100644
--- a/src/com/android/launcher3/anim/SpringObjectAnimator.java
+++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.anim;
 
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -32,8 +34,6 @@
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
-
 /**
  * This animator allows for an object's property to be be controlled by an {@link ObjectAnimator} or
  * a {@link SpringAnimation}. It extends ValueAnimator so it can be used in an AnimatorSet.
@@ -51,9 +51,9 @@
     private SpringProperty<T> mProperty;
 
     private ArrayList<AnimatorListener> mListeners;
-    private boolean mSpringEnded = false;
-    private boolean mAnimatorEnded = false;
-    private boolean mEnded = false;
+    private boolean mSpringEnded = true;
+    private boolean mAnimatorEnded = true;
+    private boolean mEnded = true;
 
     private static final FloatPropertyCompat<ProgressInterface> sFloatProperty =
             new FloatPropertyCompat<ProgressInterface>("springObjectAnimator") {
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index d6e450a..656151f 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -21,6 +21,9 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
 import android.provider.Settings;
 
 import androidx.annotation.GuardedBy;
@@ -65,9 +68,8 @@
     // When enabled the promise icon is visible in all apps while installation an app.
     public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false;
 
-    public static final TogglableFlag QSB_ON_FIRST_SCREEN = new TogglableFlag("QSB_ON_FIRST_SCREEN",
-            true,
-            "Enable moving the QSB on the 0th screen of the workspace");
+    // Enable moving the QSB on the 0th screen of the workspace
+    public static final boolean QSB_ON_FIRST_SCREEN = true;
 
     public static final TogglableFlag EXAMPLE_FLAG = new TogglableFlag("EXAMPLE_FLAG", true,
             "An example flag that doesn't do anything. Useful for testing");
@@ -116,10 +118,6 @@
             "ENABLE_HINTS_IN_OVERVIEW", false,
             "Show chip hints and gleams on the overview screen");
 
-    public static final TogglableFlag ENABLE_ASSISTANT_GESTURE = new ToggleableGlobalSettingsFlag(
-            "ENABLE_ASSISTANT_GESTURE", false,
-            "Enable swipe up from the bottom right corner to start assistant");
-
     public static void initialize(Context context) {
         // Avoid the disk read for user builds
         if (Utilities.IS_DEBUG_DEVICE) {
@@ -178,7 +176,7 @@
             currentValue = getFromStorage(context, defaultValue);
         }
 
-        void updateStorage(Context context, boolean value) {
+        public void updateStorage(Context context, boolean value) {
             SharedPreferences.Editor editor = context.getSharedPreferences(FLAGS_PREF_NAME,
                     Context.MODE_PRIVATE).edit();
             if (value == defaultValue) {
@@ -256,11 +254,21 @@
         @Override
         public void initialize(Context context) {
             contentResolver = context.getContentResolver();
+            contentResolver.registerContentObserver(Settings.Global.getUriFor(getKey()), true,
+                    new ContentObserver(new Handler(Looper.getMainLooper())) {
+                        @Override
+                        public void onChange(boolean selfChange) {
+                            superInitialize(context);
+                    }});
+            superInitialize(context);
+        }
+
+        private void superInitialize(Context context) {
             super.initialize(context);
         }
 
         @Override
-        void updateStorage(Context context, boolean value) {
+        public void updateStorage(Context context, boolean value) {
             if (contentResolver == null) {
                 return;
             }
@@ -274,10 +282,5 @@
             }
             return Settings.Global.getInt(contentResolver, getKey(), defaultValue ? 1 : 0) == 1;
         }
-
-        @Override
-        public boolean get() {
-            return getFromStorage(null, getDefaultValue());
-        }
     }
 }
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 f005ce7..9f902ed 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -63,8 +63,7 @@
     public static final int ALPHA_INDEX_OVERLAY = 0;
     public static final int ALPHA_INDEX_LAUNCHER_LOAD = 1;
     public static final int ALPHA_INDEX_TRANSITIONS = 2;
-    public static final int ALPHA_INDEX_SWIPE_UP = 3;
-    private static final int ALPHA_CHANNEL_COUNT = 4;
+    private static final int ALPHA_CHANNEL_COUNT = 3;
 
     public static final int ANIMATION_END_DISAPPEAR = 0;
     public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
@@ -127,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/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 7a14b36..bcddd03 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -618,8 +618,7 @@
             mFolderIcon.setBackgroundVisible(true);
             mFolderIcon.mFolderName.setTextVisibility(true);
             if (wasAnimated) {
-                mFolderIcon.mBackground.fadeInBackgroundShadow();
-                mFolderIcon.mBackground.animateBackgroundStroke();
+                mFolderIcon.animateBgShadowAndStroke();
                 mFolderIcon.onFolderClose(mContent.getCurrentPage());
                 if (mFolderIcon.hasDot()) {
                     mFolderIcon.animateDotScale(0f, 1f);
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 67495ea..bcd5701 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -189,7 +189,17 @@
         return icon;
     }
 
+    public void animateBgShadowAndStroke() {
+        mBackground.fadeInBackgroundShadow();
+        mBackground.animateBackgroundStroke();
+    }
+
+    public BubbleTextView getFolderName() {
+        return mFolderName;
+    }
+
     public void getPreviewBounds(Rect outBounds) {
+        mPreviewItemManager.recomputePreviewDrawingParams();
         mBackground.getBounds(outBounds);
     }
 
@@ -200,6 +210,7 @@
     private void setFolder(Folder folder) {
         mFolder = folder;
         mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv);
+        mPreviewVerifier.setFolderInfo(mFolder.getInfo());
         updatePreviewItems(false);
     }
 
diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
index 5a27cd4..4c84e35 100644
--- a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
+++ b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.folder;
 
+import android.util.Log;
+
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.InvariantDeviceProfile;
 
@@ -26,14 +28,20 @@
  */
 public class FolderIconPreviewVerifier {
 
+    private static final String TAG = "FolderPreviewVerifier";
+
     private final int mMaxGridCountX;
     private final int mMaxGridCountY;
     private final int mMaxItemsPerPage;
-    private final int[] mGridSize = new int[2];
+    private final int[] mGridSize = new int[] { 1, 1 };
 
+    private int mNumItemsInFolder;
     private int mGridCountX;
     private boolean mDisplayingUpperLeftQuadrant = false;
 
+    /**
+     * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work.
+     */
     public FolderIconPreviewVerifier(InvariantDeviceProfile profile) {
         mMaxGridCountX = profile.numFolderColumns;
         mMaxGridCountY = profile.numFolderRows;
@@ -42,11 +50,14 @@
 
     public void setFolderInfo(FolderInfo info) {
         int numItemsInFolder = info.contents.size();
-        FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, mMaxGridCountX,
-                mMaxGridCountY, mMaxItemsPerPage, mGridSize);
-        mGridCountX = mGridSize[0];
+        if (numItemsInFolder != mNumItemsInFolder) {
+            FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, mMaxGridCountX,
+                    mMaxGridCountY, mMaxItemsPerPage, mGridSize);
+            mGridCountX = mGridSize[0];
 
-        mDisplayingUpperLeftQuadrant = numItemsInFolder > MAX_NUM_ITEMS_IN_PREVIEW;
+            mDisplayingUpperLeftQuadrant = numItemsInFolder > MAX_NUM_ITEMS_IN_PREVIEW;
+            mNumItemsInFolder = numItemsInFolder;
+        }
     }
 
     /**
@@ -62,6 +73,10 @@
      * @return True iff the icon is in the 2x2 upper left quadrant of the Folder.
      */
     public boolean isItemInPreview(int page, int rank) {
+        if (mGridSize[0] == 1) {
+            Log.w(TAG, "setFolderInfo not called before checking if item is in preview.");
+        }
+
         // First page items are laid out such that the first 4 items are always in the upper
         // left quadrant. For all other pages, we need to check the row and col.
         if (page > 0 || mDisplayingUpperLeftQuadrant) {
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 06eaf38..d4ce6dd 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -321,6 +321,7 @@
 
         FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(
                 Launcher.getLauncher(getContext()).getDeviceProfile().inv);
+        verifier.setFolderInfo(mFolder.getInfo());
         rank = 0;
         for (int i = 0; i < itemCount; i++) {
             View v = list.size() > i ? list.get(i) : null;
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 837749d..eb45be1 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -262,7 +262,7 @@
             }
 
             // Add first page QSB
-            if (FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
+            if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
                 View qsb = mHomeElementInflater.inflate(
                         R.layout.search_container_workspace, mWorkspace, false);
                 CellLayout.LayoutParams lp =
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index 4ef8626..d208077 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -15,7 +15,8 @@
  */
 package com.android.launcher3.logging;
 
-import android.content.Context;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.NAVBAR;
+
 import android.util.ArrayMap;
 import android.util.SparseArray;
 import android.view.View;
@@ -97,7 +98,8 @@
             case Target.Type.CONTAINER:
                 str = getFieldName(t.containerType, ContainerType.class);
                 if (t.containerType == ContainerType.WORKSPACE ||
-                        t.containerType == ContainerType.HOTSEAT) {
+                        t.containerType == ContainerType.HOTSEAT ||
+                        t.containerType == NAVBAR) {
                     str += " id=" + t.pageIndex;
                 } else if (t.containerType == ContainerType.FOLDER) {
                     str += " grid(" + t.gridX + "," + t.gridY+ ")";
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index e115168..c8a4e2a 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -34,7 +34,8 @@
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.View;
-import android.view.ViewParent;
+
+import androidx.annotation.Nullable;
 
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.ItemInfo;
@@ -54,15 +55,12 @@
 import java.util.Locale;
 import java.util.UUID;
 
-import androidx.annotation.Nullable;
-
 /**
  * Manages the creation of {@link LauncherEvent}.
  * To debug this class, execute following command before side loading a new apk.
  *
  * $ adb shell setprop log.tag.UserEvent VERBOSE
  */
-@Deprecated
 public class UserEventDispatcher implements ResourceBasedOverride {
 
     private static final String TAG = "UserEvent";
@@ -360,6 +358,27 @@
         dispatchUserEvent(event, null);
     }
 
+    public void logActionBack(boolean completed, int downX, int downY, boolean isButton,
+            boolean gestureSwipeLeft, int containerType) {
+        int actionTouch = isButton ? Action.Touch.TAP : Action.Touch.SWIPE;
+        Action action = newCommandAction(actionTouch);
+        action.command = Action.Command.BACK;
+        action.dir = isButton
+                ? Action.Direction.NONE
+                : gestureSwipeLeft
+                        ? Action.Direction.LEFT
+                        : Action.Direction.RIGHT;
+        Target target = newControlTarget(isButton
+                ? LauncherLogProto.ControlType.BACK_BUTTON
+                : LauncherLogProto.ControlType.BACK_GESTURE);
+        target.spanX = downX;
+        target.spanY = downY;
+        target.cardinality = completed ? 1 : 0;
+        LauncherEvent event = newLauncherEvent(action, target, newContainerTarget(containerType));
+
+        dispatchUserEvent(event, null);
+    }
+
     /**
      * Currently logs following containers: workspace, allapps, widget tray.
      * @param reason
diff --git a/src/com/android/launcher3/model/AppLaunchTracker.java b/src/com/android/launcher3/model/AppLaunchTracker.java
new file mode 100644
index 0000000..1613d47
--- /dev/null
+++ b/src/com/android/launcher3/model/AppLaunchTracker.java
@@ -0,0 +1,56 @@
+/*
+ * 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.model;
+
+import static com.android.launcher3.util.ResourceBasedOverride.Overrides.getObject;
+
+import android.content.ComponentName;
+import android.os.UserHandle;
+
+import com.android.launcher3.R;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Callback for receiving various app launch events
+ */
+public class AppLaunchTracker implements ResourceBasedOverride {
+
+    /**
+     * Derived from LauncherEvent proto.
+     * TODO: Use proper descriptive constants
+     */
+    public static final String CONTAINER_DEFAULT = Integer.toString(ContainerType.WORKSPACE);
+    public static final String CONTAINER_ALL_APPS = Integer.toString(ContainerType.ALLAPPS);
+    public static final String CONTAINER_PREDICTIONS = Integer.toString(ContainerType.PREDICTION);
+    public static final String CONTAINER_SEARCH = Integer.toString(ContainerType.SEARCHRESULT);
+
+
+    public static final MainThreadInitializedObject<AppLaunchTracker> INSTANCE =
+            new MainThreadInitializedObject<>(c ->
+                    getObject(AppLaunchTracker.class, c, R.string.app_launch_tracker_class));
+
+    public void onStartShortcut(String packageName, String shortcutId, UserHandle user,
+            @Nullable String container) { }
+
+    public void onStartApp(ComponentName componentName, UserHandle user,
+            @Nullable String container) { }
+
+    public void onReturnedToHome() { }
+}
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 210f744..97cf267 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -61,6 +61,8 @@
 
     protected final WeakReference<Callbacks> mCallbacks;
 
+    private int mMyBindingId;
+
     public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel,
             AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
         mUiExecutor = new MainThreadExecutor();
@@ -94,6 +96,7 @@
             appWidgets.addAll(mBgDataModel.appWidgets);
             orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
             mBgDataModel.lastBindId++;
+            mMyBindingId = mBgDataModel.lastBindId;
         }
 
         final int currentScreen;
@@ -285,6 +288,10 @@
 
     protected void executeCallbacksTask(CallbackTask task, Executor executor) {
         executor.execute(() -> {
+            if (mMyBindingId != mBgDataModel.lastBindId) {
+                Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind");
+                return;
+            }
             Callbacks callbacks = mCallbacks.get();
             if (callbacks != null) {
                 task.execute(callbacks);
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index b338fff..e8cc8f9 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -129,7 +129,7 @@
                 screenSet.add(item.screenId);
             }
         }
-        if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() || screenSet.isEmpty()) {
+        if (FeatureFlags.QSB_ON_FIRST_SCREEN || screenSet.isEmpty()) {
             screenSet.add(Workspace.FIRST_SCREEN_ID);
         }
         return screenSet.getArray();
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index 243b286..faecc06 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -253,8 +253,7 @@
      */
     protected void migrateScreen(int screenId) {
         // If we are migrating the first screen, do not touch the first row.
-        int startY =
-                (FeatureFlags.QSB_ON_FIRST_SCREEN.get() && screenId == Workspace.FIRST_SCREEN_ID)
+        int startY = (FeatureFlags.QSB_ON_FIRST_SCREEN && screenId == Workspace.FIRST_SCREEN_ID)
                 ? 1 : 0;
 
         ArrayList<DbEntry> items = loadWorkspaceEntries(screenId);
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index d104a8b..88193d0 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -442,8 +442,7 @@
             if (item.screenId == Workspace.FIRST_SCREEN_ID) {
                 // Mark the first row as occupied (if the feature is enabled)
                 // in order to account for the QSB.
-                screen.markCells(0, 0, countX + 1, 1,
-                    FeatureFlags.QSB_ON_FIRST_SCREEN.get());
+                screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN);
             }
             occupied.put(item.screenId, screen);
         }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 7275576..2b20b08 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -340,8 +340,6 @@
                 Intent intent;
                 String targetPkg;
 
-                FolderIconPreviewVerifier verifier =
-                        new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile());
                 while (!mStopped && c.moveToNext()) {
                     try {
                         if (c.user == null) {
@@ -366,13 +364,13 @@
                             ComponentName cn = intent.getComponent();
                             targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
 
-                            if (!Process.myUserHandle().equals(c.user)) {
+                            if (allUsers.indexOfValue(c.user) < 0) {
                                 if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
-                                    c.markDeleted("Legacy shortcuts are only allowed for default user");
+                                    c.markDeleted("Legacy shortcuts are only allowed for current users");
                                     continue;
                                 } else if (c.restoreFlag != 0) {
                                     // Don't restore items for other profiles.
-                                    c.markDeleted("Restore from managed profile not supported");
+                                    c.markDeleted("Restore from other profiles not supported");
                                     continue;
                                 }
                             }
@@ -461,8 +459,7 @@
                                 c.markRestored();
                             }
 
-                            boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() &&
-                                    !verifier.isItemInPreview(c.getInt(rankIndex));
+                            boolean useLowResIcon = !c.isOnWorkspaceOrHotseat();
 
                             if (c.restoreFlag != 0) {
                                 // Already verified above that user is same as default user
@@ -745,24 +742,25 @@
                 }
             }
 
+            // Sort the folder items, update ranks, and make sure all preview items are high res.
             FolderIconPreviewVerifier verifier =
                     new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile());
-            // Sort the folder items and make sure all items in the preview are high resolution.
             for (FolderInfo folder : mBgDataModel.folders) {
                 Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
                 verifier.setFolderInfo(folder);
+                int size = folder.contents.size();
 
-                int numItemsInPreview = 0;
-                for (ShortcutInfo info : folder.contents) {
+                // Update ranks here to ensure there are no gaps caused by removed folder items.
+                // Ranks are the source of truth for folder items, so cellX and cellY can be ignored
+                // for now. Database will be updated once user manually modifies folder.
+                for (int rank = 0; rank < size; ++rank) {
+                    ShortcutInfo info = folder.contents.get(rank);
+                    info.rank = rank;
+
                     if (info.usingLowResIcon()
                             && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
                             && verifier.isItemInPreview(info.rank)) {
                         mIconCache.getTitleAndIcon(info, false);
-                        numItemsInPreview++;
-                    }
-
-                    if (numItemsInPreview >= MAX_NUM_ITEMS_IN_PREVIEW) {
-                        break;
                     }
                 }
             }
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index ac5076c..daf99e9 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.LauncherSettings.Settings;
 import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -112,19 +113,18 @@
         ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
         if (modelItem != null && item != modelItem) {
             // check all the data is consistent
-            if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
-                ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
-                ShortcutInfo shortcut = (ShortcutInfo) item;
-                if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
-                        modelShortcut.intent.filterEquals(shortcut.intent) &&
-                        modelShortcut.id == shortcut.id &&
-                        modelShortcut.itemType == shortcut.itemType &&
-                        modelShortcut.container == shortcut.container &&
-                        modelShortcut.screenId == shortcut.screenId &&
-                        modelShortcut.cellX == shortcut.cellX &&
-                        modelShortcut.cellY == shortcut.cellY &&
-                        modelShortcut.spanX == shortcut.spanX &&
-                        modelShortcut.spanY == shortcut.spanY) {
+            if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_DOGFOOD_BUILD &&
+                    modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
+                if (modelItem.title.toString().equals(item.title.toString()) &&
+                        modelItem.getIntent().filterEquals(item.getIntent()) &&
+                        modelItem.id == item.id &&
+                        modelItem.itemType == item.itemType &&
+                        modelItem.container == item.container &&
+                        modelItem.screenId == item.screenId &&
+                        modelItem.cellX == item.cellX &&
+                        modelItem.cellY == item.cellY &&
+                        modelItem.spanX == item.spanX &&
+                        modelItem.spanY == item.spanY) {
                     // For all intents and purposes, this is the same object
                     return;
                 }
@@ -310,7 +310,7 @@
     /**
      * Delete operations tracked using {@link #enqueueDeleteRunnable} will only be called
      * if {@link #commitDelete} is called. Note that one of {@link #commitDelete()} or
-     * {@link #abortDelete()} MUST be called after this method, or else all delete
+     * {@link #abortDelete} MUST be called after this method, or else all delete
      * operations will remain uncommitted indefinitely.
      */
     public void prepareToUndoDelete() {
@@ -325,7 +325,7 @@
 
     /**
      * If {@link #prepareToUndoDelete} has been called, we store the Runnable to be run when
-     * {@link #commitDelete()} is called (or abandoned if {@link #abortDelete()} is called).
+     * {@link #commitDelete()} is called (or abandoned if {@link #abortDelete} is called).
      * Otherwise, we run the Runnable immediately.
      */
     private void enqueueDeleteRunnable(Runnable r) {
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index b0af4c6..080a0cb 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
@@ -167,7 +168,8 @@
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             BaseDragLayer dl = getPopupContainer();
-            if (!dl.isEventOverView(this, ev)) {
+            final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
+            if (!cameFromNavBar && !dl.isEventOverView(this, ev)) {
                 mLauncher.getUserEventDispatcher().logActionTapOutside(
                         LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS));
                 close(true);
@@ -185,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/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index fdc1b39..e7b8292 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -195,7 +195,7 @@
             return view -> {
                 Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent(
                         itemInfo.getTargetComponent().getPackageName());
-                activity.startActivitySafely(view, intent, itemInfo);
+                activity.startActivitySafely(view, intent, itemInfo, null);
                 AbstractFloatingView.closeAllOpenViews(activity);
             };
         }
diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java
index 86fcc06..7b62f53 100644
--- a/src/com/android/launcher3/provider/ImportDataTask.java
+++ b/src/com/android/launcher3/provider/ImportDataTask.java
@@ -104,7 +104,7 @@
                 .getSerialNumberForUser(Process.myUserHandle()));
 
         boolean createEmptyRowOnFirstScreen;
-        if (FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
+        if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
             try (Cursor c = mContext.getContentResolver().query(mOtherFavoritesUri, null,
                     // get items on the first row of the first screen (min screen id)
                     "profileId = ? AND container = -100 AND cellY = 0 AND screen = " +
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index bcca4d8..040b5e5 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -18,10 +18,15 @@
 
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 
+import android.app.backup.BackupManager;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+import android.os.Build;
+import android.os.UserHandle;
+import android.util.LongSparseArray;
+import android.util.SparseLongArray;
 
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherProvider.DatabaseHelper;
@@ -48,10 +53,10 @@
     private static final String INFO_COLUMN_NAME = "name";
     private static final String INFO_COLUMN_DEFAULT_VALUE = "dflt_value";
 
-    public static boolean performRestore(DatabaseHelper helper) {
+    public static boolean performRestore(DatabaseHelper helper, BackupManager backupManager) {
         SQLiteDatabase db = helper.getWritableDatabase();
         try (SQLiteTransaction t = new SQLiteTransaction(db)) {
-            new RestoreDbTask().sanitizeDB(helper, db);
+            new RestoreDbTask().sanitizeDB(helper, db, backupManager);
             t.commit();
             return true;
         } catch (Exception e) {
@@ -62,20 +67,44 @@
 
     /**
      * Makes the following changes in the provider DB.
-     *   1. Removes all entries belonging to a managed profile as managed profiles
-     *      cannot be restored.
+     *   1. Removes all entries belonging to any profiles that were not restored.
      *   2. Marks all entries as restored. The flags are updated during first load or as
      *      the restored apps get installed.
-     *   3. If the user serial for primary profile is different than that of the previous device,
-     *      update the entries to the new profile id.
+     *   3. If the user serial for any restored profile is different than that of the previous
+     *      device, update the entries to the new profile id.
      */
-    private void sanitizeDB(DatabaseHelper helper, SQLiteDatabase db) throws Exception {
+    private void sanitizeDB(DatabaseHelper helper, SQLiteDatabase db, BackupManager backupManager)
+            throws Exception {
+        // Primary user ids
+        long myProfileId = helper.getDefaultUserSerial();
         long oldProfileId = getDefaultProfileId(db);
-        // Delete all entries which do not belong to the main user
-        int itemsDeleted = db.delete(
-                Favorites.TABLE_NAME, "profileId != ?", new String[]{Long.toString(oldProfileId)});
+        LongSparseArray<Long> oldManagedProfileIds = getManagedProfileIds(db, oldProfileId);
+        LongSparseArray<Long> profileMapping = new LongSparseArray<>(oldManagedProfileIds.size()
+                + 1);
+
+        // Build mapping of restored profile ids to their new profile ids.
+        profileMapping.put(oldProfileId, myProfileId);
+        for (int i = oldManagedProfileIds.size() - 1; i >= 0; --i) {
+            long oldManagedProfileId = oldManagedProfileIds.keyAt(i);
+            UserHandle user = getUserForAncestralSerialNumber(backupManager, oldManagedProfileId);
+            if (user != null) {
+                long newManagedProfileId = helper.getSerialNumberForUser(user);
+                profileMapping.put(oldManagedProfileId, newManagedProfileId);
+            }
+        }
+
+        // Delete all entries which do not belong to any restored profile(s).
+        int numProfiles = profileMapping.size();
+        String[] profileIds = new String[numProfiles];
+        profileIds[0] = Long.toString(oldProfileId);
+        StringBuilder whereClause = new StringBuilder("profileId != ?");
+        for (int i = profileMapping.size() - 1; i >= 1; --i) {
+            whereClause.append(" AND profileId != ?");
+            profileIds[i] = Long.toString(profileMapping.keyAt(i));
+        }
+        int itemsDeleted = db.delete(Favorites.TABLE_NAME, whereClause.toString(), profileIds);
         if (itemsDeleted > 0) {
-            FileLog.d(TAG, itemsDeleted + " items belonging to a managed profile, were deleted");
+            FileLog.d(TAG, itemsDeleted + " items from unrestored user(s) were deleted");
         }
 
         // Mark all items as restored.
@@ -85,7 +114,7 @@
                 | (keepAllIcons ? ShortcutInfo.FLAG_RESTORE_STARTED : 0));
         db.update(Favorites.TABLE_NAME, values, null, null);
 
-        // Mark widgets with appropriate restore flag
+        // Mark widgets with appropriate restore flag.
         values.put(Favorites.RESTORED,  LauncherAppWidgetInfo.FLAG_ID_NOT_VALID |
                 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY |
                 LauncherAppWidgetInfo.FLAG_UI_NOT_READY |
@@ -93,21 +122,46 @@
         db.update(Favorites.TABLE_NAME, values, "itemType = ?",
                 new String[]{Integer.toString(Favorites.ITEM_TYPE_APPWIDGET)});
 
-        long myProfileId = helper.getDefaultUserSerial();
+        // Migrate ids. To avoid any overlap, we initially move conflicting ids to a temp location.
+        // Using Long.MIN_VALUE since profile ids can not be negative, so there will be no overlap.
+        final long tempLocationOffset = Long.MIN_VALUE;
+        SparseLongArray tempMigratedIds = new SparseLongArray(profileMapping.size());
+        int numTempMigrations = 0;
+        for (int i = profileMapping.size() - 1; i >= 0; --i) {
+            long oldId = profileMapping.keyAt(i);
+            long newId = profileMapping.valueAt(i);
+
+            if (oldId != newId) {
+                if (profileMapping.indexOfKey(newId) >= 0) {
+                    tempMigratedIds.put(numTempMigrations, newId);
+                    numTempMigrations++;
+                    newId = tempLocationOffset + newId;
+                }
+                migrateProfileId(db, oldId, newId);
+            }
+        }
+
+        // Migrate ids from their temporary id to their actual final id.
+        for (int i = tempMigratedIds.size() - 1; i >= 0; --i) {
+            long newId = tempMigratedIds.valueAt(i);
+            migrateProfileId(db, tempLocationOffset + newId, newId);
+        }
+
         if (myProfileId != oldProfileId) {
-            FileLog.d(TAG, "Changing primary user id from " + oldProfileId + " to " + myProfileId);
-            migrateProfileId(db, myProfileId);
+            changeDefaultColumn(db, myProfileId);
         }
     }
 
     /**
-     * Updates profile id of all entries and changes the default value for the column.
+     * Updates profile id of all entries from {@param oldProfileId} to {@param newProfileId}.
      */
-    protected void migrateProfileId(SQLiteDatabase db, long newProfileId) {
+    protected void migrateProfileId(SQLiteDatabase db, long oldProfileId, long newProfileId) {
+        FileLog.d(TAG, "Changing profile user id from " + oldProfileId + " to " + newProfileId);
         // Update existing entries.
         ContentValues values = new ContentValues();
         values.put(Favorites.PROFILE_ID, newProfileId);
-        db.update(Favorites.TABLE_NAME, values, null, null);
+        db.update(Favorites.TABLE_NAME, values, "profileId = ?",
+                new String[]{Long.toString(oldProfileId)});
 
         // Change default value of the column.
         db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;");
@@ -116,6 +170,43 @@
         dropTable(db, "favorites_old");
     }
 
+
+    /**
+     * Changes the default value for the column.
+     */
+    protected void changeDefaultColumn(SQLiteDatabase db, long newProfileId) {
+        db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;");
+        Favorites.addTableToDb(db, newProfileId, false);
+        db.execSQL("INSERT INTO favorites SELECT * FROM favorites_old;");
+        dropTable(db, "favorites_old");
+    }
+
+    /**
+     * Returns a list of the managed profile id(s) used in the favorites table of the provided db.
+     */
+    private LongSparseArray<Long> getManagedProfileIds(SQLiteDatabase db, long defaultProfileId) {
+        LongSparseArray<Long> ids = new LongSparseArray<>();
+        try (Cursor c = db.rawQuery("SELECT profileId from favorites WHERE profileId != ? "
+                + "GROUP BY profileId", new String[] {Long.toString(defaultProfileId)})){
+                while (c.moveToNext()) {
+                    ids.put(c.getLong(c.getColumnIndex(Favorites.PROFILE_ID)), null);
+                }
+        }
+        return ids;
+    }
+
+    /**
+     * Returns a UserHandle of a restored managed profile with the given serial number, or null
+     * if none found.
+     */
+    private UserHandle getUserForAncestralSerialNumber(BackupManager backupManager,
+            long ancestralSerialNumber) {
+        if (Build.VERSION.SDK_INT < 29) {
+            return null;
+        }
+        return backupManager.getUserForAncestralSerialNumber(ancestralSerialNumber);
+    }
+
     /**
      * Returns the profile id used in the favorites table of the provided db.
      */
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index c2bae6d..857ea05 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -215,7 +215,7 @@
         }
 
         public boolean isQsbEnabled() {
-            return FeatureFlags.QSB_ON_FIRST_SCREEN.get();
+            return FeatureFlags.QSB_ON_FIRST_SCREEN;
         }
 
         protected Bundle createBindOptions() {
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index fcace98..be3e6c9 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -41,7 +41,7 @@
     }
 
     @Override
-    public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
+    public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
         DeviceProfile grid = launcher.getDeviceProfile();
         Workspace ws = launcher.getWorkspace();
         if (ws.getChildCount() == 0) {
@@ -50,7 +50,7 @@
 
         if (grid.isVerticalBarLayout()) {
             float scale = grid.workspaceSpringLoadShrinkFactor;
-            return new float[] {scale, 0, 0};
+            return new ScaleAndTranslation(scale, 0, 0);
         }
 
         float scale = grid.workspaceSpringLoadShrinkFactor;
@@ -69,12 +69,12 @@
         float myCenter = ws.getTop() + halfHeight;
         float cellTopFromCenter = halfHeight - ws.getChildAt(0).getTop();
         float actualCellTop = myCenter - cellTopFromCenter * scale;
-        return new float[] { scale, 0, (desiredCellTop - actualCellTop) / scale};
+        return new ScaleAndTranslation(scale, 0, (desiredCellTop - actualCellTop) / scale);
     }
 
     @Override
-    public float[] getHotseatScaleAndTranslation(Launcher launcher) {
-        return new float[] {1, 0, 0};
+    public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
+        return new ScaleAndTranslation(1, 0, 0);
     }
 
     @Override
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index c125c10..0274de3 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -20,7 +20,7 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
-import static com.android.launcher3.LauncherStateManager.ATOMIC_COMPONENT;
+import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_SCALE_COMPONENT;
 import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
@@ -38,9 +38,6 @@
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationComponents;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
-import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.TestProtocol;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -68,10 +65,11 @@
      * Play an atomic recents animation when the progress from NORMAL to OVERVIEW reaches this.
      */
     public static final float ATOMIC_OVERVIEW_ANIM_THRESHOLD = 0.5f;
-    protected static final long ATOMIC_DURATION = 200;
+    protected final long ATOMIC_DURATION = getAtomicDuration();
 
     protected final Launcher mLauncher;
     protected final SwipeDetector mDetector;
+    protected final SwipeDetector.Direction mSwipeDirection;
 
     private boolean mNoIntercept;
     protected int mStartContainerType;
@@ -108,6 +106,11 @@
     public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
         mLauncher = l;
         mDetector = new SwipeDetector(l, this, dir);
+        mSwipeDirection = dir;
+    }
+
+    protected long getAtomicDuration() {
+        return 200;
     }
 
     protected abstract boolean canInterceptTouch(MotionEvent ev);
@@ -214,14 +217,15 @@
         }
 
         if (mAtomicComponentsController != null) {
-            animComponents &= ~ATOMIC_COMPONENT;
+            animComponents &= ~ATOMIC_OVERVIEW_SCALE_COMPONENT;
         }
         mProgressMultiplier = initCurrentAnimation(animComponents);
         mCurrentAnimation.dispatchOnStart();
         return true;
     }
 
-    private boolean goingBetweenNormalAndOverview(LauncherState fromState, LauncherState toState) {
+    protected boolean goingBetweenNormalAndOverview(LauncherState fromState,
+            LauncherState toState) {
         return (fromState == NORMAL || fromState == OVERVIEW)
                 && (toState == NORMAL || toState == OVERVIEW)
                 && mPendingAnimation == null;
@@ -229,12 +233,17 @@
 
     @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;
         } else if (mStartState == NORMAL) {
             mStartContainerType = getLogContainerTypeForNormalState();
-        } else if (mStartState   == OVERVIEW){
+        } else if (mStartState == OVERVIEW){
             mStartContainerType = LauncherLogProto.ContainerType.TASKSWITCHER;
         }
         if (mCurrentAnimation == null) {
@@ -260,8 +269,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;
@@ -297,7 +312,7 @@
      * When going between normal and overview states, see if we passed the overview threshold and
      * play the appropriate atomic animation if so.
      */
-    protected void maybeUpdateAtomicAnim(LauncherState fromState, LauncherState toState,
+    private void maybeUpdateAtomicAnim(LauncherState fromState, LauncherState toState,
             float progress) {
         if (!goingBetweenNormalAndOverview(fromState, toState)) {
             return;
@@ -347,14 +362,8 @@
     private AnimatorSet createAtomicAnimForState(LauncherState fromState, LauncherState targetState,
             long duration) {
         AnimatorSetBuilder builder = getAnimatorSetBuilderForStates(fromState, targetState);
-        mLauncher.getStateManager().prepareForAtomicAnimation(fromState, targetState, builder);
-        AnimationConfig config = new AnimationConfig();
-        config.animComponents = ATOMIC_COMPONENT;
-        config.duration = duration;
-        for (StateHandler handler : mLauncher.getStateManager().getStateHandlers()) {
-            handler.setStateWithAnimation(targetState, builder, config);
-        }
-        return builder.build();
+        return mLauncher.getStateManager().createAtomicAnimation(fromState, targetState, builder,
+                ATOMIC_OVERVIEW_SCALE_COMPONENT, duration);
     }
 
     protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState,
@@ -384,6 +393,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;
@@ -434,11 +449,7 @@
             mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity);
         }
         anim.start();
-        settleAtomicAnimation(endProgress, anim.getDuration());
-    }
-
-    protected void settleAtomicAnimation(float endProgress, long duration) {
-        mAtomicAnimAutoPlayInfo = new AutoPlayAtomicAnimationInfo(endProgress, duration);
+        mAtomicAnimAutoPlayInfo = new AutoPlayAtomicAnimationInfo(endProgress, anim.getDuration());
         maybeAutoPlayAtomicComponentsAnim();
     }
 
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 52fef9f..3c77860 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
 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 android.app.AlertDialog;
 import android.content.Intent;
@@ -31,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;
@@ -56,9 +59,21 @@
     /**
      * Instance used for click handling on items
      */
-    public static final OnClickListener INSTANCE = ItemClickHandler::onClick;
+    public static final OnClickListener INSTANCE = getInstance(null);
 
-    private static void onClick(View v) {
+    public static final OnClickListener getInstance(String sourceContainer) {
+        return v -> onClick(v, sourceContainer);
+    }
+
+    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) {
@@ -72,13 +87,14 @@
 
         Object tag = v.getTag();
         if (tag instanceof ShortcutInfo) {
-            onClickAppShortcut(v, (ShortcutInfo) tag, launcher);
+            onClickAppShortcut(v, (ShortcutInfo) tag, launcher, sourceContainer);
         } else if (tag instanceof FolderInfo) {
             if (v instanceof FolderIcon) {
                 onClickFolderIcon(v);
             }
         } else if (tag instanceof AppInfo) {
-            startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);
+            startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher,
+                    sourceContainer == null ? CONTAINER_ALL_APPS: sourceContainer);
         } else if (tag instanceof LauncherAppWidgetInfo) {
             if (v instanceof PendingAppWidgetHostView) {
                 onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
@@ -154,7 +170,7 @@
     private static void startMarketIntentForPackage(View v, Launcher launcher, String packageName) {
         ItemInfo item = (ItemInfo) v.getTag();
         Intent intent = new PackageManagerHelper(launcher).getMarketIntent(packageName);
-        launcher.startActivitySafely(v, intent, item);
+        launcher.startActivitySafely(v, intent, item, null);
     }
 
     /**
@@ -162,7 +178,8 @@
      *
      * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
      */
-    public static void onClickAppShortcut(View v, ShortcutInfo shortcut, Launcher launcher) {
+    public static void onClickAppShortcut(View v, ShortcutInfo shortcut, Launcher launcher,
+            @Nullable String sourceContainer) {
         if (shortcut.isDisabled()) {
             final int disabledFlags = shortcut.runtimeStatusFlags & ShortcutInfo.FLAG_DISABLED_MASK;
             if ((disabledFlags &
@@ -201,10 +218,11 @@
         }
 
         // Start activities
-        startAppShortcutOrInfoActivity(v, shortcut, launcher);
+        startAppShortcutOrInfoActivity(v, shortcut, launcher, sourceContainer);
     }
 
-    private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {
+    private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher,
+            @Nullable String sourceContainer) {
         Intent intent;
         if (item instanceof PromiseAppInfo) {
             PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
@@ -227,6 +245,6 @@
                 intent.setPackage(null);
             }
         }
-        launcher.startActivitySafely(v, intent, item);
+        launcher.startActivitySafely(v, intent, item, sourceContainer);
     }
 }
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..d758a29 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;
@@ -179,18 +219,23 @@
 
         boolean onDrag(float displacement);
 
+        default boolean onDrag(float displacement, MotionEvent event) {
+            return onDrag(displacement);
+        }
+
         void onDragEnd(float velocity, boolean fling);
     }
 
     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 +257,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,14 +304,14 @@
                 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)) {
                     setState(ScrollState.DRAGGING);
                 }
                 if (mState == ScrollState.DRAGGING) {
-                    reportDragging();
+                    reportDragging(ev);
                 }
                 mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
                 break;
@@ -315,24 +360,24 @@
      * @see #DIRECTION_BOTH
      */
     public boolean wasInitialTouchPositive() {
-        return mSubtractDisplacement < 0;
+        return mDir.isPositive(mSubtractDisplacement);
     }
 
-    private boolean reportDragging() {
+    private boolean reportDragging(MotionEvent event) {
         if (mDisplacement != mLastDisplacement) {
             if (DBG) {
                 Log.d(TAG, String.format("onDrag disp=%.1f", mDisplacement));
             }
 
             mLastDisplacement = mDisplacement;
-            return mListener.onDrag(mDisplacement - mSubtractDisplacement);
+            return mListener.onDrag(mDisplacement - mSubtractDisplacement, event);
         }
         return true;
     }
 
     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/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java
index 12280f8..12d35e9 100644
--- a/src/com/android/launcher3/util/ConfigMonitor.java
+++ b/src/com/android/launcher3/util/ConfigMonitor.java
@@ -41,8 +41,6 @@
 
     private static final String TAG = "ConfigMonitor";
 
-    private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
-
     private final Point mTmpPoint1 = new Point();
     private final Point mTmpPoint2 = new Point();
 
@@ -78,11 +76,6 @@
         // Listen for configuration change
         mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
 
-        // Listen for {@link OverlayManager} change
-        IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED);
-        filter.addDataScheme("package");
-        mContext.registerReceiver(this, filter);
-
         // Listen for display manager change
         mContext.getSystemService(DisplayManager.class)
                 .registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper()));
@@ -91,12 +84,6 @@
     @Override
     public void onReceive(Context context, Intent intent) {
         Configuration config = context.getResources().getConfiguration();
-        // TODO: when overlay manager service encodes more information to the Uri such as category
-        // of the overlay, only listen to the ones that are of interest to launcher.
-        if (intent != null && ACTION_OVERLAY_CHANGED.equals(intent.getAction())) {
-            Log.d(TAG, "Overlay changed.");
-            notifyChange();
-        }
         if (mFontScale != config.fontScale || mDensity != config.densityDpi) {
             Log.d(TAG, "Configuration changed.");
             notifyChange();
diff --git a/src/com/android/launcher3/util/InstantAppResolver.java b/src/com/android/launcher3/util/InstantAppResolver.java
index 5dc7af8..031a40d 100644
--- a/src/com/android/launcher3/util/InstantAppResolver.java
+++ b/src/com/android/launcher3/util/InstantAppResolver.java
@@ -24,9 +24,6 @@
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.R;
 
-import java.util.Collections;
-import java.util.List;
-
 /**
  * A wrapper class to access instant app related APIs.
  */
@@ -55,8 +52,4 @@
         }
         return false;
     }
-
-    public List<ApplicationInfo> getInstantApps() {
-        return Collections.emptyList();
-    }
 }
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 7af2bd8..5889468 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -15,8 +15,13 @@
  */
 package com.android.launcher3.views;
 
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
@@ -43,18 +48,19 @@
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.FolderShape;
 import com.android.launcher3.graphics.ShiftedBitmapDrawable;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.shortcuts.DeepShortcutView;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
-import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
+import static com.android.launcher3.Utilities.mapToRange;
 
 /**
  * A view that is created to look like another view with the purpose of creating fluid animations.
@@ -62,12 +68,12 @@
 
 public class FloatingIconView extends View implements Animator.AnimatorListener, ClipPathView {
 
+    public static final float SHAPE_PROGRESS_DURATION = 0.15f;
+
     private static final Rect sTmpRect = new Rect();
 
-    private Runnable mStartRunnable;
     private Runnable mEndRunnable;
 
-    private int mOriginalHeight;
     private final int mBlurSizeOutline;
 
     private boolean mIsAdaptiveIcon = false;
@@ -78,30 +84,28 @@
     private final Rect mStartRevealRect = new Rect();
     private final Rect mEndRevealRect = new Rect();
     private Path mClipPath;
-    protected final Rect mOutline = new Rect();
-    private final float mTaskCornerRadius;
+    private float mTaskCornerRadius;
 
     private final Rect mFinalDrawableBounds = new Rect();
     private final Rect mBgDrawableBounds = new Rect();
     private float mBgDrawableStartScale = 1f;
+    private float mBgDrawableEndScale = 1f;
 
     private FloatingIconView(Context context) {
         super(context);
-
         mBlurSizeOutline = context.getResources().getDimensionPixelSize(
                 R.dimen.blur_size_medium_outline);
-
-        mTaskCornerRadius = 0; // TODO
     }
 
     /**
      * Positions this view to match the size and location of {@param rect}.
-     *
      * @param alpha The alpha to set this view.
      * @param progress A value from [0, 1] that represents the animation progress.
-     * @param windowAlphaThreshold The value at which the window alpha is 0.
+     * @param shapeProgressStart The progress value at which to start the shape reveal.
+     * @param cornerRadius The corner radius of {@param rect}.
      */
-    public void update(RectF rect, float alpha, float progress, float windowAlphaThreshold) {
+    public void update(RectF rect, float alpha, float progress, float shapeProgressStart,
+            float cornerRadius, boolean isOpening) {
         setAlpha(alpha);
 
         LayoutParams lp = (LayoutParams) getLayoutParams();
@@ -112,55 +116,50 @@
 
         float scaleX = rect.width() / (float) lp.width;
         float scaleY = rect.height() / (float) lp.height;
-        float scale = mIsAdaptiveIcon ? Math.max(scaleX, scaleY) : Math.min(scaleX, scaleY);
+        float scale = mIsAdaptiveIcon && !isOpening ? Math.max(scaleX, scaleY)
+                : Math.min(scaleX, scaleY);
+        scale = Math.max(1f, scale);
+
         setPivotX(0);
         setPivotY(0);
         setScaleX(scale);
         setScaleY(scale);
 
-        // Wait until the window is no longer visible before morphing the icon into its final shape.
-        float shapeRevealProgress = Utilities.mapToRange(Math.max(windowAlphaThreshold, progress),
-                windowAlphaThreshold, 1f, 0f, 1, Interpolators.LINEAR);
-        if (mIsAdaptiveIcon && shapeRevealProgress > 0) {
+        // shapeRevealProgress = 1 when progress = shapeProgressStart + SHAPE_PROGRESS_DURATION
+        float toMax = isOpening ? 1 / SHAPE_PROGRESS_DURATION : 1f;
+        float shapeRevealProgress = Utilities.boundToRange(mapToRange(
+                Math.max(shapeProgressStart, progress), shapeProgressStart, 1f, 0, toMax,
+                LINEAR), 0, 1);
+
+        mTaskCornerRadius = cornerRadius;
+        if (mIsAdaptiveIcon && shapeRevealProgress >= 0) {
             if (mRevealAnimator == null) {
-                mEndRevealRect.set(mOutline);
-                // We play the reveal animation in reverse so that we end with the icon shape.
                 mRevealAnimator = (ValueAnimator) FolderShape.getShape().createRevealAnimator(this,
-                        mStartRevealRect, mEndRevealRect, mTaskCornerRadius / scale, true);
-                mRevealAnimator.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mRevealAnimator = null;
-                    }
-                });
+                        mStartRevealRect, mEndRevealRect, mTaskCornerRadius / scale, !isOpening);
                 mRevealAnimator.start();
                 // We pause here so we can set the current fraction ourselves.
                 mRevealAnimator.pause();
             }
 
-            float bgScale = shapeRevealProgress + mBgDrawableStartScale * (1 - shapeRevealProgress);
-            setBackgroundDrawableBounds(bgScale);
-
             mRevealAnimator.setCurrentFraction(shapeRevealProgress);
-            if (Float.compare(shapeRevealProgress, 1f) >= 0f) {
-                mRevealAnimator.end();
-            }
+
+            float bgScale = (mBgDrawableEndScale * shapeRevealProgress) + mBgDrawableStartScale
+                    * (1 - shapeRevealProgress);
+            setBackgroundDrawableBounds(bgScale);
         }
         invalidate();
         invalidateOutline();
     }
 
     @Override
-    public void onAnimationStart(Animator animator) {
-        if (mStartRunnable != null) {
-            mStartRunnable.run();
-        }
-    }
-
-    @Override
     public void onAnimationEnd(Animator animator) {
         if (mEndRunnable != null) {
             mEndRunnable.run();
+        } else {
+            // End runnable also ends the reveal animator, so we manually handle it here.
+            if (mRevealAnimator != null) {
+                mRevealAnimator.end();
+            }
         }
     }
 
@@ -174,7 +173,6 @@
         Utilities.getLocationBoundsForView(launcher, v, positionOut);
         final LayoutParams lp = new LayoutParams(positionOut.width(), positionOut.height());
         lp.ignoreInsets = true;
-        mOriginalHeight = lp.height;
 
         // Position the floating view exactly on top of the original
         lp.leftMargin = positionOut.left;
@@ -187,22 +185,28 @@
     }
 
     @WorkerThread
-    private void getIcon(Launcher launcher, View v, ItemInfo info, boolean useDrawableAsIs,
-            float aspectRatio) {
+    private void getIcon(Launcher launcher, View v, ItemInfo info, boolean isOpening,
+            Runnable onIconLoadedRunnable) {
         final LayoutParams lp = (LayoutParams) getLayoutParams();
         Drawable drawable = null;
-        boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get() && !useDrawableAsIs
+        boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get()
                 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
         if (!supportsAdaptiveIcons && v instanceof BubbleTextView) {
             // Similar to DragView, we simply use the BubbleTextView icon here.
             drawable = ((BubbleTextView) v).getIcon();
         }
-        if (v instanceof ImageView && info instanceof SystemShortcut) {
-            drawable = ((ImageView) v).getDrawable();
+        if (info instanceof SystemShortcut) {
+            if (v instanceof ImageView) {
+                drawable = ((ImageView) v).getDrawable();
+            } else if (v instanceof DeepShortcutView) {
+                drawable = ((DeepShortcutView) v).getIconView().getBackground();
+            } else {
+                drawable = v.getBackground();
+            }
         }
         if (drawable == null) {
             drawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height,
-                    useDrawableAsIs, new Object[1]);
+                    false, new Object[1]);
         }
 
         Drawable finalDrawable = drawable == null ? null
@@ -214,6 +218,7 @@
         new Handler(Looper.getMainLooper()).post(() -> {
             if (isAdaptiveIcon) {
                 mIsAdaptiveIcon = true;
+                boolean isFolderIcon = finalDrawable instanceof FolderAdaptiveIcon;
 
                 AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) finalDrawable;
                 Drawable background = adaptiveIcon.getBackground();
@@ -227,35 +232,57 @@
                 }
                 mForeground = foreground;
 
-                mFinalDrawableBounds.set(iconOffset, iconOffset, lp.width -
-                        iconOffset, mOriginalHeight - iconOffset);
                 if (mForeground instanceof ShiftedBitmapDrawable && v instanceof FolderIcon) {
                     ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mForeground;
                     ((FolderIcon) v).getPreviewBounds(sTmpRect);
                     sbd.setShiftX(sbd.getShiftX() - sTmpRect.left);
                     sbd.setShiftY(sbd.getShiftY() - sTmpRect.top);
                 }
+
+                final int originalHeight = lp.height;
+                final int originalWidth = lp.width;
+
+                int blurMargin = mBlurSizeOutline / 2;
+                mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight);
+                if (!isFolderIcon) {
+                    mFinalDrawableBounds.inset(iconOffset - blurMargin, iconOffset - blurMargin);
+                }
                 mForeground.setBounds(mFinalDrawableBounds);
                 mBackground.setBounds(mFinalDrawableBounds);
 
-                int blurMargin = mBlurSizeOutline / 2;
-                mStartRevealRect.set(blurMargin, blurMargin , lp.width - blurMargin,
-                        mOriginalHeight - blurMargin);
+                mStartRevealRect.set(0, 0, originalWidth, originalHeight);
 
-                if (aspectRatio > 0) {
-                    lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
-                    layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin
-                            + lp.height);
+                if (!isFolderIcon) {
+                    mStartRevealRect.inset(mBlurSizeOutline, mBlurSizeOutline);
                 }
-                mBgDrawableStartScale = (float) lp.height / mOriginalHeight;
-                setBackgroundDrawableBounds(mBgDrawableStartScale);
 
-                // Set up outline
-                mOutline.set(0, 0, lp.width, lp.height);
+                float aspectRatio = launcher.getDeviceProfile().aspectRatio;
+                if (launcher.getDeviceProfile().isVerticalBarLayout()) {
+                    lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
+                } else {
+                    lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
+                }
+                layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin
+                        + lp.height);
+
+                Rect rectOutline = new Rect();
+                float scale = Math.max((float) lp.height / originalHeight,
+                        (float) lp.width / originalWidth);
+                if (isOpening) {
+                    mBgDrawableStartScale = 1f;
+                    mBgDrawableEndScale = scale;
+                    rectOutline.set(0, 0, originalWidth, originalHeight);
+                } else {
+                    mBgDrawableStartScale = scale;
+                    mBgDrawableEndScale = 1f;
+                    rectOutline.set(0, 0, lp.width, lp.height);
+                }
+                mEndRevealRect.set(0, 0, lp.width, lp.height);
+                setBackgroundDrawableBounds(mBgDrawableStartScale);
                 setOutlineProvider(new ViewOutlineProvider() {
                     @Override
                     public void getOutline(View view, Outline outline) {
-                        outline.setRoundRect(mOutline, mTaskCornerRadius);
+                        outline.setRoundRect(rectOutline, mTaskCornerRadius);
                     }
                 });
                 setClipToOutline(true);
@@ -263,6 +290,7 @@
                 setBackground(finalDrawable);
             }
 
+            onIconLoadedRunnable.run();
             invalidate();
             invalidateOutline();
         });
@@ -330,6 +358,9 @@
     }
 
     @Override
+    public void onAnimationStart(Animator animator) {}
+
+    @Override
     public void onAnimationCancel(Animator animator) {}
 
     @Override
@@ -337,17 +368,16 @@
 
     /**
      * Creates a floating icon view for {@param originalView}.
-     *
      * @param originalView The view to copy
      * @param hideOriginal If true, it will hide {@param originalView} while this view is visible.
-     * @param useDrawableAsIs If true, we do not separate the foreground/background of adaptive
-     * icons. TODO(b/122843905): We can remove this once app opening uses new animation.
-     * @param aspectRatio If >= 0, we will use this aspect ratio for the initial adaptive icon size.
      * @param positionOut Rect that will hold the size and position of v.
+     * @param isOpening True if this view replaces the icon for app open animation.
      */
     public static FloatingIconView getFloatingIconView(Launcher launcher, View originalView,
-            boolean hideOriginal, boolean useDrawableAsIs, float aspectRatio, Rect positionOut,
-            FloatingIconView recycle) {
+            boolean hideOriginal, Rect positionOut, boolean isOpening, FloatingIconView recycle) {
+        if (recycle != null) {
+            recycle.recycle();
+        }
         FloatingIconView view = recycle != null ? recycle : new FloatingIconView(launcher);
 
         // Match the position of the original view.
@@ -356,9 +386,16 @@
         // Get the drawable on the background thread
         // Must be called after matchPositionOf so that we know what size to load.
         if (originalView.getTag() instanceof ItemInfo) {
+            Runnable onIconLoaded = () -> {
+                // Delay swapping views until the icon is loaded to prevent a flash.
+                view.setVisibility(VISIBLE);
+                if (hideOriginal) {
+                    originalView.setVisibility(INVISIBLE);
+                }
+            };
             new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> {
-                view.getIcon(launcher, originalView, (ItemInfo) originalView.getTag(),
-                        useDrawableAsIs, aspectRatio);
+                view.getIcon(launcher, originalView, (ItemInfo) originalView.getTag(), isOpening,
+                        onIconLoaded);
             });
         }
 
@@ -367,18 +404,73 @@
         view.setVisibility(INVISIBLE);
         ((ViewGroup) dragLayer.getParent()).getOverlay().add(view);
 
-        view.mStartRunnable = () -> {
-            view.setVisibility(VISIBLE);
-            if (hideOriginal) {
-                originalView.setVisibility(INVISIBLE);
-            }
-        };
-        view.mEndRunnable = () -> {
-            ((ViewGroup) dragLayer.getParent()).getOverlay().remove(view);
-            if (hideOriginal) {
-                originalView.setVisibility(VISIBLE);
-            }
-        };
+        if (hideOriginal) {
+            view.mEndRunnable = () -> {
+                AnimatorSet fade = new AnimatorSet();
+                fade.setDuration(200);
+                fade.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationStart(Animator animation) {
+                        originalView.setVisibility(VISIBLE);
+
+                        if (originalView instanceof FolderIcon) {
+                            FolderIcon folderIcon = (FolderIcon) originalView;
+                            folderIcon.setBackgroundVisible(false);
+                            folderIcon.getFolderName().setTextVisibility(false);
+                        }
+                    }
+
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        ((ViewGroup) dragLayer.getParent()).getOverlay().remove(view);
+
+                        if (view.mRevealAnimator != null) {
+                            view.mRevealAnimator.end();
+                        }
+                    }
+                });
+
+                if (originalView instanceof FolderIcon) {
+                    FolderIcon folderIcon = (FolderIcon) originalView;
+                    fade.play(folderIcon.getFolderName().createTextAlphaAnimator(true));
+                    fade.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            folderIcon.setBackgroundVisible(true);
+                            folderIcon.animateBgShadowAndStroke();
+                            if (folderIcon.hasDot()) {
+                                folderIcon.animateDotScale(0, 1f);
+                            }
+                        }
+                    });
+                } else {
+                    fade.play(ObjectAnimator.ofFloat(originalView, ALPHA, 0f, 1f));
+                }
+                fade.start();
+                // TODO: Do not run fade animation until we fix b/129421279.
+                fade.end();
+            };
+        }
         return view;
     }
+
+    private void recycle() {
+        setTranslationX(0);
+        setTranslationY(0);
+        setScaleX(1);
+        setScaleY(1);
+        setAlpha(1);
+        setBackground(null);
+        mEndRunnable = null;
+        mIsAdaptiveIcon = false;
+        mForeground = null;
+        mBackground = null;
+        mClipPath = null;
+        mFinalDrawableBounds.setEmpty();
+        mBgDrawableBounds.setEmpty();;
+        if (mRevealAnimator != null) {
+            mRevealAnimator.cancel();
+        }
+        mRevealAnimator = null;
+    }
 }
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 48cf9e8..6a2f0ff 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -18,8 +18,10 @@
 import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_FLAVOR;
 import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.text.TextUtils;
@@ -150,7 +152,7 @@
         RectF target = new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize);
 
         ArrayList<OptionItem> options = new ArrayList<>();
-        int res = FeatureFlags.STYLE_WALLPAPER.get() ?
+        int res = FeatureFlags.STYLE_WALLPAPER.get() && existsStyleWallpapers(launcher) ?
                 R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text;
         options.add(new OptionItem(res, R.drawable.ic_wallpaper,
                 ControlType.WALLPAPER_BUTTON, OptionsPopupView::startWallpaperPicker));
@@ -164,6 +166,14 @@
         show(launcher, target, options);
     }
 
+    private static boolean existsStyleWallpapers(Launcher launcher) {
+        Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
+        intent.setComponent(new ComponentName(launcher.getString(R.string.wallpaper_picker_package),
+                "com.android.customization.picker.CustomizationPickerActivity"));
+        ResolveInfo ri = launcher.getPackageManager().resolveActivity(intent, 0);
+        return ri != null;
+    }
+
     public static boolean onWidgetsClicked(View view) {
         return openWidgets(Launcher.getLauncher(view.getContext()));
     }
@@ -207,7 +217,7 @@
         if (!TextUtils.isEmpty(pickerPackage)) {
             intent.setPackage(pickerPackage);
         }
-        return launcher.startActivitySafely(v, intent, null);
+        return launcher.startActivitySafely(v, intent, null, null);
     }
 
     public static class OptionItem {
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 641183a..c15557b 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -56,11 +56,6 @@
         addOnItemTouchListener(this);
     }
 
-    public WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        this(context, attrs, defStyleAttr);
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
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/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index 0d727fd..9939c25 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -22,6 +22,7 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.util.TouchController;
 
 import java.io.PrintWriter;
@@ -33,7 +34,9 @@
                 launcher.getDragController(), new AllAppsSwipeController(launcher)};
     }
 
-    public static void setOnTouchControllersChangedListener(Context context, Runnable listener) { }
+    public static Runnable enableLiveTouchControllerChanges(DragLayer dl) {
+        return null;
+    }
 
     public static StateHandler[] getStateHandler(Launcher launcher) {
         return new StateHandler[] {
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 92%
rename from src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
rename to src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
index f7bb254..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;
@@ -66,9 +66,9 @@
     }
 
     @Override
-    public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
-        return new float[] { 1f, 0,
-                -launcher.getAllAppsController().getShiftRange() * PARALLAX_COEFFICIENT};
+    public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
+        return new ScaleAndTranslation(1f, 0,
+                -launcher.getAllAppsController().getShiftRange() * PARALLAX_COEFFICIENT);
     }
 
     @Override
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 74%
rename from src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
rename to src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
index 8def0d3..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;
 
@@ -28,4 +28,16 @@
     public OverviewState(int id) {
         super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, FLAG_DISABLE_RESTORE);
     }
+
+    public static OverviewState newBackgroundState(int id) {
+        return new OverviewState(id);
+    }
+
+    public static OverviewState newPeekState(int id) {
+        return new OverviewState(id);
+    }
+
+    public static OverviewState newSwitchState(int id) {
+        return new OverviewState(id);
+    }
 }
diff --git a/tests/Android.mk b/tests/Android.mk
index a787537..080c98b 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -23,16 +23,19 @@
 	androidx.annotation_annotation \
 	androidx.test.runner \
 	androidx.test.rules \
-	androidx.test.uiautomator_uiautomator \
-	libSharedSystemUI
+	androidx.test.uiautomator_uiautomator
 
-LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
-  ../quickstep/src/com/android/quickstep/SwipeUpSetting.java \
-  ../src/com/android/launcher3/util/SecureSettingsObserver.java \
-  ../src/com/android/launcher3/TestProtocol.java \
+ifneq (,$(wildcard frameworks/base))
+else
+    LOCAL_STATIC_JAVA_LIBRARIES += libSharedSystemUI
 
-LOCAL_SDK_VERSION := current
+    LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
+        ../src/com/android/launcher3/util/SecureSettingsObserver.java \
+        ../src/com/android/launcher3/TestProtocol.java
+endif
+
 LOCAL_MODULE := ub-launcher-aosp-tapl
+LOCAL_SDK_VERSION := current
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
@@ -43,18 +46,18 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_STATIC_JAVA_LIBRARIES := \
-	androidx.test.runner \
-	androidx.test.rules \
-	androidx.test.uiautomator_uiautomator \
-	mockito-target-minus-junit4
+    androidx.test.runner \
+    androidx.test.rules \
+    androidx.test.uiautomator_uiautomator \
+    mockito-target-minus-junit4
 
 ifneq (,$(wildcard frameworks/base))
-  LOCAL_PRIVATE_PLATFORM_APIS := true
-  LOCAL_STATIC_JAVA_LIBRARIES += launcher-aosp-tapl
+    LOCAL_PRIVATE_PLATFORM_APIS := true
+    LOCAL_STATIC_JAVA_LIBRARIES += launcher-aosp-tapl
 else
-  LOCAL_SDK_VERSION := 28
-  LOCAL_MIN_SDK_VERSION := 21
-  LOCAL_STATIC_JAVA_LIBRARIES += ub-launcher-aosp-tapl
+    LOCAL_SDK_VERSION := 28
+    LOCAL_MIN_SDK_VERSION := 21
+    LOCAL_STATIC_JAVA_LIBRARIES += ub-launcher-aosp-tapl
 endif
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index babb731..6fa8d62 100644
--- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -41,18 +41,34 @@
         // Verify item add
         assertEquals(5, getCount(db, "select * from favorites where profileId = 42"));
 
-        new RestoreDbTask().migrateProfileId(db, 33);
+        new RestoreDbTask().migrateProfileId(db, 42, 33);
 
         // verify data migrated
         assertEquals(0, getCount(db, "select * from favorites where profileId = 42"));
         assertEquals(5, getCount(db, "select * from favorites where profileId = 33"));
+    }
+
+    @Test
+    public void testChangeDefaultColumn() throws Exception {
+        SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
+        // Add some dummy data
+        for (int i = 0; i < 5; i++) {
+            ContentValues values = new ContentValues();
+            values.put(Favorites._ID, i);
+            values.put(Favorites.TITLE, "item " + i);
+            db.insert(Favorites.TABLE_NAME, null, values);
+        }
+        // Verify default column is 42
+        assertEquals(5, getCount(db, "select * from favorites where profileId = 42"));
+
+        new RestoreDbTask().changeDefaultColumn(db, 33);
 
         // Verify default value changed
         ContentValues values = new ContentValues();
         values.put(Favorites._ID, 100);
         values.put(Favorites.TITLE, "item 100");
         db.insert(Favorites.TABLE_NAME, null, values);
-        assertEquals(6, getCount(db, "select * from favorites where profileId = 33"));
+        assertEquals(1, getCount(db, "select * from favorites where profileId = 33"));
     }
 
     private int getCount(SQLiteDatabase db, String sql) {
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..d29d29c 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;
@@ -206,8 +207,8 @@
         // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
         executeOnLauncher(launcher -> assertFalse("Initial workspace state is scrollable",
                 isWorkspaceScrollable(launcher)));
-        assertNull("Messages app was found on empty workspace",
-                workspace.tryGetWorkspaceAppIcon("Messages"));
+        assertNull("Play Store app was found on empty workspace",
+                workspace.tryGetWorkspaceAppIcon("Play Store"));
 
         workspace.ensureWorkspaceIsScrollable();
 
@@ -217,8 +218,8 @@
         executeOnLauncher(
                 launcher -> assertTrue("ensureScrollable didn't make workspace scrollable",
                         isWorkspaceScrollable(launcher)));
-        assertNotNull("ensureScrollable didn't add Messages app",
-                workspace.tryGetWorkspaceAppIcon("Messages"));
+        assertNotNull("ensureScrollable didn't add Play Store app",
+                workspace.tryGetWorkspaceAppIcon("Play Store"));
 
         // Test flinging workspace.
         workspace.flingBackward();
@@ -234,10 +235,10 @@
         assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
 
         // Test starting a workspace app.
-        final AppIcon app = workspace.tryGetWorkspaceAppIcon("Messages");
-        assertNotNull("No Messages app in workspace", app);
+        final AppIcon app = workspace.tryGetWorkspaceAppIcon("Play Store");
+        assertNotNull("No Play Store app in workspace", app);
         assertNotNull("AppIcon.launch returned null",
-                app.launch(resolveSystemApp(Intent.CATEGORY_APP_MESSAGING)));
+                app.launch(resolveSystemApp(Intent.CATEGORY_APP_MARKET)));
         executeOnLauncher(launcher -> assertTrue(
                 "Launcher activity is the top activity; expecting another activity to be the top "
                         + "one",
@@ -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/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index fbb4f51..68b16d6 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -145,7 +145,7 @@
         assertFalse(mDevice.findObject(new UiSelector().description(info.label)).exists());
     }
 
-    @Test
+    @Test @Ignore
     public void testPendingWidget_autoRestored() {
         // A non-restored widget with no config screen gets restored automatically.
         LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
diff --git a/tests/tapl/AndroidManifest.xml b/tests/tapl/AndroidManifest.xml
new file mode 100644
index 0000000..0207e2b
--- /dev/null
+++ b/tests/tapl/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.launcher3.tapl"
+>
+
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+</manifest>
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 1353a23..98fb07f 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -29,7 +29,7 @@
 public class AllApps extends LauncherInstrumentation.VisibleContainer {
     private static final int MAX_SCROLL_ATTEMPTS = 40;
     private static final int MIN_INTERACT_SIZE = 100;
-    private static final int FLING_SPEED = 3000;
+    private static final int FLING_SPEED = LauncherInstrumentation.needSlowGestures() ? 1000 : 3000;
 
     private final int mHeight;
 
@@ -44,6 +44,13 @@
         return LauncherInstrumentation.ContainerType.ALL_APPS;
     }
 
+    private boolean hasClickableIcon(UiObject2 allAppsContainer, BySelector appIconSelector) {
+        final UiObject2 icon = allAppsContainer.findObject(appIconSelector);
+        if (icon == null) return false;
+        final UiObject2 navBar = mLauncher.waitForSystemUiObject("navigation_bar_frame");
+        return icon.getVisibleBounds().bottom < navBar.getVisibleBounds().top;
+    }
+
     /**
      * Finds an icon. Fails if the icon doesn't exist. Scrolls the app list when needed to make
      * sure the icon is visible.
@@ -53,47 +60,61 @@
      */
     @NonNull
     public AppIcon getAppIcon(String appName) {
-        final UiObject2 allAppsContainer = verifyActiveContainer();
-        final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
-        if (!allAppsContainer.hasObject(appIconSelector)) {
-            scrollBackToBeginning();
-            int attempts = 0;
-            while (!allAppsContainer.hasObject(appIconSelector) &&
-                    allAppsContainer.scroll(Direction.DOWN, 0.8f)) {
-                LauncherInstrumentation.assertTrue(
-                        "Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
-                        ++attempts <= MAX_SCROLL_ATTEMPTS);
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to get app icon on all apps")) {
+            final UiObject2 allAppsContainer = verifyActiveContainer();
+            final UiObject2 navBar = mLauncher.waitForSystemUiObject("navigation_bar_frame");
+            allAppsContainer.setGestureMargins(0, 0, 0, navBar.getVisibleBounds().height() + 1);
+            final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
+            if (!hasClickableIcon(allAppsContainer, appIconSelector)) {
+                scrollBackToBeginning();
+                int attempts = 0;
+                try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled")) {
+                    while (!hasClickableIcon(allAppsContainer, appIconSelector) &&
+                            allAppsContainer.scroll(Direction.DOWN, 0.8f)) {
+                        mLauncher.assertTrue(
+                                "Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
+                                ++attempts <= MAX_SCROLL_ATTEMPTS);
+                        verifyActiveContainer();
+                    }
+                }
                 verifyActiveContainer();
             }
-        }
-        verifyActiveContainer();
 
-        final UiObject2 appIcon = mLauncher.getObjectInContainer(allAppsContainer, appIconSelector);
-        ensureIconVisible(appIcon, allAppsContainer);
-        return new AppIcon(mLauncher, appIcon);
+            final UiObject2 appIcon = mLauncher.getObjectInContainer(allAppsContainer,
+                    appIconSelector);
+            ensureIconVisible(appIcon, allAppsContainer);
+            return new AppIcon(mLauncher, appIcon);
+        }
     }
 
     private void scrollBackToBeginning() {
-        final UiObject2 allAppsContainer = verifyActiveContainer();
-        final UiObject2 searchBox =
-                mLauncher.waitForObjectInContainer(allAppsContainer, "search_container_all_apps");
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to scroll back in all apps")) {
+            final UiObject2 allAppsContainer = verifyActiveContainer();
+            final UiObject2 searchBox =
+                    mLauncher.waitForObjectInContainer(allAppsContainer,
+                            "search_container_all_apps");
 
-        int attempts = 0;
-        allAppsContainer.setGestureMargins(0, searchBox.getVisibleBounds().bottom + 1, 0, 5);
+            int attempts = 0;
+            allAppsContainer.setGestureMargins(0, searchBox.getVisibleBounds().bottom + 1, 0, 5);
 
-        for (int scroll = getScroll(allAppsContainer);
-                scroll != 0;
-                scroll = getScroll(allAppsContainer)) {
-            LauncherInstrumentation.assertTrue("Negative scroll position", scroll > 0);
+            for (int scroll = getScroll(allAppsContainer);
+                    scroll != 0;
+                    scroll = getScroll(allAppsContainer)) {
+                mLauncher.assertTrue("Negative scroll position", scroll > 0);
 
-            LauncherInstrumentation.assertTrue(
-                    "Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
-                    ++attempts <= MAX_SCROLL_ATTEMPTS);
+                mLauncher.assertTrue(
+                        "Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
+                        ++attempts <= MAX_SCROLL_ATTEMPTS);
 
-            allAppsContainer.scroll(Direction.UP, 1);
+                allAppsContainer.scroll(Direction.UP, 1);
+            }
+
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled up")) {
+                verifyActiveContainer();
+            }
         }
-
-        verifyActiveContainer();
     }
 
     private int getScroll(UiObject2 allAppsContainer) {
@@ -108,8 +129,11 @@
             // to reveal the app icon to have the MIN_INTERACT_SIZE
             final float pct = Math.max(((float) (MIN_INTERACT_SIZE - appHeight)) / mHeight, 0.2f);
             allAppsContainer.scroll(Direction.DOWN, pct);
-            mLauncher.waitForIdle();
-            verifyActiveContainer();
+            try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                    "scrolled an icon in all apps to make it visible - and then")) {
+                mLauncher.waitForIdle();
+                verifyActiveContainer();
+            }
         }
     }
 
@@ -117,21 +141,29 @@
      * Flings forward (down) and waits the fling's end.
      */
     public void flingForward() {
-        final UiObject2 allAppsContainer = verifyActiveContainer();
-        // Start the gesture in the center to avoid starting at elements near the top.
-        allAppsContainer.setGestureMargins(0, 0, 0, mHeight / 2);
-        allAppsContainer.fling(Direction.DOWN, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
-        verifyActiveContainer();
+        try (LauncherInstrumentation.Closable c =
+                     mLauncher.addContextLayer("want to fling forward in all apps")) {
+            final UiObject2 allAppsContainer = verifyActiveContainer();
+            // Start the gesture in the center to avoid starting at elements near the top.
+            allAppsContainer.setGestureMargins(0, 0, 0, mHeight / 2);
+            allAppsContainer.fling(Direction.DOWN,
+                    (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
+            verifyActiveContainer();
+        }
     }
 
     /**
      * Flings backward (up) and waits the fling's end.
      */
     public void flingBackward() {
-        final UiObject2 allAppsContainer = verifyActiveContainer();
-        // Start the gesture in the center, for symmetry with forward.
-        allAppsContainer.setGestureMargins(0, mHeight / 2, 0, 0);
-        allAppsContainer.fling(Direction.UP, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
-        verifyActiveContainer();
+        try (LauncherInstrumentation.Closable c =
+                     mLauncher.addContextLayer("want to fling backward in all apps")) {
+            final UiObject2 allAppsContainer = verifyActiveContainer();
+            // Start the gesture in the center, for symmetry with forward.
+            allAppsContainer.setGestureMargins(0, mHeight / 2, 0, 0);
+            allAppsContainer.fling(Direction.UP,
+                    (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
+            verifyActiveContainer();
+        }
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
index 2642815..c3b671b 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
@@ -23,6 +23,8 @@
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.TestProtocol;
+
 /**
  * Operations on AllApps opened from Overview.
  */
@@ -40,16 +42,24 @@
      */
     @NonNull
     public Overview switchBackToOverview() {
-        final UiObject2 allAppsContainer = verifyActiveContainer();
-        // Swipe from the search box to the bottom.
-        final UiObject2 qsb = mLauncher.waitForObjectInContainer(
-                allAppsContainer, "search_container_all_apps");
-        final Point start = qsb.getVisibleCenter();
-        final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.6);
-        LauncherInstrumentation.log("AllAppsFromOverview.switchBackToOverview before swipe");
-        mLauncher.swipe(start.x, start.y, start.x, endY, OVERVIEW_STATE_ORDINAL);
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to switch back from all apps to overview")) {
+            final UiObject2 allAppsContainer = verifyActiveContainer();
+            // Swipe from the search box to the bottom.
+            final UiObject2 qsb = mLauncher.waitForObjectInContainer(
+                    allAppsContainer, "search_container_all_apps");
+            final Point start = qsb.getVisibleCenter();
+            final int swipeHeight = mLauncher.getTestInfo(
+                    TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT).
+                    getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
 
-        return new Overview(mLauncher);
+            final int endY = start.y + swipeHeight + mLauncher.getTouchSlop();
+            LauncherInstrumentation.log("AllAppsFromOverview.switchBackToOverview before swipe");
+            mLauncher.swipe(start.x, start.y, start.x, endY, OVERVIEW_STATE_ORDINAL);
+
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("swiped down")) {
+                return new Overview(mLauncher);
+            }
+        }
     }
-
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index d39a38e..fbeb3a2 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.tapl;
 
 import android.graphics.Point;
+import android.os.SystemClock;
 import android.view.MotionEvent;
 import android.widget.TextView;
 
@@ -41,10 +42,12 @@
      */
     public AppIconMenu openMenu() {
         final Point iconCenter = mObject.getVisibleCenter();
-        mLauncher.sendPointer(MotionEvent.ACTION_DOWN, iconCenter);
+        final long downTime = SystemClock.uptimeMillis();
+        mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, iconCenter);
         final UiObject2 deepShortcutsContainer = mLauncher.waitForLauncherObject(
                 "deep_shortcuts_container");
-        mLauncher.sendPointer(MotionEvent.ACTION_UP, iconCenter);
+        mLauncher.sendPointer(
+                downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, iconCenter);
         return new AppIconMenu(mLauncher, deepShortcutsContainer);
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 606cf37..358d5e9 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -20,18 +20,23 @@
 import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
 import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName;
 
-import static org.junit.Assert.assertTrue;
+import android.graphics.Point;
+import android.os.SystemClock;
+import android.view.MotionEvent;
 
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import com.android.launcher3.TestProtocol;
+
 /**
  * Indicates the base state with a UI other than Overview running as foreground. It can also
  * indicate Launcher as long as Launcher is not in Overview state.
  */
 public class Background extends LauncherInstrumentation.VisibleContainer {
+    private static final int ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION = 500;
+    private static final int ZERO_BUTTON_SWIPE_UP_HOLD_DURATION = 400;
 
     Background(LauncherInstrumentation launcher) {
         super(launcher);
@@ -50,29 +55,60 @@
      */
     @NonNull
     public BaseOverview switchToOverview() {
-        verifyActiveContainer();
-        goToOverviewUnchecked(BACKGROUND_APP_STATE_ORDINAL);
-        assertTrue("Overview not visible", mLauncher.getDevice().wait(
-                Until.hasObject(By.pkg(getOverviewPackageName())), WAIT_TIME_MS));
-        return new BaseOverview(mLauncher);
-    }
-
-    protected void goToOverviewUnchecked(int expectedState) {
-        if (mLauncher.isSwipeUpEnabled()) {
-            final int height = mLauncher.getDevice().getDisplayHeight();
-            final UiObject2 navBar = mLauncher.getSystemUiObject("navigation_bar_frame");
-
-            int swipeLength = Math.round(getSwipeLength() * mLauncher.getDisplayDensity());
-            mLauncher.swipe(
-                    navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(),
-                    navBar.getVisibleBounds().centerX(), height - swipeLength,
-                    expectedState);
-        } else {
-            mLauncher.getSystemUiObject("recent_apps").click();
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to switch from background to overview")) {
+            verifyActiveContainer();
+            goToOverviewUnchecked(BACKGROUND_APP_STATE_ORDINAL);
+            mLauncher.assertTrue("Overview not visible", mLauncher.getDevice().wait(
+                    Until.hasObject(By.pkg(getOverviewPackageName())), WAIT_TIME_MS));
+            return new BaseOverview(mLauncher);
         }
     }
 
-    protected int getSwipeLength() {
-        return 200;
+    protected void goToOverviewUnchecked(int expectedState) {
+        switch (mLauncher.getNavigationModel()) {
+            case ZERO_BUTTON: {
+                final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
+                final int startY = getSwipeStartY();
+                final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).
+                        getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+                final Point start = new Point(centerX, startY);
+                final Point end =
+                        new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop());
+
+                final long downTime = SystemClock.uptimeMillis();
+                mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start);
+                mLauncher.movePointer(downTime, ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION, start, end);
+                LauncherInstrumentation.sleep(ZERO_BUTTON_SWIPE_UP_HOLD_DURATION);
+                mLauncher.sendPointer(
+                        downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, end);
+                break;
+            }
+
+            case TWO_BUTTON: {
+                final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
+                final int startY = getSwipeStartY();
+                final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).
+                        getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+
+                mLauncher.swipe(
+                        centerX, startY, centerX,
+                        startY - swipeHeight - mLauncher.getTouchSlop(),
+                        expectedState);
+                break;
+            }
+
+            case THREE_BUTTON:
+                mLauncher.waitForSystemUiObject("recent_apps").click();
+                break;
+        }
+    }
+
+    protected String getSwipeHeightRequestName() {
+        return TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT;
+    }
+
+    protected int getSwipeStartY() {
+        return mLauncher.waitForSystemUiObject("navigation_bar_frame").getVisibleBounds().centerY();
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 6e92dad..4205188 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -29,7 +29,7 @@
  */
 public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
     private static final int FLING_SPEED = 1500;
-    private static final int FLINGS_FOR_DISMISS_LIMIT = 5;
+    private static final int FLINGS_FOR_DISMISS_LIMIT = 40;
 
     BaseOverview(LauncherInstrumentation launcher) {
         super(launcher);
@@ -44,38 +44,54 @@
      * Flings forward (left) and waits the fling's end.
      */
     public void flingForward() {
-        final UiObject2 overview = verifyActiveContainer();
-        LauncherInstrumentation.log("Overview.flingForward before fling");
-        overview.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
-        mLauncher.waitForIdle();
-        verifyActiveContainer();
+        try (LauncherInstrumentation.Closable c =
+                     mLauncher.addContextLayer("want to fling forward in overview")) {
+            LauncherInstrumentation.log("Overview.flingForward before fling");
+            final UiObject2 overview = verifyActiveContainer();
+            final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1;
+            overview.setGestureMargins(margin, 0, 0, 0);
+            overview.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
+            mLauncher.waitForIdle();
+            verifyActiveContainer();
+        }
     }
 
     /**
      * Dismissed all tasks by scrolling to Clear-all button and pressing it.
      */
     public Workspace dismissAllTasks() {
-        final BySelector clearAllSelector = mLauncher.getLauncherObjectSelector("clear_all");
-        for (int i = 0;
-                i < FLINGS_FOR_DISMISS_LIMIT
-                        && verifyActiveContainer().findObject(clearAllSelector) == null;
-                ++i) {
-            flingForward();
-        }
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "dismissing all tasks")) {
+            final BySelector clearAllSelector = mLauncher.getLauncherObjectSelector("clear_all");
+            for (int i = 0;
+                    i < FLINGS_FOR_DISMISS_LIMIT
+                            && !verifyActiveContainer().hasObject(clearAllSelector);
+                    ++i) {
+                flingForward();
+            }
 
-        mLauncher.getObjectInContainer(verifyActiveContainer(), clearAllSelector).click();
-        return new Workspace(mLauncher);
+            mLauncher.getObjectInContainer(verifyActiveContainer(), clearAllSelector).click();
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "dismissed all tasks")) {
+                return new Workspace(mLauncher);
+            }
+        }
     }
 
     /**
      * Flings backward (right) and waits the fling's end.
      */
     public void flingBackward() {
-        final UiObject2 overview = verifyActiveContainer();
-        LauncherInstrumentation.log("Overview.flingBackward before fling");
-        overview.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
-        mLauncher.waitForIdle();
-        verifyActiveContainer();
+        try (LauncherInstrumentation.Closable c =
+                     mLauncher.addContextLayer("want to fling backward in overview")) {
+            LauncherInstrumentation.log("Overview.flingBackward before fling");
+            final UiObject2 overview = verifyActiveContainer();
+            final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1;
+            overview.setGestureMargins(0, 0, margin, 0);
+            overview.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
+            mLauncher.waitForIdle();
+            verifyActiveContainer();
+        }
     }
 
     /**
@@ -85,18 +101,21 @@
      */
     @NonNull
     public OverviewTask getCurrentTask() {
-        verifyActiveContainer();
-        final List<UiObject2> taskViews = mLauncher.getDevice().findObjects(
-                mLauncher.getLauncherObjectSelector("snapshot"));
-        LauncherInstrumentation.assertNotEquals("Unable to find a task", 0, taskViews.size());
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to get current task")) {
+            verifyActiveContainer();
+            final List<UiObject2> taskViews = mLauncher.getDevice().findObjects(
+                    mLauncher.getLauncherObjectSelector("snapshot"));
+            mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
 
-        // taskViews contains up to 3 task views: the 'main' (having the widest visible
-        // part) one in the center, and parts of its right and left siblings. Find the
-        // main task view by its width.
-        final UiObject2 widestTask = Collections.max(taskViews,
-                (t1, t2) -> Integer.compare(t1.getVisibleBounds().width(),
-                        t2.getVisibleBounds().width()));
+            // taskViews contains up to 3 task views: the 'main' (having the widest visible
+            // part) one in the center, and parts of its right and left siblings. Find the
+            // main task view by its width.
+            final UiObject2 widestTask = Collections.max(taskViews,
+                    (t1, t2) -> Integer.compare(t1.getVisibleBounds().width(),
+                            t2.getVisibleBounds().width()));
 
-        return new OverviewTask(mLauncher, widestTask, this);
+            return new OverviewTask(mLauncher, widestTask, this);
+        }
     }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java
index f8bd85a..20c116c 100644
--- a/tests/tapl/com/android/launcher3/tapl/Home.java
+++ b/tests/tapl/com/android/launcher3/tapl/Home.java
@@ -48,8 +48,14 @@
     @NonNull
     @Override
     public Overview switchToOverview() {
-        verifyActiveContainer();
-        goToOverviewUnchecked(OVERVIEW_STATE_ORDINAL);
-        return new Overview(mLauncher);
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to switch from home to overview")) {
+            verifyActiveContainer();
+            goToOverviewUnchecked(OVERVIEW_STATE_ORDINAL);
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "performed the switch action")) {
+                return new Overview(mLauncher);
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 481281a..7a2b7af 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -59,10 +59,10 @@
     private Background launch(String errorMessage, BySelector selector) {
         LauncherInstrumentation.log("Launchable.launch before click " +
                 mObject.getVisibleCenter());
-        LauncherInstrumentation.assertTrue(
+        mLauncher.assertTrue(
                 "Launching an app didn't open a new window: " + mObject.getText(),
                 mObject.clickAndWait(Until.newWindow(), LauncherInstrumentation.WAIT_TIME_MS));
-        LauncherInstrumentation.assertTrue(
+        mLauncher.assertTrue(
                 "App didn't start: " + errorMessage,
                 mLauncher.getDevice().wait(Until.hasObject(selector),
                         LauncherInstrumentation.WAIT_TIME_MS));
@@ -79,6 +79,9 @@
                 this,
                 new Point(device.getDisplayWidth() / 2, device.getDisplayHeight() / 2),
                 DRAG_SPEED);
-        return new Workspace(mLauncher);
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "dragged launchable to workspace")) {
+            return new Workspace(mLauncher);
+        }
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 93b4cc6..a4b4171 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -16,36 +16,43 @@
 
 package com.android.launcher3.tapl;
 
-import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
-
 import android.app.ActivityManager;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.graphics.Point;
+import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.os.SystemClock;
-import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.Log;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.Surface;
+import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
-
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.Configurator;
 import androidx.test.uiautomator.UiDevice;
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
-
 import com.android.launcher3.TestProtocol;
-import com.android.quickstep.SwipeUpSetting;
-
-import org.junit.Assert;
-
+import com.android.systemui.shared.system.QuickStepContract;
+import java.io.IOException;
 import java.lang.ref.WeakReference;
+import java.util.Deque;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
+import org.junit.Assert;
 
 /**
  * The main tapl object. The only object that can be explicitly constructed by the using code. It
@@ -54,6 +61,9 @@
 public final class LauncherInstrumentation {
 
     private static final String TAG = "Tapl";
+    private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
+            "config_navBarInteractionMode";
+    private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20;
 
     // Types for launcher containers that the user is interacting with. "Background" is a
     // pseudo-container corresponding to inactive launcher covered by another app.
@@ -61,6 +71,8 @@
         WORKSPACE, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND, BASE_OVERVIEW
     }
 
+    public enum NavigationModel {ZERO_BUTTON, TWO_BUTTON, THREE_BUTTON}
+
     // Base class for launcher containers.
     static abstract class VisibleContainer {
         protected final LauncherInstrumentation mLauncher;
@@ -78,11 +90,16 @@
          * @return UI object for the container.
          */
         final UiObject2 verifyActiveContainer() {
-            assertTrue("Attempt to use a stale container", this == sActiveContainer.get());
+            mLauncher.assertTrue("Attempt to use a stale container",
+                    this == sActiveContainer.get());
             return mLauncher.verifyContainerType(getContainerType());
         }
     }
 
+    interface Closable extends AutoCloseable {
+        void close();
+    }
+
     private static final String WORKSPACE_RES_ID = "workspace";
     private static final String APPS_RES_ID = "apps_view";
     private static final String OVERVIEW_RES_ID = "overview_panel";
@@ -95,6 +112,8 @@
     private final UiDevice mDevice;
     private final Instrumentation mInstrumentation;
     private int mExpectedRotation = Surface.ROTATION_0;
+    private final Uri mTestProviderUri;
+    private final Deque<String> mDiagnosticContext = new LinkedList<>();
 
     /**
      * Constructs the root of TAPL hierarchy. You get all other objects from it.
@@ -108,51 +127,109 @@
         // into Launcher.
         assertTrue("Device must run in a test harness",
                 TestHelpers.isInLauncherProcess() || ActivityManager.isRunningInTestHarness());
+
+        final String testPackage = getContext().getPackageName();
+        final String targetPackage = mInstrumentation.getTargetContext().getPackageName();
+
+        // Launcher package. As during inproc tests the tested launcher may not be selected as the
+        // current launcher, choosing target package for inproc. For out-of-proc, use the installed
+        // launcher package.
+        final String authorityPackage = testPackage.equals(targetPackage) ?
+                getLauncherPackageName() :
+                targetPackage;
+
+        mTestProviderUri = new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(authorityPackage + ".TestInfo")
+                .build();
+
+        try {
+            mDevice.executeShellCommand("pm grant " + testPackage +
+                    " android.permission.WRITE_SECURE_SETTINGS");
+        } catch (IOException e) {
+            fail(e.toString());
+        }
+    }
+
+    Context getContext() {
+        return mInstrumentation.getContext();
+    }
+
+    Bundle getTestInfo(String request) {
+        return getContext().getContentResolver().call(mTestProviderUri, request, null, null);
     }
 
     void setActiveContainer(VisibleContainer container) {
         sActiveContainer = new WeakReference<>(container);
     }
 
-    public boolean isSwipeUpEnabled() {
-        final boolean swipeUpEnabledDefaultValue = SwipeUpSetting.isSwipeUpEnabledDefaultValue();
-        return SwipeUpSetting.isSwipeUpSettingAvailable() ?
-                Settings.Secure.getInt(
-                        mInstrumentation.getTargetContext().getContentResolver(),
-                        SWIPE_UP_SETTING_NAME,
-                        swipeUpEnabledDefaultValue ? 1 : 0) == 1 :
-                swipeUpEnabledDefaultValue;
+    public NavigationModel getNavigationModel() {
+        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
+            final Context ctx = baseContext.createPackageContext("android", 0);
+            if (isGesturalMode(ctx)) {
+                return NavigationModel.ZERO_BUTTON;
+            } else if (isSwipeUpMode(ctx)) {
+                return NavigationModel.TWO_BUTTON;
+            } else if (isLegacyMode(ctx)) {
+                return NavigationModel.THREE_BUTTON;
+            } else {
+                fail("Can't detect navigation mode");
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            fail(e.toString());
+        }
+        return NavigationModel.THREE_BUTTON;
+    }
+
+    static boolean needSlowGestures() {
+        return Build.MODEL.contains("Cuttlefish");
     }
 
     static void log(String message) {
         Log.d(TAG, message);
     }
 
-    private static void fail(String message) {
-        Assert.fail("http://go/tapl : " + message);
+    Closable addContextLayer(String piece) {
+        mDiagnosticContext.addLast(piece);
+        return () -> mDiagnosticContext.removeLast();
     }
 
-    static void assertTrue(String message, boolean condition) {
+    private void fail(String message) {
+        final String ctxt = mDiagnosticContext.isEmpty() ? "" : String.join(", ",
+                mDiagnosticContext) + "; ";
+        Assert.fail("http://go/tapl : " + ctxt + message);
+    }
+
+    void assertTrue(String message, boolean condition) {
         if (!condition) {
             fail(message);
         }
     }
 
-    static void assertNotNull(String message, Object object) {
+    void assertNotNull(String message, Object object) {
         assertTrue(message, object != null);
     }
 
-    static private void failEquals(String message, Object actual) {
+    private void failEquals(String message, Object actual) {
         fail(message + ". " + "Actual: " + actual);
     }
 
-    static private void assertEquals(String message, int expected, int actual) {
+    private void assertEquals(String message, int expected, int actual) {
         if (expected != actual) {
             fail(message + " expected: " + expected + " but was: " + actual);
         }
     }
 
-    static void assertNotEquals(String message, int unexpected, int actual) {
+    private void assertEquals(String message, String expected, String actual) {
+        if (!TextUtils.equals(expected, actual)) {
+            fail(message + " expected: '" + expected + "' but was: '" + actual + "'");
+        }
+    }
+
+    void assertNotEquals(String message, int unexpected, int actual) {
         if (unexpected == actual) {
             failEquals(message, actual);
         }
@@ -165,54 +242,61 @@
     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);
 
-        switch (containerType) {
-            case WORKSPACE: {
-                waitForLauncherObject(APPS_RES_ID);
-                waitUntilGone(OVERVIEW_RES_ID);
-                waitUntilGone(WIDGETS_RES_ID);
-                return waitForLauncherObject(WORKSPACE_RES_ID);
-            }
-            case WIDGETS: {
-                waitUntilGone(WORKSPACE_RES_ID);
-                waitUntilGone(APPS_RES_ID);
-                waitUntilGone(OVERVIEW_RES_ID);
-                return waitForLauncherObject(WIDGETS_RES_ID);
-            }
-            case ALL_APPS: {
-                waitUntilGone(WORKSPACE_RES_ID);
-                waitUntilGone(OVERVIEW_RES_ID);
-                waitUntilGone(WIDGETS_RES_ID);
-                return waitForLauncherObject(APPS_RES_ID);
-            }
-            case OVERVIEW: {
-                if (mDevice.isNaturalOrientation()) {
+        try (Closable c = addContextLayer(
+                "but the current state is not " + containerType.name())) {
+            switch (containerType) {
+                case WORKSPACE: {
                     waitForLauncherObject(APPS_RES_ID);
-                } else {
-                    waitUntilGone(APPS_RES_ID);
+                    waitUntilGone(OVERVIEW_RES_ID);
+                    waitUntilGone(WIDGETS_RES_ID);
+                    return waitForLauncherObject(WORKSPACE_RES_ID);
                 }
-                // Fall through
-            }
-            case BASE_OVERVIEW: {
-                waitUntilGone(WORKSPACE_RES_ID);
-                waitUntilGone(WIDGETS_RES_ID);
+                case WIDGETS: {
+                    waitUntilGone(WORKSPACE_RES_ID);
+                    waitUntilGone(APPS_RES_ID);
+                    waitUntilGone(OVERVIEW_RES_ID);
+                    return waitForLauncherObject(WIDGETS_RES_ID);
+                }
+                case ALL_APPS: {
+                    waitUntilGone(WORKSPACE_RES_ID);
+                    waitUntilGone(OVERVIEW_RES_ID);
+                    waitUntilGone(WIDGETS_RES_ID);
+                    return waitForLauncherObject(APPS_RES_ID);
+                }
+                case OVERVIEW: {
+                    if (mDevice.isNaturalOrientation()) {
+                        waitForLauncherObject(APPS_RES_ID);
+                    } else {
+                        waitUntilGone(APPS_RES_ID);
+                    }
+                    // Fall through
+                }
+                case BASE_OVERVIEW: {
+                    waitUntilGone(WORKSPACE_RES_ID);
+                    waitUntilGone(WIDGETS_RES_ID);
 
-                return waitForLauncherObject(OVERVIEW_RES_ID);
+                    return waitForLauncherObject(OVERVIEW_RES_ID);
+                }
+                case BACKGROUND: {
+                    waitUntilGone(WORKSPACE_RES_ID);
+                    waitUntilGone(APPS_RES_ID);
+                    waitUntilGone(OVERVIEW_RES_ID);
+                    waitUntilGone(WIDGETS_RES_ID);
+                    return null;
+                }
+                default:
+                    fail("Invalid state: " + containerType);
+                    return null;
             }
-            case BACKGROUND: {
-                waitUntilGone(WORKSPACE_RES_ID);
-                waitUntilGone(APPS_RES_ID);
-                waitUntilGone(OVERVIEW_RES_ID);
-                waitUntilGone(WIDGETS_RES_ID);
-                return null;
-            }
-            default:
-                fail("Invalid state: " + containerType);
-                return null;
         }
     }
 
@@ -250,25 +334,50 @@
         // We need waiting for any accessibility event generated after pressing Home because
         // otherwise waitForIdle may return immediately in case when there was a big enough pause in
         // accessibility events prior to pressing Home.
-        executeAndWaitForEvent(
-                () -> {
-                    log("LauncherInstrumentation.pressHome before clicking");
-                    getSystemUiObject("home").click();
-                },
-                event -> true,
-                "Pressing Home didn't produce any events");
-        mDevice.waitForIdle();
+        final String action;
+        if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
+            if (hasLauncherObject(WORKSPACE_RES_ID)) {
+                log(action = "0-button: already in workspace");
+            } else if (hasLauncherObject(OVERVIEW_RES_ID)) {
+                log(action = "0-button: from overview");
+                mDevice.pressHome();
+            } else if (hasLauncherObject(WIDGETS_RES_ID)) {
+                log(action = "0-button: from widgets");
+                mDevice.pressHome();
+            } else if (hasLauncherObject(APPS_RES_ID)) {
+                log(action = "0-button: from all apps");
+                mDevice.pressHome();
+            } else {
+                log(action = "0-button: from another app");
+                assertTrue("Launcher is visible, don't know how to go home",
+                        !mDevice.hasObject(By.pkg(getLauncherPackageName())));
+                mDevice.pressHome();
+            }
+        } else {
+            log(action = "clicking home button");
+            executeAndWaitForEvent(
+                    () -> {
+                        log("LauncherInstrumentation.pressHome before clicking");
+                        waitForSystemUiObject("home").click();
+                    },
+                    event -> true,
+                    "Pressing Home didn't produce any events");
+            mDevice.waitForIdle();
 
-        // Temporarily press home twice as the first click sometimes gets ignored  (b/124239413)
-        executeAndWaitForEvent(
-                () -> {
-                    log("LauncherInstrumentation.pressHome before clicking");
-                    getSystemUiObject("home").click();
-                },
-                event -> true,
-                "Pressing Home didn't produce any events");
-        mDevice.waitForIdle();
-        return getWorkspace();
+            // Temporarily press home twice as the first click sometimes gets ignored  (b/124239413)
+            executeAndWaitForEvent(
+                    () -> {
+                        log("LauncherInstrumentation.pressHome before clicking");
+                        waitForSystemUiObject("home").click();
+                    },
+                    event -> true,
+                    "Pressing Home didn't produce any events");
+            mDevice.waitForIdle();
+        }
+        try (LauncherInstrumentation.Closable c = addContextLayer(
+                "performed action to switch to Home - " + action)) {
+            return getWorkspace();
+        }
     }
 
     /**
@@ -279,7 +388,9 @@
      */
     @NonNull
     public Workspace getWorkspace() {
-        return new Workspace(this);
+        try (LauncherInstrumentation.Closable c = addContextLayer("want to get workspace object")) {
+            return new Workspace(this);
+        }
     }
 
     /**
@@ -301,7 +412,9 @@
      */
     @NonNull
     public Widgets getAllWidgets() {
-        return new Widgets(this);
+        try (LauncherInstrumentation.Closable c = addContextLayer("want to get widgets")) {
+            return new Widgets(this);
+        }
     }
 
     /**
@@ -312,7 +425,9 @@
      */
     @NonNull
     public Overview getOverview() {
-        return new Overview(this);
+        try (LauncherInstrumentation.Closable c = addContextLayer("want to get overview")) {
+            return new Overview(this);
+        }
     }
 
     /**
@@ -336,7 +451,9 @@
      */
     @NonNull
     public AllApps getAllApps() {
-        return new AllApps(this);
+        try (LauncherInstrumentation.Closable c = addContextLayer("want to get all apps object")) {
+            return new AllApps(this);
+        }
     }
 
     /**
@@ -349,7 +466,9 @@
      */
     @NonNull
     public AllAppsFromOverview getAllAppsFromOverview() {
-        return new AllAppsFromOverview(this);
+        try (LauncherInstrumentation.Closable c = addContextLayer("want to get all apps object")) {
+            return new AllAppsFromOverview(this);
+        }
     }
 
     void waitUntilGone(String resId) {
@@ -359,8 +478,9 @@
     }
 
     @NonNull
-    UiObject2 getSystemUiObject(String resId) {
-        final UiObject2 object = mDevice.findObject(By.res(SYSTEMUI_PACKAGE, resId));
+    UiObject2 waitForSystemUiObject(String resId) {
+        final UiObject2 object = mDevice.wait(
+                Until.findObject(By.res(SYSTEMUI_PACKAGE, resId)), WAIT_TIME_MS);
         assertNotNull("Can't find a systemui object with id: " + resId, object);
         return object;
     }
@@ -387,6 +507,11 @@
         return object;
     }
 
+    @Nullable
+    private boolean hasLauncherObject(String resId) {
+        return mDevice.hasObject(getLauncherObjectSelector(resId));
+    }
+
     @NonNull
     UiObject2 waitForLauncherObject(String resName) {
         final BySelector selector = getLauncherObjectSelector(resName);
@@ -409,27 +534,103 @@
     }
 
     void swipe(int startX, int startY, int endX, int endY, int expectedState) {
+        swipe(startX, startY, endX, endY, expectedState, 60);
+    }
+
+    void swipe(int startX, int startY, int endX, int endY, int expectedState, int steps) {
         final Bundle parcel = (Bundle) executeAndWaitForEvent(
-                () -> mDevice.swipe(startX, startY, endX, endY, 60),
+                () -> mDevice.swipe(startX, startY, endX, endY, steps),
                 event -> TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName()),
                 "Swipe failed to receive an event for the swipe end: " + startX + ", " + startY
                         + ", " + endX + ", " + endY);
-        assertEquals("Swipe switched launcher to a wrong state",
-                expectedState, parcel.getInt(TestProtocol.STATE_FIELD));
+        assertEquals("Swipe switched launcher to a wrong state;",
+                TestProtocol.stateOrdinalToString(expectedState),
+                TestProtocol.stateOrdinalToString(parcel.getInt(TestProtocol.STATE_FIELD)));
     }
 
     void waitForIdle() {
         mDevice.waitForIdle();
     }
 
-    void sendPointer(int action, Point point) {
-        final MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(),
-                SystemClock.uptimeMillis(), action, point.x, point.y, 0);
-        mInstrumentation.sendPointerSync(event);
+    float getDisplayDensity() {
+        return mInstrumentation.getTargetContext().getResources().getDisplayMetrics().density;
+    }
+
+    int getTouchSlop() {
+        return ViewConfiguration.get(getContext()).getScaledTouchSlop();
+    }
+
+    private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
+            float x, float y) {
+        MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
+        properties.id = 0;
+        properties.toolType = Configurator.getInstance().getToolType();
+
+        MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+        coords.pressure = 1;
+        coords.size = 1;
+        coords.x = x;
+        coords.y = y;
+
+        return MotionEvent.obtain(downTime, eventTime, action, 1,
+                new MotionEvent.PointerProperties[]{properties},
+                new MotionEvent.PointerCoords[]{coords},
+                0, 0, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
+    }
+
+    void sendPointer(long downTime, long currentTime, int action, Point point) {
+        final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
+        mInstrumentation.getUiAutomation().injectInputEvent(event, true);
         event.recycle();
     }
 
-    float getDisplayDensity() {
-        return mInstrumentation.getTargetContext().getResources().getDisplayMetrics().density;
+    void movePointer(long downTime, long duration, Point from, Point to) {
+        final Point point = new Point();
+        for (; ; ) {
+            sleep(16);
+
+            final long currentTime = SystemClock.uptimeMillis();
+            final float progress = (currentTime - downTime) / (float) duration;
+            if (progress > 1) return;
+
+            point.x = from.x + (int) (progress * (to.x - from.x));
+            point.y = from.y + (int) (progress * (to.y - from.y));
+
+            sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point);
+        }
+    }
+
+    public static boolean isGesturalMode(Context context) {
+        return QuickStepContract.isGesturalMode(
+                getSystemIntegerRes(context, NAV_BAR_INTERACTION_MODE_RES_NAME));
+    }
+
+    public static boolean isSwipeUpMode(Context context) {
+        return QuickStepContract.isSwipeUpMode(
+                getSystemIntegerRes(context, NAV_BAR_INTERACTION_MODE_RES_NAME));
+    }
+
+    public static boolean isLegacyMode(Context context) {
+        return QuickStepContract.isLegacyMode(
+                getSystemIntegerRes(context, NAV_BAR_INTERACTION_MODE_RES_NAME));
+    }
+
+    private static int getSystemIntegerRes(Context context, String resName) {
+        Resources res = context.getResources();
+        int resId = res.getIdentifier(resName, "integer", "android");
+
+        if (resId != 0) {
+            return res.getInteger(resId);
+        } else {
+            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+            return -1;
+        }
+    }
+
+    static void sleep(int duration) {
+        try {
+            Thread.sleep(duration);
+        } catch (InterruptedException e) {
+        }
     }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Overview.java b/tests/tapl/com/android/launcher3/tapl/Overview.java
index 0208144..b88da3a 100644
--- a/tests/tapl/com/android/launcher3/tapl/Overview.java
+++ b/tests/tapl/com/android/launcher3/tapl/Overview.java
@@ -18,13 +18,11 @@
 
 import static com.android.launcher3.TestProtocol.ALL_APPS_STATE_ORDINAL;
 
-import android.graphics.Point;
-
-import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
-
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
+
 /**
  * Overview pane.
  */
@@ -47,14 +45,20 @@
      */
     @NonNull
     public AllAppsFromOverview switchToAllApps() {
-        verifyActiveContainer();
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to switch from overview to all apps")) {
+            verifyActiveContainer();
 
-        // Swipe from navbar to the top.
-        final UiObject2 navBar = mLauncher.getSystemUiObject("navigation_bar_frame");
-        final Point start = navBar.getVisibleCenter();
-        LauncherInstrumentation.log("Overview.switchToAllApps before swipe");
-        mLauncher.swipe(start.x, start.y, start.x, 0, ALL_APPS_STATE_ORDINAL);
+            // Swipe from navbar to the top.
+            final UiObject2 navBar = mLauncher.waitForSystemUiObject("navigation_bar_frame");
+            LauncherInstrumentation.log("Overview.switchToAllApps before swipe");
+            final int x = navBar.getVisibleCenter().x;
+            mLauncher.swipe(x, navBar.getVisibleBounds().top - 1, x, 0, ALL_APPS_STATE_ORDINAL);
 
-        return new AllAppsFromOverview(mLauncher);
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "swiped all way up from overview")) {
+                return new AllAppsFromOverview(mLauncher);
+            }
+        }
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 7ccd49b..b966851 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -44,10 +44,13 @@
      * Swipes the task up.
      */
     public void dismiss() {
-        verifyActiveContainer();
-        // Dismiss the task via flinging it up.
-        mTask.fling(Direction.DOWN, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
-        mLauncher.waitForIdle();
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to dismiss a task")) {
+            verifyActiveContainer();
+            // Dismiss the task via flinging it up.
+            mTask.fling(Direction.DOWN, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
+            mLauncher.waitForIdle();
+        }
     }
 
     /**
@@ -55,7 +58,7 @@
      */
     public Background open() {
         verifyActiveContainer();
-        LauncherInstrumentation.assertTrue("Launching task didn't open a new window: " +
+        mLauncher.assertTrue("Launching task didn't open a new window: " +
                         mTask.getParent().getContentDescription(),
                 mTask.clickAndWait(Until.newWindow(), LauncherInstrumentation.WAIT_TIME_MS));
         return new Background(mLauncher);
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 89affd1..2de53c3 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -34,21 +34,37 @@
      * Flings forward (down) and waits the fling's end.
      */
     public void flingForward() {
-        final UiObject2 widgetsContainer = verifyActiveContainer();
-        widgetsContainer.setGestureMargin(100);
-        widgetsContainer.fling(Direction.DOWN, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
-        verifyActiveContainer();
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to fling forward in widgets")) {
+            LauncherInstrumentation.log("Widgets.flingForward enter");
+            final UiObject2 widgetsContainer = verifyActiveContainer();
+            widgetsContainer.setGestureMargin(100);
+            widgetsContainer.fling(Direction.DOWN,
+                    (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung forward")) {
+                verifyActiveContainer();
+            }
+            LauncherInstrumentation.log("Widgets.flingForward exit");
+        }
     }
 
     /**
      * Flings backward (up) and waits the fling's end.
      */
     public void flingBackward() {
-        final UiObject2 widgetsContainer = verifyActiveContainer();
-        widgetsContainer.setGestureMargin(100);
-        widgetsContainer.fling(Direction.UP, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
-        mLauncher.waitForIdle();
-        verifyActiveContainer();
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to fling backwards in widgets")) {
+            LauncherInstrumentation.log("Widgets.flingBackward enter");
+            final UiObject2 widgetsContainer = verifyActiveContainer();
+            widgetsContainer.setGestureMargin(100);
+            widgetsContainer.fling(Direction.UP,
+                    (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
+            mLauncher.waitForIdle();
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung back")) {
+                verifyActiveContainer();
+            }
+            LauncherInstrumentation.log("Widgets.flingBackward exit");
+        }
     }
 
     @Override
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 5cd41f9..91443d0 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -28,13 +28,15 @@
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.TestProtocol;
+
 /**
  * Operations on the workspace screen.
  */
 public final class Workspace extends Home {
     private static final float FLING_SPEED = 3500.0F;
     private final UiObject2 mHotseat;
-    private final int ICON_DRAG_SPEED = 2000;
+    private final int ICON_DRAG_SPEED = LauncherInstrumentation.needSlowGestures() ? 100 : 570;
 
     Workspace(LauncherInstrumentation launcher) {
         super(launcher);
@@ -48,20 +50,27 @@
      */
     @NonNull
     public AllApps switchToAllApps() {
-        verifyActiveContainer();
-        // Swipe from the hotseat to near the top, e.g. 10% of the screen.
-        final UiObject2 hotseat = mHotseat;
-        final Point start = hotseat.getVisibleCenter();
-        final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.1f);
-        mLauncher.swipe(
-                start.x,
-                start.y,
-                start.x,
-                endY,
-                ALL_APPS_STATE_ORDINAL
-        );
+        try (LauncherInstrumentation.Closable c =
+                     mLauncher.addContextLayer("want to switch from workspace to all apps")) {
+            verifyActiveContainer();
+            final UiObject2 hotseat = mHotseat;
+            final Point start = hotseat.getVisibleCenter();
+            final int swipeHeight = mLauncher.getTestInfo(
+                    TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT).
+                    getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+            mLauncher.swipe(
+                    start.x,
+                    start.y,
+                    start.x,
+                    start.y - swipeHeight - mLauncher.getTouchSlop(),
+                    ALL_APPS_STATE_ORDINAL
+            );
 
-        return new AllApps(mLauncher);
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "swiped to all apps")) {
+                return new AllApps(mLauncher);
+            }
+        }
     }
 
     /**
@@ -72,9 +81,13 @@
      */
     @Nullable
     public AppIcon tryGetWorkspaceAppIcon(String appName) {
-        final UiObject2 workspace = verifyActiveContainer();
-        final UiObject2 icon = workspace.findObject(AppIcon.getAppIconSelector(appName, mLauncher));
-        return icon != null ? new AppIcon(mLauncher, icon) : null;
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to get a workspace icon")) {
+            final UiObject2 workspace = verifyActiveContainer();
+            final UiObject2 icon = workspace.findObject(
+                    AppIcon.getAppIconSelector(appName, mLauncher));
+            return icon != null ? new AppIcon(mLauncher, icon) : null;
+        }
     }
 
 
@@ -101,10 +114,10 @@
         if (!isWorkspaceScrollable(workspace)) {
             dragIconToWorkspace(
                     mLauncher,
-                    getHotseatAppIcon("Messages"),
+                    getHotseatAppIcon("Play Store"),
                     new Point(mLauncher.getDevice().getDisplayWidth(),
                             workspace.getVisibleBounds().centerY()),
-                    ICON_DRAG_SPEED);
+                    (int) (ICON_DRAG_SPEED * mLauncher.getDisplayDensity()));
             verifyActiveContainer();
         }
         assertTrue("Home screen workspace didn't become scrollable",
@@ -123,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");
     }
 
@@ -133,6 +148,8 @@
      */
     public void flingForward() {
         final UiObject2 workspace = verifyActiveContainer();
+        final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1;
+        workspace.setGestureMargins(0, 0, margin, 0);
         workspace.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
         mLauncher.waitForIdle();
         verifyActiveContainer();
@@ -144,6 +161,8 @@
      */
     public void flingBackward() {
         final UiObject2 workspace = verifyActiveContainer();
+        final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1;
+        workspace.setGestureMargins(margin, 0, 0, 0);
         workspace.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
         mLauncher.waitForIdle();
         verifyActiveContainer();
@@ -158,11 +177,18 @@
     public Widgets openAllWidgets() {
         verifyActiveContainer();
         mLauncher.getDevice().pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON);
-        return new Widgets(mLauncher);
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer("pressed Ctrl+W")) {
+            return new Widgets(mLauncher);
+        }
     }
 
     @Override
-    protected int getSwipeLength() {
-        return 100;
+    protected String getSwipeHeightRequestName() {
+        return TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT;
+    }
+
+    @Override
+    protected int getSwipeStartY() {
+        return mLauncher.waitForLauncherObject("hotseat").getVisibleBounds().top;
     }
 }
\ No newline at end of file