Merge "Fixing testOverview test" into sc-dev
diff --git a/Android.bp b/Android.bp
index 002f6fe..92cc36b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -94,27 +94,35 @@
     min_sdk_version: "28",
 }
 
+// Library with all the dependencies for building Launcher3
+android_library {
+    name: "Launcher3ResLib",
+    srcs: [ ],
+    resource_dirs: ["res"],
+    static_libs: [
+        "LauncherPluginLib",
+        "launcher_quickstep_log_protos_lite",
+        "androidx-constraintlayout_constraintlayout",
+        "androidx.recyclerview_recyclerview",
+        "androidx.dynamicanimation_dynamicanimation",
+        "androidx.fragment_fragment",
+        "androidx.preference_preference",
+        "androidx.slice_slice-view",
+        "androidx.cardview_cardview",
+        "iconloader_base",
+    ],
+    manifest: "AndroidManifest-common.xml",
+    sdk_version: "current",
+    min_sdk_version: "26",
+}
+
 //
 // Build rule for Launcher3 dependencies lib.
 //
 android_library {
     name: "Launcher3CommonDepsLib",
-    static_libs: [
-        "androidx.recyclerview_recyclerview",
-        "androidx.dynamicanimation_dynamicanimation",
-        "androidx.preference_preference",
-        "androidx.slice_slice-view",
-        "iconloader_base",
-        "LauncherPluginLib",
-        "launcher_quickstep_log_protos_lite"
-    ],
-    srcs: [
-        "src_build_config/**/*.java",
-    ],
-    resource_dirs: ["res"],
-    optimize: {
-        enabled: false,
-    },
+    srcs: ["src_build_config/**/*.java"],
+    static_libs: ["Launcher3ResLib"],
     sdk_version: "current",
     min_sdk_version: "26",
     manifest: "AndroidManifest-common.xml",
@@ -164,22 +172,42 @@
     ],
 }
 
-//
-// Launcher Robolectric test target.
-//
-java_library {
-    name: "Launcher3TestCommon",
-    libs: [
-        "Launcher3CommonDepsLib",
+// Library with all the dependencies for building quickstep
+android_library {
+    name: "QuickstepResLib",
+    srcs: [ ],
+    resource_dirs: [
+        "quickstep/res",
+        "quickstep/overview_ui_overrides/res",
     ],
+    static_libs: [
+        "Launcher3ResLib",
+        "SystemUISharedLib",
+        "SystemUI-statsd",
+    ],
+    manifest: "quickstep/AndroidManifest.xml",
+    min_sdk_version: "28",
+}
+
+
+// Source code used for test helpers
+filegroup {
+    name: "launcher-src-ext-tests",
+    srcs: ["ext_tests/src/**/*.java"],
+}
+
+// Common source files used to build launcher
+filegroup {
+    name: "launcher-src-no-build-config",
     srcs: [
         "src/**/*.java",
         "src_shortcuts_overrides/**/*.java",
-        "src_ui_overrides/**/*.java",
-        "ext_tests/src/**/*.java",
-        "tests/src_common/**/*.java",
+        "quickstep/src/**/*.java",
     ],
-    target_sdk_version: "29",
-    sdk_version: "current",
-    min_sdk_version: "26",
+}
+
+// Proguard files for Launcher3
+filegroup {
+    name: "launcher-proguard-rules",
+    srcs: ["proguard.flags"],
 }
diff --git a/OWNERS b/OWNERS
index 1d6ad8c..daad057 100644
--- a/OWNERS
+++ b/OWNERS
@@ -4,6 +4,13 @@
 # People who can approve changes for submission
 #
 
+petrcermak@google.com
+pbdr@google.com
+kideckel@google.com
+stevenckng@google.com
+ydixit@google.com
+boadway@google.com
+alinazaidi@google.com
 adamcohen@google.com
 hyunyoungs@google.com
 mrcasey@google.com
diff --git a/SharedLibWrapper/build.gradle b/SharedLibWrapper/build.gradle
deleted file mode 100644
index 674e38a..0000000
--- a/SharedLibWrapper/build.gradle
+++ /dev/null
@@ -1,17 +0,0 @@
-apply plugin: 'java'
-
-final String ANDROID_TOP = "${rootDir}/../../.."
-final String FRAMEWORK_PREBUILTS_DIR = "${ANDROID_TOP}/prebuilts/framework_intermediates/"
-
-sourceSets {
-    main {
-        java.srcDirs = ["${ANDROID_TOP}/frameworks/lib/systemui/SharedLibWrapper/src"]
-    }
-}
-
-sourceCompatibility = 1.8
-
-dependencies {
-    implementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/quickstep/libs", include: 'sysui_shared.jar')
-    compileOnly fileTree(dir: "$ANDROID_TOP/prebuilts/fullsdk-${org.gradle.internal.os.OperatingSystem.current().isMacOsX() ? "darwin" : "linux"}/platforms/${COMPILE_SDK}", include: 'android.jar')
-}
diff --git a/quickstep/Android.bp b/quickstep/Android.bp
new file mode 100644
index 0000000..585b6ad
--- /dev/null
+++ b/quickstep/Android.bp
@@ -0,0 +1,19 @@
+// Copyright (C) 2021 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.
+
+filegroup {
+    name: "launcher3-quickstep-robolectric-src",
+    path: "robolectric_tests",
+    srcs: ["robolectric_tests/src/**/*.java"],
+}
diff --git a/quickstep/res/drawable/task_menu_bg.xml b/quickstep/res/drawable/task_menu_bg.xml
index 7334d98..a60defc 100644
--- a/quickstep/res/drawable/task_menu_bg.xml
+++ b/quickstep/res/drawable/task_menu_bg.xml
@@ -14,25 +14,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:gravity="bottom">
-        <!-- Shadow -->
-        <shape>
-            <gradient android:angle="270"
-                android:endColor="@android:color/transparent"
-                android:startColor="#26000000" />
-            <size android:height="@dimen/task_card_menu_shadow_height" />
-        </shape>
-    </item>
-    <item android:bottom="@dimen/task_card_menu_shadow_height">
-        <!-- Background -->
-        <shape>
-            <corners
-                android:topLeftRadius="?android:attr/dialogCornerRadius"
-                android:topRightRadius="?android:attr/dialogCornerRadius"
-                android:bottomLeftRadius="0dp"
-                android:bottomRightRadius="0dp" />
-            <solid android:color="?attr/popupColorPrimary" />
-        </shape>
-    </item>
-</layer-list>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="?attr/popupColorPrimary" />
+</shape>
diff --git a/quickstep/res/layout/fallback_recents_activity.xml b/quickstep/res/layout/fallback_recents_activity.xml
index cd64a94..55400a7 100644
--- a/quickstep/res/layout/fallback_recents_activity.xml
+++ b/quickstep/res/layout/fallback_recents_activity.xml
@@ -19,6 +19,14 @@
     android:layout_height="match_parent"
     android:fitsSystemWindows="true">
 
+    <com.android.quickstep.views.SplitPlaceholderView
+        android:id="@+id/split_placeholder"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/split_placeholder_size"
+        android:background="@android:color/white"
+        android:alpha=".8"
+        android:visibility="gone" />
+
     <com.android.quickstep.fallback.RecentsDragLayer
         android:id="@+id/drag_layer"
         android:layout_width="match_parent"
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index fe57e9b..394e880 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -15,6 +15,14 @@
 -->
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
 
+    <com.android.quickstep.views.SplitPlaceholderView
+        android:id="@+id/split_placeholder"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/split_placeholder_size"
+        android:background="@android:color/white"
+        android:alpha=".8"
+        android:visibility="gone" />
+
     <com.android.quickstep.views.LauncherRecentsView
         android:id="@+id/overview_panel"
         android:layout_width="match_parent"
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index b124b33..84e2304 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -25,7 +25,6 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:background="@color/taskbar_background"
-        android:gravity="center"
-        android:animateLayoutChanges="true"/>
+        android:gravity="center"/>
 
 </com.android.launcher3.taskbar.TaskbarContainerView>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 9773366..755bce8 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -16,20 +16,31 @@
 
 <resources>
 
-    <dimen name="task_thumbnail_top_margin">24dp</dimen>
-    <dimen name="task_thumbnail_half_top_margin">12dp</dimen>
+    <dimen name="task_thumbnail_top_margin">80dp</dimen>
+    <dimen name="task_thumbnail_half_top_margin">40dp</dimen>
     <dimen name="task_thumbnail_icon_size">48dp</dimen>
-    <dimen name="task_icon_top_margin">-16dp</dimen>
+    <dimen name="task_icon_top_margin">16dp</dimen>
     <!-- For screens without rounded corners -->
     <dimen name="task_corner_radius_small">2dp</dimen>
 
+    <dimen name="overview_proactive_row_height">48dp</dimen>
+    <dimen name="overview_proactive_row_bottom_margin">16dp</dimen>
+
+    <dimen name="overview_minimum_next_prev_size">48dp</dimen>
+    <dimen name="overview_task_margin">16dp</dimen>
+
     <!-- Overrideable in overlay that provides the Overview Actions. -->
-    <dimen name="overview_actions_height">66dp</dimen>
-    <dimen name="overview_actions_bottom_margin_gesture">16dp</dimen>
+    <dimen name="overview_actions_height">48dp</dimen>
+    <dimen name="overview_actions_bottom_margin_gesture">12dp</dimen>
     <dimen name="overview_actions_bottom_margin_three_button">8dp</dimen>
     <dimen name="overview_actions_horizontal_margin">16dp</dimen>
 
-    <dimen name="recents_row_spacing">48dp</dimen>
+    <dimen name="overview_grid_top_margin">77dp</dimen>
+    <dimen name="overview_grid_bottom_margin">90dp</dimen>
+    <dimen name="overview_grid_side_margin">54dp</dimen>
+    <dimen name="overview_grid_row_spacing">42dp</dimen>
+    <dimen name="split_placeholder_size">110dp</dimen>
+
     <dimen name="recents_page_spacing">16dp</dimen>
     <dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
 
@@ -59,10 +70,6 @@
     <dimen name="task_card_menu_option_vertical_padding">8dp</dimen>
     <dimen name="task_card_menu_shadow_height">3dp</dimen>
     <dimen name="task_card_menu_horizontal_padding">0dp</dimen>
-    <dimen name="portrait_task_card_horz_space_big_overview">132dp</dimen>
-    <dimen name="portrait_modal_task_card_horz_space">60dp</dimen>
-    <dimen name="landscape_task_card_horz_space">200dp</dimen>
-    <dimen name="multi_window_task_card_horz_space">100dp</dimen>
     <!-- Copied from framework resource:
        docked_stack_divider_thickness - 2 * docked_stack_divider_insets -->
     <dimen name="multi_window_task_divider_size">10dp</dimen>
diff --git a/quickstep/res/values/override.xml b/quickstep/res/values/override.xml
index 605774d..705ec9d 100644
--- a/quickstep/res/values/override.xml
+++ b/quickstep/res/values/override.xml
@@ -17,8 +17,6 @@
 <!-- Class overrides for launcher with quickstep. -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string name="app_transition_manager_class" translatable="false">com.android.launcher3.LauncherAppTransitionManagerImpl</string>
-
   <string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
 
   <string name="app_launch_tracker_class" translatable="false">com.android.launcher3.appprediction.PredictionAppTracker</string>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 9a4487c..0764bb3 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -22,8 +22,6 @@
     <string name="derived_app_name" translatable="false">Quickstep</string>
 
     <!-- Options for recent tasks -->
-    <!-- Title for an option to enter split screen mode for a given app -->
-    <string name="recent_task_option_split_screen">Split screen</string>
     <!-- Title for an option to keep an app pinned to the screen until it is unpinned -->
     <string name="recent_task_option_pin">Pin</string>
     <!-- Title for an option to enter freeform mode for a given app -->
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
index 7049af0..9df9ab1 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
@@ -37,6 +37,7 @@
 
 @RunWith(RobolectricTestRunner.class)
 @LooperMode(Mode.PAUSED)
+@org.junit.Ignore
 public class RecentsActivityTest {
 
     @Test
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index 688f323..88079ae 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -17,6 +17,8 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static org.mockito.Mockito.mock;
+
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -29,6 +31,7 @@
 import com.android.launcher3.shadows.LShadowDisplay;
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.LauncherActivityInterface;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 
 import org.hamcrest.Description;
@@ -162,7 +165,7 @@
         @Override
         public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
             SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null);
-            proxy.onBuildTargetParams(builder, null, this);
+            proxy.onBuildTargetParams(builder, mock(RemoteAnimationTargetCompat.class), this);
             return new SurfaceParams[] {builder.build()};
         }
 
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 56d25f2..20a645e 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -21,11 +21,11 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_SIZE;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
 
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
-import android.app.ActivityOptions;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.os.Bundle;
@@ -47,6 +47,7 @@
 import com.android.launcher3.taskbar.TaskbarController;
 import com.android.launcher3.taskbar.TaskbarStateHandler;
 import com.android.launcher3.uioverrides.RecentsViewStateController;
+import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.quickstep.RecentsModel;
@@ -57,8 +58,10 @@
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
+import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.SplitPlaceholderView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -73,12 +76,13 @@
         implements NavigationModeChangeListener {
 
     private DepthController mDepthController = new DepthController(this);
+    private QuickstepTransitionManager mAppTransitionManager;
 
     /**
      * Reusable command for applying the back button alpha on the background thread.
      */
     public static final UiThreadHelper.AsyncCommand SET_BACK_BUTTON_ALPHA =
-            (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setBackButtonAlpha(
+            (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setNavBarButtonAlpha(
                     Float.intBitsToFloat(arg1), arg2 != 0);
 
     private OverviewActionsView mActionsView;
@@ -86,11 +90,13 @@
     private @Nullable TaskbarController mTaskbarController;
     private final TaskbarStateHandler mTaskbarStateHandler = new TaskbarStateHandler(this);
     // Will be updated when dragging from taskbar.
-    private DragOptions mWorkspaceDragOptions = new DragOptions();
+    private @Nullable DragOptions mNextWorkspaceDragOptions = null;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        mAppTransitionManager = new QuickstepTransitionManager(this);
+        mAppTransitionManager.registerRemoteAnimations();
 
         SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this);
         addMultiWindowModeChangedListener(mDepthController);
@@ -98,8 +104,9 @@
 
     @Override
     public void onDestroy() {
-        SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
+        mAppTransitionManager.onActivityDestroyed();
 
+        SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
         if (mTaskbarController != null) {
             mTaskbarController.cleanup();
         }
@@ -107,6 +114,10 @@
         super.onDestroy();
     }
 
+    public QuickstepTransitionManager getAppTransitionManager() {
+        return mAppTransitionManager;
+    }
+
     @Override
     public void onNavigationModeChanged(Mode newMode) {
         getDragLayer().recreateControllers();
@@ -206,7 +217,12 @@
 
         SysUINavigationMode.INSTANCE.get(this).updateMode();
         mActionsView = findViewById(R.id.overview_actions_view);
-        ((RecentsView) getOverviewPanel()).init(mActionsView);
+        SplitPlaceholderView splitPlaceholderView = findViewById(R.id.split_placeholder);
+        RecentsView overviewPanel = (RecentsView) getOverviewPanel();
+        splitPlaceholderView.init(
+                new SplitSelectStateController(SystemUiProxy.INSTANCE.get(this))
+        );
+        overviewPanel.init(mActionsView, splitPlaceholderView);
         mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
 
         addTaskbarIfNecessary();
@@ -270,19 +286,29 @@
         return mTaskbarController != null && mTaskbarController.isViewInTaskbar(v);
     }
 
-    @Override
-    public DragOptions getDefaultWorkspaceDragOptions() {
-        return mWorkspaceDragOptions;
+    public boolean supportsAdaptiveIconAnimation(View clickedView) {
+        return mAppTransitionManager.hasControlRemoteAppTransitionPermission()
+                && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get()
+                && !isViewInTaskbar(clickedView);
     }
 
-    public void setWorkspaceDragOptions(DragOptions dragOptions) {
-        mWorkspaceDragOptions = dragOptions;
+    @Override
+    public DragOptions getDefaultWorkspaceDragOptions() {
+        if (mNextWorkspaceDragOptions != null) {
+            DragOptions options = mNextWorkspaceDragOptions;
+            mNextWorkspaceDragOptions = null;
+            return options;
+        }
+        return super.getDefaultWorkspaceDragOptions();
+    }
+
+    public void setNextWorkspaceDragOptions(DragOptions dragOptions) {
+        mNextWorkspaceDragOptions = dragOptions;
     }
 
     @Override
     public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
-        QuickstepAppTransitionManagerImpl appTransitionManager =
-                (QuickstepAppTransitionManagerImpl) getAppTransitionManager();
+        QuickstepTransitionManager appTransitionManager = getAppTransitionManager();
         appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() {
             @Override
             public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
@@ -310,6 +336,10 @@
     @Override
     public void onDragLayerHierarchyChanged() {
         onLauncherStateOrFocusChanged();
+
+        if (mTaskbarController != null) {
+            mTaskbarController.onLauncherDragLayerHierarchyChanged();
+        }
     }
 
     @Override
@@ -351,8 +381,10 @@
      */
     private void onLauncherStateOrFocusChanged() {
         boolean shouldBackButtonBeHidden = shouldBackButtonBeHidden(getStateManager().getState());
-        UiThreadHelper.setBackButtonAlphaAsync(this, SET_BACK_BUTTON_ALPHA,
-                shouldBackButtonBeHidden ? 0f : 1f, true /* animate */);
+        if (SysUINavigationMode.getMode(this) == TWO_BUTTONS) {
+            UiThreadHelper.setBackButtonAlphaAsync(this, SET_BACK_BUTTON_ALPHA,
+                    shouldBackButtonBeHidden ? 0f : 1f, true /* animate */);
+        }
         if (getDragLayer() != null) {
             getRootView().setDisallowBackGesture(shouldBackButtonBeHidden);
         }
@@ -374,10 +406,14 @@
     }
 
     @Override
-    public ActivityOptions getActivityLaunchOptions(View v) {
-        ActivityOptions activityOptions = super.getActivityLaunchOptions(v);
-        if (activityOptions != null && mLastTouchUpTime > 0) {
-            ActivityOptionsCompat.setLauncherSourceInfo(activityOptions, mLastTouchUpTime);
+    public ActivityOptionsWrapper getActivityLaunchOptions(View v) {
+        ActivityOptionsWrapper activityOptions =
+                mAppTransitionManager.hasControlRemoteAppTransitionPermission()
+                        ? mAppTransitionManager.getActivityLaunchOptions(this, v)
+                        : super.getActivityLaunchOptions(v);
+        if (mLastTouchUpTime > 0) {
+            ActivityOptionsCompat.setLauncherSourceInfo(
+                    activityOptions.options, mLastTouchUpTime);
         }
         return activityOptions;
     }
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 588d676..be98157 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
 
@@ -29,6 +30,7 @@
 import android.os.Handler;
 
 import androidx.annotation.BinderThread;
+import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
@@ -37,8 +39,6 @@
 @TargetApi(Build.VERSION_CODES.P)
 public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat {
 
-    private static final String TAG = "LauncherAnimationRunner";
-
     private final Handler mHandler;
     private final boolean mStartAtFrontOfQueue;
     private AnimationResult mAnimationResult;
@@ -66,10 +66,7 @@
             Runnable runnable) {
         Runnable r = () -> {
             finishExistingAnimation();
-            mAnimationResult = new AnimationResult(() -> {
-                UI_HELPER_EXECUTOR.execute(runnable);
-                mAnimationResult = null;
-            });
+            mAnimationResult = new AnimationResult(() -> mAnimationResult = null, runnable);
             onCreateAnimation(transit, appTargets, wallpaperTargets, nonAppTargets,
                     mAnimationResult);
         };
@@ -126,37 +123,60 @@
 
     public static final class AnimationResult {
 
-        private final Runnable mFinishRunnable;
+        private final Runnable mSyncFinishRunnable;
+        private final Runnable mASyncFinishRunnable;
 
         private AnimatorSet mAnimator;
+        private Runnable mOnCompleteCallback;
         private boolean mFinished = false;
         private boolean mInitialized = false;
 
-        private AnimationResult(Runnable finishRunnable) {
-            mFinishRunnable = finishRunnable;
+        private AnimationResult(Runnable syncFinishRunnable, Runnable asyncFinishRunnable) {
+            mSyncFinishRunnable = syncFinishRunnable;
+            mASyncFinishRunnable = asyncFinishRunnable;
         }
 
         @UiThread
         private void finish() {
             if (!mFinished) {
-                mFinishRunnable.run();
+                mSyncFinishRunnable.run();
+                UI_HELPER_EXECUTOR.execute(() -> {
+                    mASyncFinishRunnable.run();
+                    if (mOnCompleteCallback != null) {
+                        MAIN_EXECUTOR.execute(mOnCompleteCallback);
+                    }
+                });
                 mFinished = true;
             }
         }
 
         @UiThread
         public void setAnimation(AnimatorSet animation, Context context) {
+            setAnimation(animation, context, null);
+
+        }
+
+        /**
+         * Sets the animation to play for this app launch
+         */
+        @UiThread
+        public void setAnimation(AnimatorSet animation, Context context,
+                @Nullable Runnable onCompleteCallback) {
             if (mInitialized) {
                 throw new IllegalStateException("Animation already initialized");
             }
             mInitialized = true;
             mAnimator = animation;
+            mOnCompleteCallback = onCompleteCallback;
             if (mAnimator == null) {
                 finish();
             } else if (mFinished) {
                 // Animation callback was already finished, skip the animation.
                 mAnimator.start();
                 mAnimator.end();
+                if (mOnCompleteCallback != null) {
+                    mOnCompleteCallback.run();
+                }
             } else {
                 // Start the animation
                 mAnimator.addListener(new AnimatorListenerAdapter() {
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
deleted file mode 100644
index ae4bd96..0000000
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ /dev/null
@@ -1,83 +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;
-
-import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
-
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.quickstep.TaskViewUtils;
-import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-/**
- * A {@link QuickstepAppTransitionManagerImpl} that also implements recents transitions from
- * {@link RecentsView}.
- */
-public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransitionManagerImpl {
-
-    public LauncherAppTransitionManagerImpl(Context context) {
-        super(context);
-    }
-
-    @Override
-    protected boolean isLaunchingFromRecents(@NonNull View v,
-            @Nullable RemoteAnimationTargetCompat[] targets) {
-        return mLauncher.getStateManager().getState().overviewUi
-                && findTaskViewToLaunch(mLauncher.getOverviewPanel(), v, targets) != null;
-    }
-
-    @Override
-    protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
-            @NonNull RemoteAnimationTargetCompat[] appTargets,
-            @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing) {
-        TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets,
-                launcherClosing, mLauncher.getStateManager(), mLauncher.getOverviewPanel(),
-                mLauncher.getDepthController());
-    }
-
-    @Override
-    protected Runnable composeViewContentAnimator(@NonNull AnimatorSet anim, float[] alphas,
-            float[] trans) {
-        RecentsView overview = mLauncher.getOverviewPanel();
-        ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
-                RecentsView.CONTENT_ALPHA, alphas);
-        alpha.setDuration(CONTENT_ALPHA_DURATION);
-        alpha.setInterpolator(LINEAR);
-        anim.play(alpha);
-        overview.setFreezeViewVisibility(true);
-
-        ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
-        transY.setInterpolator(AGGRESSIVE_EASE);
-        transY.setDuration(CONTENT_TRANSLATION_DURATION);
-        anim.play(transY);
-
-        return () -> {
-            overview.setFreezeViewVisibility(false);
-            overview.setTranslationY(0);
-            mLauncher.getStateManager().reapplyState();
-        };
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index 7fb0d43..5fc79f0 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -46,8 +46,8 @@
     @Override
     public boolean handleInit(Launcher launcher, boolean alreadyOnHome) {
         if (mRemoteAnimationProvider != null) {
-            QuickstepAppTransitionManagerImpl appTransitionManager =
-                    (QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
+            QuickstepTransitionManager appTransitionManager =
+                    ((BaseQuickstepLauncher) launcher).getAppTransitionManager();
 
             // Set a one-time animation provider. After the first call, this will get cleared.
             // TODO: Probably also check the intended target id.
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
similarity index 87%
rename from quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
rename to quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index c4b6961..7404dee 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3;
 
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+
 import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
@@ -24,7 +27,6 @@
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 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;
@@ -34,6 +36,7 @@
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
+import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
 import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
 import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -44,7 +47,6 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
-import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -64,22 +66,25 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
-import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.RemoteAnimationTargets;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskViewUtils;
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.StaggeredWorkspaceAnim;
 import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.recents.IStartingWindowListener;
 import com.android.systemui.shared.system.ActivityCompat;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
@@ -92,13 +97,12 @@
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
+import java.util.LinkedHashMap;
+
 /**
- * {@link LauncherAppTransitionManager} with Quickstep-specific app transitions for launching from
- * home and/or all-apps.  Not used for 3p launchers.
+ * Manages the opening and closing app transitions from Launcher
  */
-@SuppressWarnings("unused")
-public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTransitionManager
-        implements OnDeviceProfileChangeListener {
+public class QuickstepTransitionManager implements OnDeviceProfileChangeListener {
 
     private static final String TAG = "QuickstepTransition";
 
@@ -109,8 +113,8 @@
     public static final int STATUS_BAR_TRANSITION_DURATION = 120;
 
     /**
-     * Since our animations decelerate heavily when finishing, we want to start status bar animations
-     * x ms before the ending.
+     * Since our animations decelerate heavily when finishing, we want to start status bar
+     * animations x ms before the ending.
      */
     public static final int STATUS_BAR_TRANSITION_PRE_DELAY = 96;
 
@@ -142,8 +146,7 @@
     public static final int CONTENT_ALPHA_DURATION = 217;
     protected static final int CONTENT_TRANSLATION_DURATION = 350;
 
-    // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
-    public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f;
+    private static final int MAX_NUM_TASKS = 5;
 
     protected final BaseQuickstepLauncher mLauncher;
 
@@ -180,7 +183,10 @@
         }
     };
 
-    public QuickstepAppTransitionManagerImpl(Context context) {
+    // Will never be larger than MAX_NUM_TASKS
+    private LinkedHashMap<Integer, Integer> mTypeForTaskId;
+
+    public QuickstepTransitionManager(Context context) {
         mLauncher = Launcher.cast(Launcher.getLauncher(context));
         mDragLayer = mLauncher.getDragLayer();
         mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS);
@@ -194,6 +200,23 @@
         mMaxShadowRadius = res.getDimensionPixelSize(R.dimen.max_shadow_radius);
 
         mLauncher.addOnDeviceProfileChangeListener(this);
+
+        if (supportsSSplashScreen()) {
+            mTypeForTaskId = new LinkedHashMap<Integer, Integer>(MAX_NUM_TASKS) {
+                @Override
+                protected boolean removeEldestEntry(Entry<Integer, Integer> entry) {
+                    return size() > MAX_NUM_TASKS;
+                }
+            };
+
+            SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(
+                    new IStartingWindowListener.Stub() {
+                        @Override
+                        public void onTaskLaunching(int taskId, int supportedType) {
+                            mTypeForTaskId.put(taskId, supportedType);
+                        }
+                    });
+        }
     }
 
     @Override
@@ -201,37 +224,29 @@
         mDeviceProfile = dp;
     }
 
-    @Override
-    public boolean supportsAdaptiveIconAnimation(View clickedView) {
-        return hasControlRemoteAppTransitionPermission()
-                && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get()
-                && !mLauncher.isViewInTaskbar(clickedView);
-    }
-
     /**
      * @return ActivityOptions with remote animations that controls how the window of the opening
      *         targets are displayed.
      */
-    @Override
-    public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
-        if (hasControlRemoteAppTransitionPermission()) {
-            boolean fromRecents = isLaunchingFromRecents(v, null /* targets */);
-            mAppLaunchRunner = new AppLaunchAnimationRunner(mHandler, v);
-            RemoteAnimationRunnerCompat runner = new WrappedLauncherAnimationRunner<>(
-                    mAppLaunchRunner, true /* startAtFrontOfQueue */);
+    public ActivityOptionsWrapper getActivityLaunchOptions(Launcher launcher, View v) {
+        boolean fromRecents = isLaunchingFromRecents(v, null /* targets */);
+        RunnableList onEndCallback = new RunnableList();
+        mAppLaunchRunner = new AppLaunchAnimationRunner(mHandler, v, onEndCallback);
+        RemoteAnimationRunnerCompat runner = new WrappedLauncherAnimationRunner<>(
+                mHandler, mAppLaunchRunner, true /* startAtFrontOfQueue */);
 
-            // 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.
-            long duration = fromRecents
-                    ? RECENTS_LAUNCH_DURATION
-                    : APP_LAUNCH_DURATION;
+        // 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.
+        long duration = fromRecents
+                ? RECENTS_LAUNCH_DURATION
+                : APP_LAUNCH_DURATION;
 
-            long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
-                    - STATUS_BAR_TRANSITION_PRE_DELAY;
-            return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
-                    runner, duration, statusBarTransitionDelay));
-        }
-        return super.getActivityLaunchOptions(launcher, v);
+        long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
+                - STATUS_BAR_TRANSITION_PRE_DELAY;
+        RemoteAnimationAdapterCompat adapterCompat =
+                new RemoteAnimationAdapterCompat(runner, duration, statusBarTransitionDelay);
+        return new ActivityOptionsWrapper(
+                ActivityOptionsCompat.makeRemoteAnimation(adapterCompat), onEndCallback);
     }
 
     /**
@@ -244,8 +259,11 @@
      * @param targets apps that are opening/closing
      * @return true if the app is launching from recents, false if it most likely is not
      */
-    protected abstract boolean isLaunchingFromRecents(@NonNull View v,
-            @Nullable RemoteAnimationTargetCompat[] targets);
+    protected boolean isLaunchingFromRecents(@NonNull View v,
+            @Nullable RemoteAnimationTargetCompat[] targets) {
+        return mLauncher.getStateManager().getState().overviewUi
+                && findTaskViewToLaunch(mLauncher.getOverviewPanel(), v, targets) != null;
+    }
 
     /**
      * Composes the animations for a launch from the recents list.
@@ -255,9 +273,13 @@
      * @param appTargets the apps that are opening/closing
      * @param launcherClosing true if the launcher app is closing
      */
-    protected abstract void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
+    protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
             @NonNull RemoteAnimationTargetCompat[] appTargets,
-            @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing);
+            @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing) {
+        TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets,
+                launcherClosing, mLauncher.getStateManager(), mLauncher.getOverviewPanel(),
+                mLauncher.getDepthController());
+    }
 
     private boolean areAllTargetsTranslucent(@NonNull RemoteAnimationTargetCompat[] targets) {
         boolean isAllOpeningTargetTrs = true;
@@ -336,6 +358,15 @@
         return bounds;
     }
 
+    private int getOpeningTaskId(RemoteAnimationTargetCompat[] appTargets) {
+        for (RemoteAnimationTargetCompat target : appTargets) {
+            if (target.mode == MODE_OPENING) {
+                return target.taskId;
+            }
+        }
+        return -1;
+    }
+
     public void setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider,
             CancellationSignal cancellationSignal) {
         mRemoteAnimationProvider = animationProvider;
@@ -393,9 +424,6 @@
                 appsView.setLayerType(View.LAYER_TYPE_NONE, null);
             };
         } else if (mLauncher.isInState(OVERVIEW)) {
-            AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
-            launcherAnimator.play(ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS,
-                    allAppsController.getProgress(), ALL_APPS_PROGRESS_OFF_SCREEN));
             endListener = composeViewContentAnimator(launcherAnimator, alphas, trans);
         } else {
             mDragLayerAlpha.setValue(alphas[0]);
@@ -446,8 +474,27 @@
      * @param trans the translation Y values to animator to over time
      * @return listener to run when the animation ends
      */
-    protected abstract Runnable composeViewContentAnimator(@NonNull AnimatorSet anim,
-            float[] alphas, float[] trans);
+    protected Runnable composeViewContentAnimator(@NonNull AnimatorSet anim,
+            float[] alphas, float[] trans) {
+        RecentsView overview = mLauncher.getOverviewPanel();
+        ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
+                RecentsView.CONTENT_ALPHA, alphas);
+        alpha.setDuration(CONTENT_ALPHA_DURATION);
+        alpha.setInterpolator(LINEAR);
+        anim.play(alpha);
+        overview.setFreezeViewVisibility(true);
+
+        ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
+        transY.setInterpolator(AGGRESSIVE_EASE);
+        transY.setDuration(CONTENT_TRANSLATION_DURATION);
+        anim.play(transY);
+
+        return () -> {
+            overview.setFreezeViewVisibility(false);
+            overview.setTranslationY(0);
+            mLauncher.getStateManager().reapplyState();
+        };
+    }
 
     /**
      * @return Animator that controls the window of the opening targets from app icons.
@@ -471,8 +518,19 @@
         int[] dragLayerBounds = new int[2];
         mDragLayer.getLocationOnScreen(dragLayerBounds);
 
+        final boolean hasSplashScreen;
+        if (supportsSSplashScreen()) {
+            int taskId = getOpeningTaskId(appTargets);
+            int type = mTypeForTaskId.getOrDefault(taskId, STARTING_WINDOW_TYPE_NONE);
+            mTypeForTaskId.remove(taskId);
+            hasSplashScreen = type == STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+        } else {
+            hasSplashScreen = false;
+        }
+
         AnimOpenProperties prop = new AnimOpenProperties(mLauncher.getResources(), mDeviceProfile,
-                windowTargetBounds, launcherIconBounds, v, dragLayerBounds[0], dragLayerBounds[1]);
+                windowTargetBounds, launcherIconBounds, v, dragLayerBounds[0], dragLayerBounds[1],
+                hasSplashScreen);
         int left = (int) (prop.cropCenterXStart - prop.cropWidthStart / 2);
         int top = (int) (prop.cropCenterYStart - prop.cropHeightStart / 2);
         int right = (int) (left + prop.cropWidthStart);
@@ -562,7 +620,7 @@
                 Utilities.scaleRectFAboutCenter(tmpRectF, mIconScaleToFitScreen.value);
                 float windowTransX0 = tmpRectF.left - offsetX;
                 float windowTransY0 = tmpRectF.top - offsetY;
-                if (ENABLE_SHELL_STARTING_SURFACE) {
+                if (hasSplashScreen) {
                     windowTransX0 -= crop.left * scale;
                     windowTransY0 -= crop.top * scale;
                 }
@@ -638,7 +696,6 @@
     /**
      * Registers remote animations used when closing apps to home screen.
      */
-    @Override
     public void registerRemoteAnimations() {
         if (SEPARATE_RECENTS_ACTIVITY.get()) {
             return;
@@ -650,7 +707,7 @@
             definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
                     WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
                     new RemoteAnimationAdapterCompat(
-                            new WrappedLauncherAnimationRunner<>(mWallpaperOpenRunner,
+                            new WrappedLauncherAnimationRunner<>(mHandler, mWallpaperOpenRunner,
                                     false /* startAtFrontOfQueue */),
                             CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
 
@@ -659,7 +716,8 @@
                 definition.addRemoteAnimation(
                         WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
                         new RemoteAnimationAdapterCompat(
-                                new WrappedLauncherAnimationRunner<>(mKeyguardGoingAwayRunner,
+                                new WrappedLauncherAnimationRunner<>(
+                                        mHandler, mKeyguardGoingAwayRunner,
                                         true /* startAtFrontOfQueue */),
                                 CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
             }
@@ -671,7 +729,6 @@
     /**
      * Registers remote animations used when closing apps to home screen.
      */
-    @Override
     public void registerRemoteTransitions() {
         if (SEPARATE_RECENTS_ACTIVITY.get()) {
             return;
@@ -679,18 +736,20 @@
         if (hasControlRemoteAppTransitionPermission()) {
             mWallpaperOpenTransitionRunner = createWallpaperOpenRunner(false /* fromUnlock */);
             mLauncherOpenTransition = RemoteAnimationAdapterCompat.buildRemoteTransition(
-                    new WrappedLauncherAnimationRunner<>(mWallpaperOpenTransitionRunner,
+                    new WrappedLauncherAnimationRunner<>(mHandler, mWallpaperOpenTransitionRunner,
                             false /* startAtFrontOfQueue */));
             mLauncherOpenTransition.addHomeOpenCheck();
             SystemUiProxy.INSTANCE.getNoCreate().registerRemoteTransition(mLauncherOpenTransition);
         }
     }
 
-    /**
-     * Unregisters all remote animations.
-     */
-    @Override
-    public void unregisterRemoteAnimations() {
+    public void onActivityDestroyed() {
+        unregisterRemoteAnimations();
+        unregisterRemoteTransitions();
+        SystemUiProxy.INSTANCE.getNoCreate().setStartingWindowListener(null);
+    }
+
+    private void unregisterRemoteAnimations() {
         if (SEPARATE_RECENTS_ACTIVITY.get()) {
             return;
         }
@@ -705,8 +764,7 @@
         }
     }
 
-    @Override
-    public void unregisterRemoteTransitions() {
+    private void unregisterRemoteTransitions() {
         if (SEPARATE_RECENTS_ACTIVITY.get()) {
             return;
         }
@@ -821,7 +879,16 @@
         return closingAnimator;
     }
 
-    private boolean hasControlRemoteAppTransitionPermission() {
+    private boolean supportsSSplashScreen() {
+        return hasControlRemoteAppTransitionPermission()
+                && Utilities.ATLEAST_S
+                && ENABLE_SHELL_STARTING_SURFACE;
+    }
+
+    /**
+     * Returns true if we have permission to control remote app transisions
+     */
+    public boolean hasControlRemoteAppTransitionPermission() {
         return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
                 == PackageManager.PERMISSION_GRANTED;
     }
@@ -861,11 +928,6 @@
         }
 
         @Override
-        public Handler getHandler() {
-            return mHandler;
-        }
-
-        @Override
         public void onCreateAnimation(int transit,
                 RemoteAnimationTargetCompat[] appTargets,
                 RemoteAnimationTargetCompat[] wallpaperTargets,
@@ -955,15 +1017,12 @@
 
         private final Handler mHandler;
         private final View mV;
+        private final RunnableList mOnEndCallback;
 
-        AppLaunchAnimationRunner(Handler handler, View v) {
+        AppLaunchAnimationRunner(Handler handler, View v, RunnableList onEndCallback) {
             mHandler = handler;
             mV = v;
-        }
-
-        @Override
-        public Handler getHandler() {
-            return mHandler;
+            mOnEndCallback = onEndCallback;
         }
 
         @Override
@@ -998,7 +1057,7 @@
                 anim.addListener(mForceInvisibleListener);
             }
 
-            result.setAnimation(anim, mLauncher);
+            result.setAnimation(anim, mLauncher, mOnEndCallback::executeAllAndDestroy);
         }
     }
 
@@ -1030,7 +1089,8 @@
         public final float iconAlphaStart;
 
         AnimOpenProperties(Resources r, DeviceProfile dp, Rect windowTargetBounds,
-                RectF launcherIconBounds, View view, int dragLayerLeft, int dragLayerTop) {
+                RectF launcherIconBounds, View view, int dragLayerLeft, int dragLayerTop,
+                boolean hasSplashScreen) {
             // Scale the app icon to take up the entire screen. This simplifies the math when
             // animating the app window position / scale.
             float smallestSize = Math.min(windowTargetBounds.height(), windowTargetBounds.width());
@@ -1063,7 +1123,7 @@
             alphaDuration = useUpwardAnimation ? APP_LAUNCH_ALPHA_DURATION
                     : APP_LAUNCH_ALPHA_DOWN_DURATION;
 
-            if (ENABLE_SHELL_STARTING_SURFACE) {
+            if (hasSplashScreen) {
                 iconAlphaStart = 0;
 
                 // TOOD: Share value from shell when available.
diff --git a/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java b/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java
index 03cc28e..16727ec 100644
--- a/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java
+++ b/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3;
 
-import android.os.Handler;
-
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 /**
@@ -25,7 +23,7 @@
  * implementation.
  */
 public interface WrappedAnimationRunnerImpl {
-    Handler getHandler();
+
     void onCreateAnimation(int transit,
             RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets,
diff --git a/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java
index 1e1631b..e319275 100644
--- a/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import android.os.Handler;
+
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.lang.ref.WeakReference;
@@ -40,8 +42,9 @@
         extends LauncherAnimationRunner {
     private WeakReference<R> mImpl;
 
-    public WrappedLauncherAnimationRunner(R animationRunnerImpl, boolean startAtFrontOfQueue) {
-        super(animationRunnerImpl.getHandler(), startAtFrontOfQueue);
+    public WrappedLauncherAnimationRunner(
+            Handler handler, R animationRunnerImpl, boolean startAtFrontOfQueue) {
+        super(handler, startAtFrontOfQueue);
         mImpl = new WeakReference<>(animationRunnerImpl);
     }
 
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index 66b1a86..d17a5ae 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.appprediction;
 
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 
 import android.annotation.TargetApi;
@@ -30,7 +29,6 @@
 import android.text.TextPaint;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.animation.Interpolator;
 
 import androidx.annotation.ColorInt;
 import androidx.core.content.ContextCompat;
@@ -41,7 +39,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
-import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.util.Themes;
 
@@ -287,13 +284,6 @@
     }
 
     @Override
-    public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
-            PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
-        // Don't use setViewAlpha as we want to control the visibility ourselves.
-        setter.setFloat(this, VIEW_ALPHA, hasAllAppsContent ? 1 : 0, allAppsFade);
-    }
-
-    @Override
     public void setVerticalScroll(int scroll, boolean isScrolledOut) {
         setTranslationY(scroll);
         mIsScrolledOut = isScrolledOut;
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 83234e3..8e92b59 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -16,20 +16,14 @@
 
 package com.android.launcher3.appprediction;
 
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
-
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.Build;
 import android.util.AttributeSet;
-import android.util.IntProperty;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.animation.Interpolator;
 import android.widget.LinearLayout;
 
 import androidx.annotation.NonNull;
@@ -44,7 +38,6 @@
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
 import com.android.launcher3.anim.AlphaUpdateListener;
-import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.keyboard.FocusIndicatorHelper;
 import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
@@ -53,8 +46,6 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
-import com.android.launcher3.util.Themes;
-import com.android.quickstep.AnimatedFloat;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -63,22 +54,6 @@
 public class PredictionRowView extends LinearLayout implements
         OnDeviceProfileChangeListener, FloatingHeaderRow {
 
-    private static final IntProperty<PredictionRowView> TEXT_ALPHA =
-            new IntProperty<PredictionRowView>("textAlpha") {
-                @Override
-                public void setValue(PredictionRowView view, int alpha) {
-                    view.setTextAlpha(alpha);
-                }
-
-                @Override
-                public Integer get(PredictionRowView view) {
-                    return view.mIconLastSetTextAlpha;
-                }
-            };
-
-    private static final Interpolator ALPHA_FACTOR_INTERPOLATOR =
-            (t) -> (t < 0.8f) ? 0 : (t - 0.8f) / 0.2f;
-
     private final Launcher mLauncher;
     private int mNumPredictedAppsPerRow;
 
@@ -88,21 +63,9 @@
     // The set of predicted apps resolved from the component names and the current set of apps
     private final List<WorkspaceItemInfo> mPredictedApps = new ArrayList<>();
 
-    private final int mIconTextColor;
-    private final int mIconFullTextAlpha;
-    private int mIconLastSetTextAlpha;
-    // Might use mIconFullTextAlpha instead of mIconLastSetTextAlpha if we are translucent.
-    private int mIconCurrentTextAlpha;
-
     private FloatingHeaderView mParent;
     private boolean mScrolledOut;
 
-    private float mScrollTranslation = 0;
-    private final AnimatedFloat mContentAlphaFactor =
-            new AnimatedFloat(this::updateTranslationAndAlpha);
-    private final AnimatedFloat mOverviewScrollFactor =
-            new AnimatedFloat(this::updateTranslationAndAlpha);
-
     private boolean mPredictionsEnabled = false;
 
     @Nullable
@@ -117,15 +80,9 @@
         setOrientation(LinearLayout.HORIZONTAL);
 
         mFocusHelper = new SimpleFocusIndicatorHelper(this);
-
         mNumPredictedAppsPerRow = LauncherAppState.getIDP(context).numAllAppsColumns;
         mLauncher = Launcher.getLauncher(context);
         mLauncher.addOnDeviceProfileChangeListener(this);
-
-        mIconTextColor = Themes.getAttrColor(context, android.R.attr.textColorSecondary);
-        mIconFullTextAlpha = Color.alpha(mIconTextColor);
-        mIconCurrentTextAlpha = mIconFullTextAlpha;
-
         updateVisibility();
     }
 
@@ -246,7 +203,6 @@
         }
 
         int predictionCount = mPredictedApps.size();
-        int iconColor = setColorAlphaBound(mIconTextColor, mIconCurrentTextAlpha);
 
         for (int i = 0; i < getChildCount(); i++) {
             BubbleTextView icon = (BubbleTextView) getChildAt(i);
@@ -254,7 +210,6 @@
             if (predictionCount > i) {
                 icon.setVisibility(View.VISIBLE);
                 icon.applyFromWorkspaceItem(mPredictedApps.get(i));
-                icon.setTextColor(iconColor);
             } else {
                 icon.setVisibility(predictionCount == 0 ? GONE : INVISIBLE);
             }
@@ -269,27 +224,6 @@
         mParent.onHeightUpdated();
     }
 
-    public void setTextAlpha(int textAlpha) {
-        mIconLastSetTextAlpha = textAlpha;
-        if (getAlpha() < 1 && textAlpha > 0) {
-            // If the entire header is translucent, make sure the text is at full opacity so it's
-            // not double-translucent. However, we support keeping the text invisible (alpha == 0).
-            textAlpha = mIconFullTextAlpha;
-        }
-        mIconCurrentTextAlpha = textAlpha;
-        int iconColor = setColorAlphaBound(mIconTextColor, mIconCurrentTextAlpha);
-        for (int i = 0; i < getChildCount(); i++) {
-            ((BubbleTextView) getChildAt(i)).setTextColor(iconColor);
-        }
-    }
-
-    @Override
-    public void setAlpha(float alpha) {
-        super.setAlpha(alpha);
-        // Reapply text alpha so that we update it to be full alpha if the row is now translucent.
-        setTextAlpha(mIconLastSetTextAlpha);
-    }
-
     @Override
     public boolean hasOverlappingRendering() {
         return false;
@@ -299,34 +233,11 @@
     @Override
     public void setVerticalScroll(int scroll, boolean isScrolledOut) {
         mScrolledOut = isScrolledOut;
-        updateTranslationAndAlpha();
         if (!isScrolledOut) {
-            mScrollTranslation = scroll;
-            updateTranslationAndAlpha();
+            setTranslationY(scroll);
         }
-    }
-
-    private void updateTranslationAndAlpha() {
-        if (mPredictionsEnabled) {
-            setTranslationY((1 - mOverviewScrollFactor.value) * mScrollTranslation);
-
-            float factor = ALPHA_FACTOR_INTERPOLATOR.getInterpolation(mOverviewScrollFactor.value);
-            float endAlpha = factor + (1 - factor) * (mScrolledOut ? 0 : 1);
-            setAlpha(mContentAlphaFactor.value * endAlpha);
-            AlphaUpdateListener.updateVisibility(this);
-        }
-    }
-
-    @Override
-    public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
-            PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
-        // Text follows all apps visibility
-        int textAlpha = hasHeaderExtra && hasAllAppsContent ? mIconFullTextAlpha : 0;
-        setter.setInt(this, TEXT_ALPHA, textAlpha, allAppsFade);
-        setter.setFloat(mOverviewScrollFactor, AnimatedFloat.VALUE,
-                (hasHeaderExtra && !hasAllAppsContent) ? 1 : 0, LINEAR);
-        setter.setFloat(mContentAlphaFactor, AnimatedFloat.VALUE, hasHeaderExtra ? 1 : 0,
-                headerFade);
+        setAlpha(mScrolledOut ? 0 : 1);
+        AlphaUpdateListener.updateVisibility(this);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
index 9944270..eed493d 100644
--- a/quickstep/src/com/android/launcher3/model/AppEventProducer.java
+++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
@@ -42,6 +42,7 @@
 import android.app.prediction.AppTargetId;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.ShortcutInfo;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
@@ -62,9 +63,11 @@
 import com.android.launcher3.logging.StatsLogManager.EventEnum;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;
 
 import java.util.Locale;
+import java.util.Optional;
 import java.util.function.ObjIntConsumer;
 import java.util.function.Predicate;
 
@@ -174,6 +177,7 @@
             return null;
         }
         ComponentName cn = null;
+        ShortcutInfo shortcutInfo = null;
         String id = null;
 
         switch (info.getItemCase()) {
@@ -188,6 +192,14 @@
                 LauncherAtom.Shortcut si = info.getShortcut();
                 if (!TextUtils.isEmpty(si.getShortcutId())
                         && (cn = parseNullable(si.getShortcutName())) != null) {
+                    Optional<ShortcutInfo> opt = new ShortcutRequest(mContext,
+                            userHandle).forPackage(cn.getPackageName(), si.getShortcutId()).query(
+                            ShortcutRequest.ALL).stream().findFirst();
+                    if (opt.isPresent()) {
+                        shortcutInfo = opt.get();
+                    } else {
+                        return null;
+                    }
                     id = "shortcut:" + si.getShortcutId();
                 }
                 break;
@@ -210,6 +222,9 @@
                 return createTempFolderTarget();
         }
         if (id != null && cn != null) {
+            if (shortcutInfo != null) {
+                return new AppTarget.Builder(new AppTargetId(id), shortcutInfo).build();
+            }
             return new AppTarget.Builder(new AppTargetId(id), cn.getPackageName(), userHandle)
                     .setClassName(cn.getClassName())
                     .build();
@@ -217,6 +232,7 @@
         return null;
     }
 
+
     private AppTarget createTempFolderTarget() {
         return new AppTarget.Builder(new AppTargetId("folder:" + SystemClock.uptimeMillis()),
                 mContext.getPackageName(), Process.myUserHandle())
diff --git a/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
index 13501a4..ce94305 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.AnimatedFloat.VALUE;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
@@ -30,7 +31,7 @@
 import com.android.quickstep.SystemUiProxy;
 
 /**
- * State handler for animating back button alpha
+ * State handler for animating back button alpha in two-button nav mode.
  */
 public class BackButtonAlphaHandler implements StateHandler<LauncherState> {
 
@@ -51,14 +52,11 @@
             return;
         }
 
-        if (!SysUINavigationMode.getMode(mLauncher).hasGestures) {
-            // If the nav mode is not gestural, then force back button alpha to be 1
-            UiThreadHelper.setBackButtonAlphaAsync(mLauncher,
-                    BaseQuickstepLauncher.SET_BACK_BUTTON_ALPHA, 1f, true /* animate */);
+        if (SysUINavigationMode.getMode(mLauncher) != TWO_BUTTONS) {
             return;
         }
 
-        mBackAlpha.value = SystemUiProxy.INSTANCE.get(mLauncher).getLastBackButtonAlpha();
+        mBackAlpha.value = SystemUiProxy.INSTANCE.get(mLauncher).getLastNavButtonAlpha();
         animation.setFloat(mBackAlpha, VALUE,
                 mLauncher.shouldBackButtonBeHidden(toState) ? 0 : 1, LINEAR);
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
index 1e5e3e7..528f43e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
@@ -83,10 +83,15 @@
 
     private ViewTreeObserverWrapper.OnComputeInsetsListener createTaskbarInsetsComputer() {
         return insetsInfo -> {
-            if (getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
-                // We're invisible, let touches pass through us.
+            if (getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD
+                    || mTaskbarView.isDraggingItem()) {
+                // We're invisible or dragging out of taskbar, let touches pass through us.
                 insetsInfo.touchableRegion.setEmpty();
                 insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+                // TODO(b/182234653): Shouldn't need to do this, but for the meantime, reporting
+                // that visibleInsets is empty allows DragEvents through. Setting them as completely
+                // empty reverts to default behavior, so set 1 px instead.
+                insetsInfo.visibleInsets.set(0, 0, 0, 1);
             } else {
                  // We're visible again, accept touches anywhere in our bounds.
                 insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index 544835c..a2b2ddd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -19,6 +19,8 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
+import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_TASKBAR;
+import static com.android.launcher3.AbstractFloatingView.TYPE_REPLACE_TASKBAR_WITH_HOTSEAT;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
@@ -40,8 +42,10 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Hotseat;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.QuickstepAppTransitionManagerImpl;
+import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.folder.Folder;
@@ -144,22 +148,13 @@
                         ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
                                 ActivityOptions.makeBasic());
                     } else if (tag instanceof FolderInfo) {
-                        FolderIcon folderIcon = (FolderIcon) view;
-                        Folder folder = folderIcon.getFolder();
-
-                        setTaskbarWindowFullscreen(true);
-
-                        mTaskbarContainerView.post(() -> {
-                            folder.animateOpen();
-
-                            folder.iterateOverItems((itemInfo, itemView) -> {
-                                itemView.setOnClickListener(getItemOnClickListener());
-                                itemView.setOnLongClickListener(getItemOnLongClickListener());
-                                // To play haptic when dragging, like other Taskbar items do.
-                                itemView.setHapticFeedbackEnabled(true);
-                                return false;
-                            });
-                        });
+                        if (mLauncher.hasBeenResumed()) {
+                            FolderInfo folderInfo = (FolderInfo) tag;
+                            onClickedOnFolderFromHome(folderInfo);
+                        } else {
+                            FolderIcon folderIcon = (FolderIcon) view;
+                            onClickedOnFolderInApp(folderIcon);
+                        }
                     } else {
                         ItemClickHandler.INSTANCE.onClick(view);
                     }
@@ -169,6 +164,34 @@
                 };
             }
 
+            // Open the real folder in Launcher.
+            private void onClickedOnFolderFromHome(FolderInfo folderInfo) {
+                alignRealHotseatWithTaskbar();
+
+                FolderIcon folderIcon = (FolderIcon) mLauncher.getHotseat()
+                        .getFirstItemMatch((info, v) -> info == folderInfo);
+                folderIcon.post(folderIcon::performClick);
+            }
+
+            // Open the Taskbar folder, and handle clicks on folder items.
+            private void onClickedOnFolderInApp(FolderIcon folderIcon) {
+                Folder folder = folderIcon.getFolder();
+
+                setTaskbarWindowFullscreen(true);
+
+                mTaskbarContainerView.post(() -> {
+                    folder.animateOpen();
+
+                    folder.iterateOverItems((itemInfo, itemView) -> {
+                        itemView.setOnClickListener(getItemOnClickListener());
+                        itemView.setOnLongClickListener(getItemOnLongClickListener());
+                        // To play haptic when dragging, like other Taskbar items do.
+                        itemView.setHapticFeedbackEnabled(true);
+                        return false;
+                    });
+                });
+            }
+
             @Override
             public View.OnLongClickListener getItemOnLongClickListener() {
                 return view -> {
@@ -246,6 +269,11 @@
      * Removes the Taskbar from the screen, and removes any obsolete listeners etc.
      */
     public void cleanup() {
+        if (mAnimator != null) {
+            // End this first, in case it relies on properties that are about to be cleaned up.
+            mAnimator.end();
+        }
+
         mTaskbarView.cleanup();
         mTaskbarContainerView.cleanup();
         removeFromWindowManager();
@@ -253,10 +281,6 @@
         mTaskbarVisibilityController.cleanup();
         mHotseatController.cleanup();
         mRecentsController.cleanup();
-
-        if (mAnimator != null) {
-            mAnimator.end();
-        }
     }
 
     private void removeFromWindowManager() {
@@ -298,7 +322,7 @@
      * Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
      */
     public void onLauncherResumedOrPaused(boolean isResumed) {
-        long duration = QuickstepAppTransitionManagerImpl.CONTENT_ALPHA_DURATION;
+        long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION;
         if (mAnimator != null) {
             mAnimator.cancel();
         }
@@ -306,6 +330,7 @@
             mAnimator = createAnimToLauncher(null, duration);
         } else {
             mAnimator = createAnimToApp(duration);
+            replaceTaskbarWithHotseatOrViceVersa();
         }
         mAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -355,6 +380,7 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 mTaskbarView.updateHotseatItemsVisibility();
+                setReplaceTaskbarWithHotseat(false);
             }
         });
         return anim.buildAnim();
@@ -445,12 +471,49 @@
      */
     public void alignRealHotseatWithTaskbar() {
         Rect hotseatBounds = new Rect();
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        int hotseatHeight = grid.workspacePadding.bottom + grid.getInsets().bottom;
+        int hotseatTopDiff = hotseatHeight - grid.taskbarSize;
+
         mTaskbarView.getHotseatBoundsAtScale(getTaskbarScaleOnHome()).roundOut(hotseatBounds);
-        mLauncher.getHotseat().setPadding(hotseatBounds.left, hotseatBounds.top,
+        mLauncher.getHotseat().setPadding(hotseatBounds.left, hotseatBounds.top + hotseatTopDiff,
                 mTaskbarView.getWidth() - hotseatBounds.right,
                 mTaskbarView.getHeight() - hotseatBounds.bottom);
     }
 
+    /**
+     * A view was added or removed from DragLayer, check if we need to hide our hotseat copy and
+     * show the real one instead.
+     */
+    public void onLauncherDragLayerHierarchyChanged() {
+        replaceTaskbarWithHotseatOrViceVersa();
+    }
+
+    private void replaceTaskbarWithHotseatOrViceVersa() {
+        boolean replaceTaskbarWithHotseat = AbstractFloatingView.getTopOpenViewWithType(mLauncher,
+                TYPE_REPLACE_TASKBAR_WITH_HOTSEAT) != null;
+        if (!mLauncher.hasBeenResumed()) {
+            replaceTaskbarWithHotseat = false;
+        }
+        setReplaceTaskbarWithHotseat(replaceTaskbarWithHotseat);
+
+        boolean hideTaskbar = AbstractFloatingView.getTopOpenViewWithType(mLauncher,
+                TYPE_HIDE_TASKBAR) != null;
+        mTaskbarVisibilityController.animateToVisibilityForFloatingView(hideTaskbar ? 0f : 1f);
+    }
+
+    private void setReplaceTaskbarWithHotseat(boolean replaceTaskbarWithHotseat) {
+        Hotseat hotseat = mLauncher.getHotseat();
+        if (replaceTaskbarWithHotseat) {
+            alignRealHotseatWithTaskbar();
+            hotseat.getReplaceTaskbarAlpha().setValue(1f);
+            mTaskbarView.setHotseatViewsHidden(true);
+        } else {
+            hotseat.getReplaceTaskbarAlpha().setValue(0f);
+            mTaskbarView.setHotseatViewsHidden(false);
+        }
+    }
+
     private float getTaskbarScaleOnHome() {
         return 1f / mTaskbarContainerView.getTaskbarActivityContext().getTaskbarIconScale();
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragListener.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragListener.java
index 2bd5861..dc27df1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragListener.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragListener.java
@@ -37,7 +37,6 @@
 
     private final BaseQuickstepLauncher mLauncher;
     private final ItemInfo mDraggedItem;
-    private final DragOptions mDragOptions;
     // Randomly generated id used to verify the drag event.
     private final String mId;
 
@@ -51,19 +50,20 @@
     public TaskbarDragListener(BaseQuickstepLauncher launcher, ItemInfo draggedItem) {
         mLauncher = launcher;
         mDraggedItem = draggedItem;
-        mDragOptions = new DragOptions();
-        mDragOptions.simulatedDndStartPoint = new Point();
         mId = UUID.randomUUID().toString();
     }
 
     protected void init(DragLayer dragLayer) {
         mDragLayer = dragLayer;
         mDragLayer.setOnDragListener(this);
+        // Temporarily disable haptics, as system will already play one when drag and drop starts.
+        mDragLayer.setHapticFeedbackEnabled(false);
     }
 
     private void cleanup() {
         mDragLayer.setOnDragListener(null);
-        mLauncher.setWorkspaceDragOptions(new DragOptions());
+        mLauncher.setNextWorkspaceDragOptions(null);
+        mDragLayer.setHapticFeedbackEnabled(true);
     }
 
     /**
@@ -88,8 +88,10 @@
                 cleanup();
                 return false;
             }
-            mDragOptions.simulatedDndStartPoint.set((int) dragEvent.getX(), (int) dragEvent.getY());
-            mLauncher.setWorkspaceDragOptions(mDragOptions);
+            DragOptions dragOptions = new DragOptions();
+            dragOptions.simulatedDndStartPoint = new Point((int) dragEvent.getX(),
+                    (int) dragEvent.getY());
+            mLauncher.setNextWorkspaceDragOptions(dragOptions);
             hotseatView.performLongClick();
         } else if (dragEvent.getAction() == DragEvent.ACTION_DRAG_ENDED) {
             cleanup();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
index 082343e..b1bafdb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
@@ -78,7 +78,10 @@
                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
                 // Since the hotseat might be laid out vertically or horizontally, use whichever
                 // index is higher.
-                hotseatItemInfos[Math.max(lp.cellX, lp.cellY)] = itemInfo;
+                int index = Math.max(lp.cellX, lp.cellY);
+                if (0 <= index && index < hotseatItemInfos.length) {
+                    hotseatItemInfos[index] = itemInfo;
+                }
             }
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index a729e77..1d762e9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.taskbar;
 
+import android.animation.LayoutTransition;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
@@ -63,6 +64,7 @@
     private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
 
     // Initialized in init().
+    private LayoutTransition mLayoutTransition;
     private int mHotseatStartIndex;
     private int mHotseatEndIndex;
     private View mHotseatRecentsDivider;
@@ -76,6 +78,7 @@
     private boolean mIsDraggingItem;
     // Only non-null when the corresponding Folder is open.
     private @Nullable FolderIcon mLeaveBehindFolderIcon;
+    private boolean mIsHotseatHidden;
 
     public TaskbarView(@NonNull Context context) {
         this(context, null);
@@ -107,6 +110,9 @@
     }
 
     protected void init(int numHotseatIcons, int numRecentIcons) {
+        mLayoutTransition = new LayoutTransition();
+        setLayoutTransitionsEnabled(true);
+
         mHotseatStartIndex = 0;
         mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1;
         updateHotseatItems(new ItemInfo[numHotseatIcons]);
@@ -119,6 +125,10 @@
         updateRecentTasks(new Task[numRecentIcons]);
     }
 
+    private void setLayoutTransitionsEnabled(boolean enabled) {
+        setLayoutTransition(enabled ? mLayoutTransition : null);
+    }
+
     protected void cleanup() {
         removeAllViews();
     }
@@ -206,9 +216,19 @@
         }
     }
 
+    /**
+     * Hides or shows the hotseat items immediately (without layout transitions).
+     */
+    protected void setHotseatViewsHidden(boolean hidden) {
+        mIsHotseatHidden = hidden;
+        setLayoutTransitionsEnabled(false);
+        updateHotseatItemsVisibility();
+        setLayoutTransitionsEnabled(true);
+    }
+
     private void updateHotseatItemVisibility(View hotseatView) {
         if (hotseatView.getTag() != null) {
-            hotseatView.setVisibility(VISIBLE);
+            hotseatView.setVisibility(mIsHotseatHidden ? INVISIBLE : VISIBLE);
         } else {
             int oldVisibility = hotseatView.getVisibility();
             int newVisibility = mControllerCallbacks.getEmptyHotseatViewVisibility();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
index 6d20d97..2228eba 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
@@ -31,6 +31,7 @@
 public class TaskbarVisibilityController {
 
     private static final long IME_VISIBILITY_ALPHA_DURATION = 120;
+    private static final long FLOATING_VIEW_VISIBILITY_ALPHA_DURATION = 120;
 
     private final BaseQuickstepLauncher mLauncher;
     private final TaskbarController.TaskbarVisibilityControllerCallbacks mTaskbarCallbacks;
@@ -44,6 +45,8 @@
             this::updateVisibilityAlpha);
     private AnimatedFloat mTaskbarVisibilityAlphaForIme = new AnimatedFloat(
             this::updateVisibilityAlpha);
+    private AnimatedFloat mTaskbarVisibilityAlphaForFloatingView = new AnimatedFloat(
+            this::updateVisibilityAlpha);
 
     public TaskbarVisibilityController(BaseQuickstepLauncher launcher,
             TaskbarController.TaskbarVisibilityControllerCallbacks taskbarCallbacks) {
@@ -59,12 +62,14 @@
         boolean isImeVisible = (SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags()
                 & QuickStepContract.SYSUI_STATE_IME_SHOWING) != 0;
         mTaskbarVisibilityAlphaForIme.updateValue(isImeVisible ? 0f : 1f);
+        mTaskbarVisibilityAlphaForFloatingView.updateValue(1f);
 
         onTaskbarBackgroundAlphaChanged();
         updateVisibilityAlpha();
     }
 
     protected void cleanup() {
+        setNavBarButtonAlpha(1f);
     }
 
     protected AnimatedFloat getTaskbarVisibilityForLauncherState() {
@@ -81,6 +86,11 @@
                 .setDuration(IME_VISIBILITY_ALPHA_DURATION).start();
     }
 
+    protected void animateToVisibilityForFloatingView(float toAlpha) {
+        mTaskbarVisibilityAlphaForIme.animateToValue(mTaskbarVisibilityAlphaForFloatingView.value,
+                toAlpha).setDuration(FLOATING_VIEW_VISIBILITY_ALPHA_DURATION).start();
+    }
+
     private void onTaskbarBackgroundAlphaChanged() {
         mTaskbarCallbacks.updateTaskbarBackgroundAlpha(mTaskbarBackgroundAlpha.value);
         updateVisibilityAlpha();
@@ -92,7 +102,16 @@
         // LauncherState if Launcher is paused.
         float alphaDueToLauncher = Math.max(mTaskbarBackgroundAlpha.value,
                 mTaskbarVisibilityAlphaForLauncherState.value);
-        float alphaDueToOther = mTaskbarVisibilityAlphaForIme.value;
-        mTaskbarCallbacks.updateTaskbarVisibilityAlpha(alphaDueToLauncher * alphaDueToOther);
+        float alphaDueToOther = mTaskbarVisibilityAlphaForIme.value
+                * mTaskbarVisibilityAlphaForFloatingView.value;
+        float taskbarAlpha = alphaDueToLauncher * alphaDueToOther;
+        mTaskbarCallbacks.updateTaskbarVisibilityAlpha(taskbarAlpha);
+
+        // Make the nav bar invisible if taskbar is visible.
+        setNavBarButtonAlpha(1f - taskbarAlpha);
+    }
+
+    private void setNavBarButtonAlpha(float navBarAlpha) {
+        SystemUiProxy.INSTANCE.get(mLauncher).setNavBarButtonAlpha(navBarAlpha, false);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 0f13ef9..d65c59e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -32,6 +32,7 @@
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
 import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
+import static com.android.quickstep.views.RecentsView.TASK_PRIMARY_TRANSLATION;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 
 import android.util.FloatProperty;
@@ -44,6 +45,7 @@
 import com.android.launcher3.graphics.OverviewScrim;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -74,8 +76,8 @@
         SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher));
         SCRIM_MULTIPLIER.set(scrim, 1f);
         getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
-        RECENTS_GRID_PROGRESS.set(mRecentsView, state.displayOverviewTasksAsGrid(mLauncher)
-                ? 1f : 0f);
+        RECENTS_GRID_PROGRESS.set(mRecentsView,
+                state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f);
     }
 
     @Override
@@ -105,7 +107,12 @@
                 config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
         setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
                 config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
-        setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
+        PagedOrientationHandler orientationHandler =
+                ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
+        FloatProperty taskViewsFloat = orientationHandler.getSplitSelectTaskOffset(
+                TASK_PRIMARY_TRANSLATION, TASK_SECONDARY_TRANSLATION, mLauncher.getDeviceProfile());
+        setter.setFloat(mRecentsView, taskViewsFloat,
+                toState.getOverviewSecondaryTranslation(mLauncher),
                 config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
 
         setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
@@ -121,7 +128,7 @@
                 toState.getOverviewModalness(),
                 config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
         setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
-                toState.displayOverviewTasksAsGrid(mLauncher) ? 1f : 0f, LINEAR);
+                toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f, LINEAR);
     }
 
     abstract FloatProperty getTaskModalnessProperty();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index d330a68..b4aa596 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -34,11 +34,11 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
@@ -85,8 +85,13 @@
     public void onDraw(Canvas canvas) {
         int count = canvas.save();
         if (!mIsPinned) {
-            boolean isBadged = getTag() instanceof WorkspaceItemInfo
-                    && !Process.myUserHandle().equals(((ItemInfo) getTag()).user);
+            boolean isBadged = false;
+            if (getTag() instanceof WorkspaceItemInfo) {
+                WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
+                isBadged = !Process.myUserHandle().equals(info.user)
+                        || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
+                        || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+            }
             drawEffect(canvas, isBadged);
             canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
             canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index b009629..bae97d7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -30,7 +30,6 @@
 import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
 
 import android.content.Intent;
@@ -235,7 +234,7 @@
     @Override
     public void onDestroy() {
         super.onDestroy();
-        getAppsView().getSearchUiManager().destroy();
+        getAppsView().getSearchUiManager().destroySearch();
         mHotseatPredictionController.destroy();
     }
 
@@ -262,14 +261,13 @@
                 RecentsView rv = getOverviewPanel();
                 TaskView tasktolaunch = rv.getTaskViewAt(0);
                 if (tasktolaunch != null) {
-                    tasktolaunch.launchTask(false, success -> {
+                    tasktolaunch.launchTask(success -> {
                         if (!success) {
                             getStateManager().goToState(OVERVIEW);
-                            tasktolaunch.notifyTaskLaunchFailed(TAG);
                         } else {
                             getStateManager().moveToRestState();
                         }
-                    }, MAIN_EXECUTOR.getHandler());
+                    });
                 } else {
                     getStateManager().goToState(NORMAL);
                 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index c9de662..750f673 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -17,11 +17,14 @@
 
 import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
 import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
+import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
+import static com.android.launcher3.LauncherState.SPLIT_PLACHOLDER_VIEW;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
+import static com.android.quickstep.views.SplitPlaceholderView.ALPHA_FLOAT;
 
 import android.annotation.TargetApi;
 import android.os.Build;
@@ -77,11 +80,26 @@
                     AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
         }
 
+        // Create or dismiss split screen select animations
+        LauncherState currentState = mLauncher.getStateManager().getState();
+        if (isSplitSelectionState(toState) && !isSplitSelectionState(currentState)) {
+            builder.add(mRecentsView.createSplitSelectInitAnimation().buildAnim());
+        } else if (!isSplitSelectionState(toState) && isSplitSelectionState(currentState)) {
+            builder.add(mRecentsView.cancelSplitSelect(true).buildAnim());
+        }
+
         setAlphas(builder, config, toState);
         builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
                 toState.getOverviewFullscreenProgress(), LINEAR);
     }
 
+    /**
+     * @return true if {@param toState} is {@link LauncherState#OVERVIEW_SPLIT_SELECT}
+     */
+    private boolean isSplitSelectionState(@NonNull LauncherState toState) {
+        return toState == OVERVIEW_SPLIT_SELECT;
+    }
+
     private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config,
             LauncherState state) {
         float clearAllButtonAlpha = (state.getVisibleElements(mLauncher) & CLEAR_ALL_BUTTON) != 0
@@ -93,6 +111,11 @@
         propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
                 MultiValueAlpha.VALUE, overviewButtonAlpha, config.getInterpolator(
                         ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
+
+        float splitPlaceholderAlpha = state.areElementsVisible(mLauncher, SPLIT_PLACHOLDER_VIEW) ?
+                1 : 0;
+        propertySetter.setFloat(mRecentsView.getSplitPlaceholder(), ALPHA_FLOAT,
+                splitPlaceholderAlpha, LINEAR);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 37c774b..b2f8a40 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -78,7 +78,7 @@
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        return ALL_APPS_HEADER | ALL_APPS_HEADER_EXTRA | ALL_APPS_CONTENT;
+        return ALL_APPS_CONTENT;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 2ad718b..fb58bf6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.quickstep.util.LayoutUtils;
@@ -76,7 +77,7 @@
     }
 
     @Override
-    public boolean displayOverviewTasksAsGrid(Launcher launcher) {
+    public boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
         return false;
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index d480b6d..5a28cfd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -123,7 +123,7 @@
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        return displayOverviewTasksAsGrid(launcher) ? CLEAR_ALL_BUTTON
+        return displayOverviewTasksAsGrid(launcher.getDeviceProfile()) ? CLEAR_ALL_BUTTON
                 : CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
     }
 
@@ -133,8 +133,8 @@
     }
 
     @Override
-    public boolean displayOverviewTasksAsGrid(Launcher launcher) {
-        return launcher.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
+    public boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
+        return deviceProfile.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
     }
 
     @Override
@@ -155,7 +155,7 @@
     public void onBackPressed(Launcher launcher) {
         TaskView taskView = launcher.<RecentsView>getOverviewPanel().getRunningTaskView();
         if (taskView != null) {
-            taskView.launchTask(true);
+            taskView.launchTaskAnimated();
         } else {
             super.onBackPressed(launcher);
         }
@@ -175,4 +175,12 @@
     public static OverviewState newModalTaskState(int id) {
         return new OverviewModalTaskState(id);
     }
+
+    /**
+     * New Overview substate representing state where 1 app for split screen has been selected and
+     * pinned and user is selecting the second one
+     */
+    public static OverviewState newSplitSelectState(int id) {
+        return new SplitScreenSelectState(id);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
new file mode 100644
index 0000000..722d74a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2021 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;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * New Overview substate representing state where 1 app for split screen has been selected and
+ * pinned and user is selecting the second one
+ */
+public class SplitScreenSelectState extends OverviewState {
+    public SplitScreenSelectState(int id) {
+        super(id);
+    }
+
+    @Override
+    public void onBackPressed(Launcher launcher) {
+        launcher.getStateManager().goToState(OVERVIEW);
+    }
+
+    @Override
+    public int getVisibleElements(Launcher launcher) {
+        return SPLIT_PLACHOLDER_VIEW;
+    }
+
+    @Override
+    public float getOverviewSecondaryTranslation(Launcher launcher) {
+        RecentsView recentsView = launcher.getOverviewPanel();
+        PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
+        int splitPosition = recentsView.getSplitPlaceholder().getSplitController()
+                .getActiveSplitPositionOption().mStagePosition;
+        int direction = orientationHandler.getSplitTranslationDirectionFactor(splitPosition);
+        return launcher.getResources().getDimension(R.dimen.split_placeholder_size) * direction;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 45cb46f..6c71995 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -155,7 +155,7 @@
             super.onDragEnd(velocity);
         }
 
-        View searchView = mLauncher.getAppsView().getSearchView();
+        View searchView = mLauncher.getHotseat().getQsb();
         if (searchView instanceof FeedbackHandler) {
             ((FeedbackHandler) searchView).resetFeedback();
         }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index e0c041e..6da2201 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -22,7 +22,7 @@
 
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
@@ -48,6 +48,7 @@
 import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
 
 import android.animation.Animator;
@@ -80,9 +81,9 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.tracing.InputConsumerProto;
 import com.android.launcher3.tracing.SwipeHandlerProto;
@@ -124,14 +125,15 @@
  * Handles the navigation gestures when Launcher is the default home activity.
  */
 @TargetApi(Build.VERSION_CODES.R)
-public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends RecentsView>
+public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
+        Q extends RecentsView, S extends BaseState<S>>
         extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener,
         RecentsAnimationCallbacks.RecentsAnimationListener {
     private static final String TAG = "AbsSwipeUpHandler";
 
     private static final String[] STATE_NAMES = DEBUG_STATES ? new String[17] : null;
 
-    protected final BaseActivityInterface<?, T> mActivityInterface;
+    protected final BaseActivityInterface<S, T> mActivityInterface;
     protected final InputConsumerProxy mInputConsumerProxy;
     protected final ActivityInitListener mActivityInitListener;
     // Callbacks to be made once the recents animation starts
@@ -615,7 +617,7 @@
         final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
         if (passed != mPassedOverviewThreshold) {
             mPassedOverviewThreshold = passed;
-            if (!mDeviceState.isFullyGesturalNavMode()) {
+            if (!mDeviceState.isTwoButtonNavMode()) {
                 performHapticFeedback();
             }
         }
@@ -721,6 +723,9 @@
 
     @UiThread
     public void onGestureStarted(boolean isLikelyToStartNewTask) {
+        mActivityInterface.closeOverlay();
+        TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+
         if (mRecentsView != null) {
             InteractionJankMonitorWrapper.begin(mRecentsView,
                     InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH, 2000 /* ms timeout */);
@@ -848,6 +853,10 @@
 
     private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
             boolean isCancel) {
+        if (mDeviceState.isButtonNavMode()) {
+            // Button mode, this is only used to go to recents
+            return RECENTS;
+        }
         final GestureEndTarget endTarget;
         final boolean goingToNewTask;
         if (mRecentsView != null) {
@@ -934,9 +943,15 @@
                     duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
             }
         }
-        Interpolator interpolator =
-                endTarget == RECENTS ? (mDp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()
-                        ? ACCEL_DEACCEL : OVERSHOOT_1_2) : DEACCEL;
+        Interpolator interpolator;
+        S state = mActivityInterface.stateFromGestureEndTarget(endTarget);
+        if (state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile())) {
+            interpolator = ACCEL_DEACCEL;
+        } else if (endTarget == RECENTS) {
+            interpolator = OVERSHOOT_1_2;
+        } else {
+            interpolator = DEACCEL;
+        }
 
         if (endTarget.isLauncher) {
             mInputConsumerProxy.enable();
@@ -946,15 +961,20 @@
         } else if (endTarget == RECENTS) {
             if (mRecentsView != null) {
                 int nearestPage = mRecentsView.getDestinationPage();
+                boolean isScrolling = false;
                 if (mRecentsView.getNextPage() != nearestPage) {
                     // We shouldn't really scroll to the next page when swiping up to recents.
                     // Only allow settling on the next page if it's nearest to the center.
                     mRecentsView.snapToPage(nearestPage, Math.toIntExact(duration));
+                    isScrolling = true;
                 }
                 if (mRecentsView.getScroller().getDuration() > MAX_SWIPE_DURATION) {
                     mRecentsView.snapToPage(mRecentsView.getNextPage(), (int) MAX_SWIPE_DURATION);
+                    isScrolling = true;
                 }
-                duration = Math.max(duration, mRecentsView.getScroller().getDuration());
+                if (!mDeviceState.isButtonNavMode() || isScrolling) {
+                    duration = Math.max(duration, mRecentsView.getScroller().getDuration());
+                }
             }
         }
 
@@ -1126,8 +1146,9 @@
                 }
             });
             animatorSet.play(windowAnim);
-            if (mRecentsView != null && mDp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()
-                    && mGestureState.getEndTarget() == RECENTS) {
+            S state = mActivityInterface.stateFromGestureEndTarget(mGestureState.getEndTarget());
+            if (mRecentsView != null && state.displayOverviewTasksAsGrid(
+                    mActivity.getDeviceProfile())) {
                 animatorSet.play(ObjectAnimator.ofFloat(mRecentsView, RECENTS_GRID_PROGRESS, 1));
                 animatorSet.play(mTaskViewSimulator.gridProgress.animateToValue(0, 1));
             }
@@ -1469,7 +1490,9 @@
                     mSwipePipToHomeAnimator.getDestinationBounds());
             mRecentsAnimationController.setFinishTaskBounds(
                     mSwipePipToHomeAnimator.getTaskId(),
-                    mSwipePipToHomeAnimator.getDestinationBounds());
+                    mSwipePipToHomeAnimator.getDestinationBounds(),
+                    mSwipePipToHomeAnimator.getFinishWindowCrop(),
+                    mSwipePipToHomeAnimator.getFinishTransform());
             mIsSwipingPipToHome = false;
         }
     }
@@ -1624,19 +1647,17 @@
                 mGestureState.updateLastStartedTaskId(taskId);
                 boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
                         .contains(taskId);
-                nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
-                        success -> {
-                            resultCallback.accept(success);
-                            if (success) {
-                                if (hasTaskPreviouslyAppeared) {
-                                    onRestartPreviouslyAppearedTask();
-                                }
-                            } else {
-                                mActivityInterface.onLaunchTaskFailed();
-                                nextTask.notifyTaskLaunchFailed(TAG);
-                                mRecentsAnimationController.finish(true /* toRecents */, null);
-                            }
-                        }, MAIN_EXECUTOR.getHandler());
+                nextTask.launchTask(success -> {
+                    resultCallback.accept(success);
+                    if (success) {
+                        if (hasTaskPreviouslyAppeared) {
+                            onRestartPreviouslyAppearedTask();
+                        }
+                    } else {
+                        mActivityInterface.onLaunchTaskFailed();
+                        mRecentsAnimationController.finish(true /* toRecents */, null);
+                    }
+                }, true /* freezeTaskList */);
             } else {
                 mActivityInterface.onLaunchTaskFailed();
                 Toast.makeText(mContext, R.string.activity_not_available, LENGTH_SHORT).show();
@@ -1756,7 +1777,6 @@
 
     public interface Factory {
 
-        AbsSwipeUpHandler<StatefulActivity<?>, RecentsView> newHandler(
-                GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
+        AbsSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
deleted file mode 100644
index d159fa0..0000000
--- a/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ /dev/null
@@ -1,180 +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 static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.statehandlers.DepthController.DEPTH;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.util.Log;
-import android.view.animation.Interpolator;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.taskbar.TaskbarController;
-import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.SurfaceTransactionApplier;
-import com.android.quickstep.util.TaskViewSimulator;
-import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-/**
- * Provider for the atomic (for 3-button mode) remote window animation from the app to the overview.
- *
- * @param <T> activity that contains the overview
- */
-final class AppToOverviewAnimationProvider<T extends StatefulActivity<?>> extends
-        RemoteAnimationProvider {
-
-    private static final long RECENTS_LAUNCH_DURATION = 250;
-    private static final String TAG = "AppToOverviewAnimationProvider";
-
-    private final BaseActivityInterface<?, T> mActivityInterface;
-    // The id of the currently running task that is transitioning to overview.
-    private final RunningTaskInfo mTargetTask;
-    private final RecentsAnimationDeviceState mDeviceState;
-
-    private T mActivity;
-    private RecentsView mRecentsView;
-
-    AppToOverviewAnimationProvider(
-            BaseActivityInterface<?, T> activityInterface, RunningTaskInfo targetTask,
-            RecentsAnimationDeviceState deviceState) {
-        mActivityInterface = activityInterface;
-        mTargetTask = targetTask;
-        mDeviceState = deviceState;
-    }
-
-    /**
-     * Callback for when the activity is ready/initialized.
-     *
-     * @param activity the activity that is ready
-     * @param wasVisible true if it was visible before
-     */
-    boolean onActivityReady(T activity, Boolean wasVisible) {
-        activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTask);
-        AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
-        BaseActivityInterface.AnimationFactory factory = mActivityInterface.prepareRecentsUI(
-                mDeviceState,
-                wasVisible, (controller) -> {
-                    controller.getNormalController().dispatchOnStart();
-                    controller.getNormalController().getAnimationPlayer().end();
-                });
-        factory.createActivityInterface(RECENTS_LAUNCH_DURATION);
-        factory.setRecentsAttachedToAppWindow(true, false);
-        mActivity = activity;
-        mRecentsView = mActivity.getOverviewPanel();
-        return false;
-    }
-
-    /**
-     * Create remote window animation from the currently running app to the overview panel.
-     *
-     * @param appTargets the target apps
-     * @return animation from app to overview
-     */
-    @Override
-    public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
-            RemoteAnimationTargetCompat[] wallpaperTargets) {
-        PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
-        if (mActivity == null) {
-            Log.e(TAG, "Animation created, before activity");
-            return pa.buildAnim();
-        }
-
-        mRecentsView.setRunningTaskIconScaledDown(true);
-        pa.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                mActivityInterface.onSwipeUpToRecentsComplete();
-                mRecentsView.animateUpRunningTaskIconScale();
-            }
-        });
-
-        DepthController depthController = mActivityInterface.getDepthController();
-        if (depthController != null) {
-            pa.addFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(mActivity),
-                    OVERVIEW.getDepth(mActivity), TOUCH_RESPONSE_INTERPOLATOR);
-        }
-
-        TaskbarController taskbarController = mActivityInterface.getTaskbarController();
-        if (taskbarController != null) {
-            pa.add(taskbarController.createAnimToLauncher(OVERVIEW, getRecentsLaunchDuration()));
-        }
-
-        RemoteAnimationTargets targets = new RemoteAnimationTargets(appTargets,
-                wallpaperTargets, MODE_CLOSING);
-
-        // Use the top closing app to determine the insets for the animation
-        RemoteAnimationTargetCompat runningTaskTarget = mTargetTask == null ? null
-                : targets.findTask(mTargetTask.taskId);
-        if (runningTaskTarget == null) {
-            Log.e(TAG, "No closing app");
-            return pa.buildAnim();
-        }
-
-        TaskViewSimulator tsv = new TaskViewSimulator(mActivity, mRecentsView.getSizeStrategy());
-        tsv.setDp(mActivity.getDeviceProfile());
-        tsv.setOrientationState(mRecentsView.getPagedViewOrientedState());
-        tsv.setPreview(runningTaskTarget);
-
-        TransformParams params = new TransformParams()
-                .setTargetSet(targets)
-                .setSyncTransactionApplier(new SurfaceTransactionApplier(mActivity.getRootView()));
-
-        AnimatedFloat recentsAlpha = new AnimatedFloat(() -> { });
-        params.setBaseBuilderProxy((builder, app, p)
-                -> builder.withAlpha(recentsAlpha.value));
-
-        Interpolator taskInterpolator;
-        if (targets.isAnimatingHome()) {
-            params.setHomeBuilderProxy((builder, app, p) -> builder.withAlpha(1 - p.getProgress()));
-
-            taskInterpolator = TOUCH_RESPONSE_INTERPOLATOR;
-            pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1, TOUCH_RESPONSE_INTERPOLATOR);
-        } else {
-            // When animation from app to recents, the recents layer is drawn on top of the app. To
-            // prevent the overlap, we animate the task first and then quickly fade in the recents.
-            taskInterpolator = clampToProgress(TOUCH_RESPONSE_INTERPOLATOR, 0, 0.8f);
-            pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1,
-                    clampToProgress(TOUCH_RESPONSE_INTERPOLATOR, 0.8f, 1));
-        }
-
-        pa.addFloat(params, TransformParams.PROGRESS, 0, 1, taskInterpolator);
-        tsv.addAppToOverviewAnim(pa, taskInterpolator);
-        pa.addOnFrameCallback(() -> tsv.apply(params));
-        return pa.buildAnim();
-    }
-
-    /**
-     * Get duration of animation from app to overview.
-     *
-     * @return duration of animation
-     */
-    long getRecentsLaunchDuration() {
-        return RECENTS_LAUNCH_DURATION;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index ce14197..5942b3a 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -33,6 +33,7 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.os.Build;
+import android.view.Gravity;
 import android.view.MotionEvent;
 
 import androidx.annotation.Nullable;
@@ -53,6 +54,7 @@
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.SplitScreenBounds;
 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.system.RemoteAnimationTargetCompat;
 
@@ -197,33 +199,23 @@
      */
     public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
             PagedOrientationHandler orientedState) {
-        calculateTaskSize(context, dp, getExtraSpace(context, dp, orientedState), outRect);
-    }
-
-    protected abstract float getExtraSpace(Context context, DeviceProfile dp,
-            PagedOrientationHandler orientedState);
-
-    private void calculateTaskSize(Context context, DeviceProfile dp, float extraVerticalSpace,
-            Rect outRect) {
         Resources res = context.getResources();
 
-        final int paddingResId;
-        if (dp.isMultiWindowMode) {
-            paddingResId = R.dimen.multi_window_task_card_horz_space;
-        } else if (dp.isVerticalBarLayout()) {
-            paddingResId = R.dimen.landscape_task_card_horz_space;
-        } else {
-            paddingResId = R.dimen.portrait_task_card_horz_space_big_overview;
-        }
-        float paddingHorz = res.getDimension(paddingResId);
-        float paddingVert = 0;
+        int taskMargin = res.getDimensionPixelSize(R.dimen.overview_task_margin);
+        int taskIconAndMargin = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_size)
+                + res.getDimensionPixelSize(R.dimen.task_icon_top_margin);
+        int proactiveRowAndMargin = res.getDimensionPixelSize(R.dimen.overview_proactive_row_height)
+                + res.getDimensionPixelSize(R.dimen.overview_proactive_row_bottom_margin);
 
-        calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert,
-                res.getDimension(R.dimen.task_thumbnail_top_margin), outRect);
+        calculateTaskSizeInternal(context, dp,
+                taskIconAndMargin + taskMargin,
+                proactiveRowAndMargin + getOverviewActionsHeight(context) + taskMargin,
+                res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin,
+                outRect);
     }
 
     private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
-            float extraVerticalSpace, float paddingHorz, float paddingVert, float topIconMargin,
+            int claimedSpaceAbove, int claimedSpaceBelow, int minimumHorizontalPadding,
             Rect outRect) {
         float taskWidth, taskHeight;
         Rect insets = dp.getInsets();
@@ -231,52 +223,64 @@
             WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context);
             taskWidth = bounds.availableSize.x;
             taskHeight = bounds.availableSize.y;
-        } else {
+        } else if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
             taskWidth = dp.availableWidthPx;
             taskHeight = dp.availableHeightPx;
+        } else {
+            taskWidth = dp.widthPx;
+            taskHeight = dp.heightPx;
         }
 
-        // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless
-        // we override the insets ourselves.
-        int launcherVisibleWidth = dp.widthPx - insets.left - insets.right;
-        int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom;
+        Rect potentialTaskRect = new Rect(0, 0, dp.widthPx, dp.heightPx);
+        potentialTaskRect.inset(insets.left, insets.top, insets.right, insets.bottom);
+        potentialTaskRect.inset(
+                minimumHorizontalPadding,
+                claimedSpaceAbove,
+                minimumHorizontalPadding,
+                claimedSpaceBelow);
 
-        float availableHeight = launcherVisibleHeight
-                - topIconMargin - extraVerticalSpace - paddingVert;
-        float availableWidth = launcherVisibleWidth - paddingHorz;
+        float scale = Math.min(
+                potentialTaskRect.width() / taskWidth,
+                potentialTaskRect.height() / taskHeight);
+        int outWidth = Math.round(scale * taskWidth);
+        int outHeight = Math.round(scale * taskHeight);
 
-        float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
-        float outWidth = scale * taskWidth;
-        float outHeight = scale * taskHeight;
+        Gravity.apply(Gravity.CENTER, outWidth, outHeight, potentialTaskRect, outRect);
+    }
 
-        // Center in the visible space
-        float x = insets.left + (launcherVisibleWidth - outWidth) / 2;
-        float y = insets.top + Math.max(topIconMargin,
-                (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2);
-        outRect.set(Math.round(x), Math.round(y),
-                Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight));
+    /**
+     * Calculates the overview grid size for the provided device configuration.
+     */
+    public final void calculateGridSize(Context context, DeviceProfile dp, Rect outRect) {
+        Resources res = context.getResources();
+        int topMargin = res.getDimensionPixelSize(R.dimen.overview_grid_top_margin);
+        int bottomMargin = res.getDimensionPixelSize(R.dimen.overview_grid_bottom_margin);
+        int sideMargin = res.getDimensionPixelSize(R.dimen.overview_grid_side_margin);
+
+        Rect insets = dp.getInsets();
+        outRect.set(0, 0, dp.widthPx, dp.heightPx);
+        outRect.inset(Math.max(insets.left, sideMargin), Math.max(insets.top, topMargin),
+                Math.max(insets.right, sideMargin), Math.max(insets.bottom, bottomMargin));
     }
 
     /**
      * Calculates the modal taskView size for the provided device configuration
      */
     public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) {
-        float paddingHorz = context.getResources().getDimension(dp.isMultiWindowMode
-                ? R.dimen.multi_window_task_card_horz_space
-                : dp.isVerticalBarLayout()
-                        ? R.dimen.landscape_task_card_horz_space
-                        : R.dimen.portrait_modal_task_card_horz_space);
-        float extraVerticalSpace = getOverviewActionsHeight(context);
-        float paddingVert = 0;
-        float topIconMargin = 0;
-        calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert,
-                topIconMargin, outRect);
+        Resources res = context.getResources();
+        calculateTaskSizeInternal(
+                context, dp,
+                res.getDimensionPixelSize(R.dimen.overview_task_margin),
+                getOverviewActionsHeight(context)
+                        + res.getDimensionPixelSize(R.dimen.overview_task_margin),
+                res.getDimensionPixelSize(R.dimen.overview_task_margin),
+                outRect);
     }
 
-    /** Gets the space that the overview actions will take, including margins. */
-    public final float getOverviewActionsHeight(Context context) {
+    /** Gets the space that the overview actions will take, including bottom margin. */
+    public final int getOverviewActionsHeight(Context context) {
         Resources res = context.getResources();
-        float actionsBottomMargin = 0;
+        int actionsBottomMargin = 0;
         if (getMode(context) == Mode.THREE_BUTTONS) {
             actionsBottomMargin = res.getDimensionPixelSize(
                     R.dimen.overview_actions_bottom_margin_three_button);
@@ -284,9 +288,8 @@
             actionsBottomMargin = res.getDimensionPixelSize(
                     R.dimen.overview_actions_bottom_margin_gesture);
         }
-        float overviewActionsHeight = actionsBottomMargin
+        return actionsBottomMargin
                 + res.getDimensionPixelSize(R.dimen.overview_actions_height);
-        return overviewActionsHeight;
     }
 
     /**
@@ -303,6 +306,11 @@
     public void onSystemUiFlagsChanged(int systemUiStateFlags) {
     }
 
+    /**
+     * Returns the expected STATE_TYPE from the provided GestureEndTarget.
+     */
+    public abstract STATE_TYPE stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget);
+
     public interface AnimationFactory {
 
         void createActivityInterface(long transitionLength);
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 96e4f38..e13d1a4 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -18,6 +18,7 @@
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.fallback.RecentsState.BACKGROUND_APP;
 import static com.android.quickstep.fallback.RecentsState.DEFAULT;
+import static com.android.quickstep.fallback.RecentsState.HOME;
 
 import android.content.Context;
 import android.graphics.Rect;
@@ -26,7 +27,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.util.ActivityInitListener;
@@ -82,6 +82,7 @@
     @Override
     public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
             boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback) {
+        notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
         DefaultAnimationFactory factory = new DefaultAnimationFactory(callback);
         factory.initUI();
         return factory;
@@ -104,7 +105,7 @@
     @Override
     public RecentsView getVisibleRecentsView() {
         RecentsActivity activity = getCreatedActivity();
-        if (activity != null && activity.hasWindowFocus()) {
+        if (activity != null && activity.hasBeenResumed()) {
             return activity.getOverviewPanel();
         }
         return null;
@@ -157,8 +158,23 @@
     }
 
     @Override
-    protected float getExtraSpace(Context context, DeviceProfile dp,
-            PagedOrientationHandler orientationHandler) {
-        return context.getResources().getDimensionPixelSize(R.dimen.overview_actions_height);
+    public RecentsState stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget) {
+        switch (endTarget) {
+            case RECENTS:
+                return DEFAULT;
+            case NEW_TASK:
+            case LAST_TASK:
+                return BACKGROUND_APP;
+            case HOME:
+            default:
+                return HOME;
+        }
+    }
+
+    private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) {
+        // reset layout on swipe to home
+        RecentsView recentsView = getCreatedActivity().getOverviewPanel();
+        recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
+                rotationTouchHelper.getDisplayRotation());
     }
 }
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index a80c111..7e4a352 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -55,6 +55,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.util.TransformParams.BuilderProxy;
@@ -73,7 +74,7 @@
  */
 @TargetApi(Build.VERSION_CODES.R)
 public class FallbackSwipeHandler extends
-        AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
+        AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView, RecentsState> {
 
     /**
      * Message used for receiving gesture nav contract information. We use a static messenger to
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 8d67ee6..ebdc1e6 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -25,6 +25,7 @@
 import android.content.Intent;
 import android.os.Build;
 
+import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.tracing.GestureStateProto;
 import com.android.launcher3.tracing.SwipeHandlerProto;
@@ -213,7 +214,8 @@
     /**
      * @return the interface to the activity handing the UI updates for this gesture.
      */
-    public <T extends StatefulActivity<?>> BaseActivityInterface<?, T> getActivityInterface() {
+    public <S extends BaseState<S>,
+            T extends StatefulActivity<S>> BaseActivityInterface<S, T> getActivityInterface() {
         return mActivityInterface;
     }
 
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 3f3e5ad..98b96b2 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -18,14 +18,12 @@
 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.LauncherState.QUICK_SWITCH;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.SysUINavigationMode.getMode;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Rect;
-import android.util.Log;
 import android.view.MotionEvent;
 
 import androidx.annotation.Nullable;
@@ -36,13 +34,11 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherInitListener;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.taskbar.TaskbarController;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.GestureState.GestureEndTarget;
 import com.android.quickstep.SysUINavigationMode.Mode;
@@ -181,7 +177,7 @@
     @UiThread
     private Launcher getVisibleLauncher() {
         Launcher launcher = getCreatedActivity();
-        return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus()
+        return (launcher != null) && launcher.isStarted() && launcher.hasBeenResumed()
                 ? launcher : null;
     }
 
@@ -192,6 +188,7 @@
             return false;
         }
 
+        closeOverlay();
         launcher.getStateManager().goToState(OVERVIEW,
                 launcher.getStateManager().shouldAnimateStateChange(), onCompleteCallback);
         return true;
@@ -263,27 +260,6 @@
     }
 
     @Override
-    protected float getExtraSpace(Context context, DeviceProfile dp,
-            PagedOrientationHandler orientationHandler) {
-        Resources res = context.getResources();
-        //TODO: this needs to account for the swipe gesture height and accessibility
-        // UI when shown.
-        float actionsBottomMargin = 0;
-        if (!dp.isVerticalBarLayout()) {
-            if (getMode(context) == Mode.THREE_BUTTONS) {
-                actionsBottomMargin = res.getDimensionPixelSize(
-                    R.dimen.overview_actions_bottom_margin_three_button);
-            } else {
-                actionsBottomMargin = res.getDimensionPixelSize(
-                    R.dimen.overview_actions_bottom_margin_gesture);
-            }
-        }
-        float actionsHeight = actionsBottomMargin
-                + res.getDimensionPixelSize(R.dimen.overview_actions_height);
-        return actionsHeight;
-    }
-
-    @Override
     void onOverviewServiceBound() {
         final BaseQuickstepLauncher activity = getCreatedActivity();
         if (activity == null) return;
@@ -296,7 +272,7 @@
         if (taskbarController == null) {
             return;
         }
-        LauncherState toState = endTarget == GestureEndTarget.RECENTS ? OVERVIEW : NORMAL;
+        LauncherState toState = stateFromGestureEndTarget(endTarget);
         taskbarController.createAnimToLauncher(toState, duration).start();
     }
 
@@ -327,4 +303,18 @@
         }
         return taskbarController.isDraggingItem();
     }
+
+    @Override
+    public LauncherState stateFromGestureEndTarget(GestureEndTarget endTarget) {
+        switch (endTarget) {
+            case RECENTS:
+                return OVERVIEW;
+            case NEW_TASK:
+            case LAST_TASK:
+                return QUICK_SWITCH;
+            case HOME:
+            default:
+                return NORMAL;
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 842fb84..1ce4201 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -27,6 +27,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.util.RectFSpringAnim;
@@ -39,7 +40,7 @@
  * Temporary class to allow easier refactoring
  */
 public class LauncherSwipeHandlerV2 extends
-        AbsSwipeUpHandler<BaseQuickstepLauncher, RecentsView> {
+        AbsSwipeUpHandler<BaseQuickstepLauncher, RecentsView, LauncherState> {
 
     public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 985389e..923d4f1 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -16,29 +16,28 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.annotation.TargetApi;
-import android.content.Context;
+import android.content.Intent;
+import android.graphics.PointF;
 import android.os.Build;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.view.ViewConfiguration;
 
 import androidx.annotation.BinderThread;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
 
 import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.quickstep.util.ActivityInitListener;
-import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.launcher3.util.RunnableList;
+import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.systemui.shared.system.LatencyTrackerCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+import java.util.ArrayList;
 
 /**
  * Helper class to handle various atomic commands for switching between Overview.
@@ -46,66 +45,191 @@
 @TargetApi(Build.VERSION_CODES.P)
 public class OverviewCommandHelper {
 
-    private final Context mContext;
-    private final RecentsAnimationDeviceState mDeviceState;
+    public static final int TYPE_SHOW = 1;
+    public static final int TYPE_SHOW_NEXT_FOCUS = 2;
+    public static final int TYPE_HIDE = 3;
+    public static final int TYPE_TOGGLE = 4;
+
+    private static final String TRANSITION_NAME = "Transition:toOverview";
+
+    private final TouchInteractionService mService;
     private final OverviewComponentObserver mOverviewComponentObserver;
+    private final TaskAnimationManager mTaskAnimationManager;
+    private final ArrayList<CommandInfo> mPendingCommands = new ArrayList<>();
 
-    private long mLastToggleTime;
-
-    public OverviewCommandHelper(Context context, RecentsAnimationDeviceState deviceState,
-            OverviewComponentObserver observer) {
-        mContext = context;
-        mDeviceState = deviceState;
+    public OverviewCommandHelper(TouchInteractionService service,
+            OverviewComponentObserver observer,
+            TaskAnimationManager taskAnimationManager) {
+        mService = service;
         mOverviewComponentObserver = observer;
+        mTaskAnimationManager = taskAnimationManager;
     }
 
-    @BinderThread
-    public void onOverviewToggle() {
-        // If currently screen pinning, do not enter overview
-        if (mDeviceState.isScreenPinningActive()) {
+    /**
+     * Called when the command finishes execution.
+     */
+    private void scheduleNextTask(CommandInfo command) {
+        if (!mPendingCommands.isEmpty() && mPendingCommands.get(0) == command) {
+            mPendingCommands.remove(0);
+            executeNext();
+        }
+    }
+
+    /**
+     * Executes the next command from the queue. If the command finishes immediately (returns true),
+     * it continues to execute the next command, until the queue is empty of a command defer's its
+     * completion (returns false).
+     */
+    @UiThread
+    private void executeNext() {
+        if (mPendingCommands.isEmpty()) {
             return;
         }
-
-        TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-        MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
+        CommandInfo cmd = mPendingCommands.get(0);
+        if (executeCommand(cmd)) {
+            scheduleNextTask(cmd);
+        }
     }
 
+    @UiThread
+    private void addCommand(CommandInfo cmd) {
+        boolean wasEmpty = mPendingCommands.isEmpty();
+        mPendingCommands.add(cmd);
+        if (wasEmpty) {
+            executeNext();
+        }
+    }
+
+    /**
+     * Adds a command to be executed next, after all pending tasks are completed
+     */
     @BinderThread
-    public void onOverviewShown(boolean triggeredFromAltTab) {
-        if (triggeredFromAltTab) {
-            TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-        }
-        MAIN_EXECUTOR.execute(new ShowRecentsCommand(triggeredFromAltTab));
+    public void addCommand(int type) {
+        CommandInfo cmd = new CommandInfo(type);
+        MAIN_EXECUTOR.execute(() -> addCommand(cmd));
     }
 
-    @BinderThread
-    public void onOverviewHidden() {
-        MAIN_EXECUTOR.execute(new HideRecentsCommand());
+    private TaskView getNextTask(RecentsView view) {
+        final TaskView runningTaskView = view.getRunningTaskView();
+
+        if (runningTaskView == null) {
+            return view.getTaskViewCount() > 0 ? view.getTaskViewAt(0) : null;
+        } else {
+            final TaskView nextTask = view.getNextTaskView();
+            return nextTask != null ? nextTask : runningTaskView;
+        }
     }
 
-    private class ShowRecentsCommand extends RecentsActivityCommand {
-
-        private final boolean mTriggeredFromAltTab;
-
-        ShowRecentsCommand(boolean triggeredFromAltTab) {
-            mTriggeredFromAltTab = triggeredFromAltTab;
+    private boolean launchTask(RecentsView recents, @Nullable TaskView taskView, CommandInfo cmd) {
+        RunnableList callbackList = null;
+        if (taskView != null) {
+            taskView.setEndQuickswitchCuj(true);
+            callbackList = taskView.launchTaskAnimated();
         }
 
-        @Override
-        protected boolean handleCommand(long elapsedTime) {
-            // TODO: Go to the next page if started from alt-tab.
-            return mActivityInterface.getVisibleRecentsView() != null;
+        if (callbackList != null) {
+            callbackList.add(() -> scheduleNextTask(cmd));
+            return false;
+        } else {
+            recents.startHome();
+            return true;
         }
+    }
 
-        @Override
-        protected void onTransitionComplete() {
-            // TODO(b/138729100) This doesn't execute first time launcher is run
-            if (mTriggeredFromAltTab) {
-                RecentsView rv =  mActivityInterface.getVisibleRecentsView();
-                if (rv == null) {
-                    return;
+    /**
+     * Executes the task and returns true if next task can be executed. If false, then the next
+     * task is deferred until {@link #scheduleNextTask} is called
+     */
+    private <T extends StatefulActivity<?>> boolean executeCommand(CommandInfo cmd) {
+        BaseActivityInterface<?, T> activityInterface =
+                mOverviewComponentObserver.getActivityInterface();
+        RecentsView recents = activityInterface.getVisibleRecentsView();
+        if (recents == null) {
+            if (cmd.type == TYPE_HIDE) {
+                // already hidden
+                return true;
+            }
+        } else {
+            switch (cmd.type) {
+                case TYPE_SHOW:
+                    // already visible
+                    return true;
+                case TYPE_HIDE: {
+                    int currentPage = recents.getNextPage();
+                    TaskView tv = (currentPage >= 0 && currentPage < recents.getTaskViewCount())
+                            ? (TaskView) recents.getPageAt(currentPage)
+                            : null;
+                    return launchTask(recents, tv, cmd);
                 }
+                case TYPE_TOGGLE:
+                    return launchTask(recents, getNextTask(recents), cmd);
+            }
+        }
 
+        if (activityInterface.switchToRecentsIfVisible(() -> scheduleNextTask(cmd))) {
+            // If successfully switched, wait until animation finishes
+            return false;
+        }
+
+        final T activity = activityInterface.getCreatedActivity();
+        if (activity != null) {
+            InteractionJankMonitorWrapper.begin(
+                    activity.getRootView(),
+                    InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+        }
+
+        GestureState gestureState = mService.createGestureState(GestureState.DEFAULT_STATE);
+        AbsSwipeUpHandler interactionHandler = mService.getSwipeUpHandlerFactory()
+                .newHandler(gestureState, cmd.createTime);
+        interactionHandler.setGestureEndCallback(
+                () -> onTransitionComplete(cmd, interactionHandler));
+
+        Intent intent = new Intent(interactionHandler.getLaunchIntent());
+        interactionHandler.initWhenReady(intent);
+
+        RecentsAnimationListener recentAnimListener = new RecentsAnimationListener() {
+            @Override
+            public void onRecentsAnimationStart(RecentsAnimationController controller,
+                    RecentsAnimationTargets targets) {
+                interactionHandler.onGestureEnded(0, new PointF(), new PointF());
+                cmd.removeListener(this);
+            }
+
+            @Override
+            public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+                interactionHandler.onGestureCancelled();
+                cmd.removeListener(this);
+            }
+        };
+
+        if (mTaskAnimationManager.isRecentsAnimationRunning()) {
+            cmd.mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(gestureState);
+            cmd.mActiveCallbacks.addListener(interactionHandler);
+            mTaskAnimationManager.notifyRecentsAnimationState(interactionHandler);
+            interactionHandler.onGestureStarted(true /*isLikelyToStartNewTask*/);
+
+            cmd.mActiveCallbacks.addListener(recentAnimListener);
+            mTaskAnimationManager.notifyRecentsAnimationState(recentAnimListener);
+        } else {
+            intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, gestureState.getGestureId());
+            cmd.mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(
+                    gestureState, intent, interactionHandler);
+            interactionHandler.onGestureStarted(false /*isLikelyToStartNewTask*/);
+            cmd.mActiveCallbacks.addListener(recentAnimListener);
+        }
+
+        Trace.beginAsyncSection(TRANSITION_NAME, 0);
+        return false;
+    }
+
+    private void onTransitionComplete(CommandInfo cmd, AbsSwipeUpHandler handler) {
+        cmd.removeListener(handler);
+        Trace.endAsyncSection(TRANSITION_NAME, 0);
+
+        if (cmd.type == TYPE_SHOW_NEXT_FOCUS) {
+            RecentsView rv =
+                    mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
+            if (rv != null) {
                 // Ensure that recents view has focus so that it receives the followup key inputs
                 TaskView taskView = rv.getNextTaskView();
                 if (taskView == null) {
@@ -120,130 +244,22 @@
                 }
             }
         }
+        scheduleNextTask(cmd);
     }
 
-    private class HideRecentsCommand extends RecentsActivityCommand {
+    private static class CommandInfo {
+        public final long createTime = SystemClock.elapsedRealtime();
+        public final int type;
+        RecentsAnimationCallbacks mActiveCallbacks;
 
-        @Override
-        protected boolean handleCommand(long elapsedTime) {
-            RecentsView recents = mActivityInterface.getVisibleRecentsView();
-            if (recents == null) {
-                return false;
-            }
-            int currentPage = recents.getNextPage();
-            if (currentPage >= 0 && currentPage < recents.getTaskViewCount()) {
-                ((TaskView) recents.getPageAt(currentPage)).launchTask(true);
-            } else {
-                recents.startHome();
-            }
-            return true;
-        }
-    }
-
-    private class RecentsActivityCommand<T extends StatefulActivity<?>> implements Runnable {
-
-        private static final String TRANSITION_NAME = "Transition:toOverview";
-        protected final BaseActivityInterface<?, T> mActivityInterface;
-        private final long mCreateTime;
-        private final AppToOverviewAnimationProvider<T> mAnimationProvider;
-
-        private final long mToggleClickedTime = SystemClock.uptimeMillis();
-        private ActivityInitListener mListener;
-
-        public RecentsActivityCommand() {
-            mActivityInterface = mOverviewComponentObserver.getActivityInterface();
-            mCreateTime = SystemClock.elapsedRealtime();
-            mAnimationProvider = new AppToOverviewAnimationProvider<>(mActivityInterface,
-                    ActivityManagerWrapper.getInstance().getRunningTask(), mDeviceState);
-
-            // Preload the plan
-            RecentsModel.INSTANCE.get(mContext).getTasks(null);
+        CommandInfo(int type) {
+            this.type = type;
         }
 
-        @Override
-        public void run() {
-            long elapsedTime = mCreateTime - mLastToggleTime;
-            mLastToggleTime = mCreateTime;
-
-            if (handleCommand(elapsedTime)) {
-                // Command already handled.
-                return;
+        void removeListener(RecentsAnimationListener listener) {
+            if (mActiveCallbacks != null) {
+                mActiveCallbacks.removeListener(listener);
             }
-
-            if (mActivityInterface.switchToRecentsIfVisible(this::onTransitionComplete)) {
-                // If successfully switched, then return
-                return;
-            }
-
-            final T activity = mActivityInterface.getCreatedActivity();
-            if (activity != null) {
-                InteractionJankMonitorWrapper.begin(
-                        activity.getRootView(),
-                        InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
-            }
-
-            // Otherwise, start overview.
-            mListener = mActivityInterface.createActivityInitListener(this::onActivityReady);
-            mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
-                    new RemoteAnimationProvider() {
-                        @Override
-                        public AnimatorSet createWindowAnimation(
-                                RemoteAnimationTargetCompat[] appTargets,
-                                RemoteAnimationTargetCompat[] wallpaperTargets) {
-                            return RecentsActivityCommand.this.createWindowAnimation(appTargets,
-                                    wallpaperTargets);
-                        }
-                    }, mContext, MAIN_EXECUTOR.getHandler(),
-                    mAnimationProvider.getRecentsLaunchDuration());
         }
-
-        protected boolean handleCommand(long elapsedTime) {
-            // TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows
-            //       the menu activity which takes window focus, preventing the right condition from
-            //       being run below
-            RecentsView recents = mActivityInterface.getVisibleRecentsView();
-            if (recents != null) {
-                // Launch the next task
-                recents.showNextTask();
-                return true;
-            } else if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
-                // The user tried to launch back into overview too quickly, either after
-                // launching an app, or before overview has actually shown, just ignore for now
-                return true;
-            }
-            return false;
-        }
-
-        private boolean onActivityReady(Boolean wasVisible) {
-            final T activity = mActivityInterface.getCreatedActivity();
-            return mAnimationProvider.onActivityReady(activity, wasVisible);
-        }
-
-        private AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
-                RemoteAnimationTargetCompat[] wallpaperTargets) {
-            LatencyTrackerCompat.logToggleRecents(
-                    mContext, (int) (SystemClock.uptimeMillis() - mToggleClickedTime));
-
-            mListener.unregister();
-
-            AnimatorSet animatorSet = mAnimationProvider.createWindowAnimation(appTargets,
-                    wallpaperTargets);
-            animatorSet.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    Trace.beginAsyncSection(TRANSITION_NAME, 0);
-                    super.onAnimationStart(animation);
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    onTransitionComplete();
-                    Trace.endAsyncSection(TRANSITION_NAME, 0);
-                }
-            });
-            return animatorSet;
-        }
-
-        protected void onTransitionComplete() { }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 1b2fd41..d3ed791 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -18,9 +18,9 @@
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY;
+import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
+import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
 import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
@@ -30,7 +30,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
-import android.app.ActivityOptions;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.os.Bundle;
@@ -46,7 +45,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.WrappedAnimationRunnerImpl;
 import com.android.launcher3.WrappedLauncherAnimationRunner;
-import com.android.launcher3.allapps.search.SearchAdapterProvider;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -54,7 +52,9 @@
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.ActivityTracker;
+import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer;
@@ -63,7 +63,9 @@
 import com.android.quickstep.fallback.RecentsDragLayer;
 import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.util.RecentsAtomicAnimationFactory;
+import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.views.OverviewActionsView;
+import com.android.quickstep.views.SplitPlaceholderView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -94,7 +96,6 @@
 
     // Strong refs to runners which are cleared when the activity is destroyed
     private WrappedAnimationRunnerImpl mActivityLaunchAnimationRunner;
-    private SearchAdapterProvider mSearchAdapterProvider;
 
     /**
      * Init drag layer and overview panel views.
@@ -106,8 +107,14 @@
         mFallbackRecentsView = findViewById(R.id.overview_panel);
         mActionsView = findViewById(R.id.overview_actions_view);
 
+        SplitPlaceholderView splitPlaceholderView = findViewById(R.id.split_placeholder);
+        splitPlaceholderView.init(
+                new SplitSelectStateController(
+                        SystemUiProxy.INSTANCE.get(this))
+        );
+
         mDragLayer.recreateControllers();
-        mFallbackRecentsView.init(mActionsView);
+        mFallbackRecentsView.init(mActionsView, splitPlaceholderView);
     }
 
     @Override
@@ -172,42 +179,40 @@
     }
 
     @Override
-    public ActivityOptions getActivityLaunchOptions(final View v) {
+    public ActivityOptionsWrapper getActivityLaunchOptions(final View v) {
         if (!(v instanceof TaskView)) {
-            return null;
+            return super.getActivityLaunchOptions(v);
         }
 
         final TaskView taskView = (TaskView) v;
-        mActivityLaunchAnimationRunner = new WrappedAnimationRunnerImpl() {
-            @Override
-            public Handler getHandler() {
-                return mUiHandler;
-            }
+        RunnableList onEndCallback = new RunnableList();
 
-            @Override
-            public void onCreateAnimation(int transit,
+        mActivityLaunchAnimationRunner = (int transit,
                     RemoteAnimationTargetCompat[] appTargets,
                     RemoteAnimationTargetCompat[] wallpaperTargets,
                     RemoteAnimationTargetCompat[] nonAppTargets,
-                    AnimationResult result) {
-                AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets,
-                        wallpaperTargets);
-                anim.addListener(resetStateListener());
-                result.setAnimation(anim, RecentsActivity.this);
-            }
+                    AnimationResult result) -> {
+            AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets,
+                    wallpaperTargets);
+            anim.addListener(resetStateListener());
+            result.setAnimation(anim, RecentsActivity.this, onEndCallback::executeAllAndDestroy);
         };
+
         final LauncherAnimationRunner wrapper = new WrappedLauncherAnimationRunner<>(
-                mActivityLaunchAnimationRunner, true /* startAtFrontOfQueue */);
-        return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
+                mUiHandler, mActivityLaunchAnimationRunner, true /* startAtFrontOfQueue */);
+        RemoteAnimationAdapterCompat adapterCompat = new RemoteAnimationAdapterCompat(
                 wrapper, RECENTS_LAUNCH_DURATION,
                 RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION
-                        - STATUS_BAR_TRANSITION_PRE_DELAY));
+                        - STATUS_BAR_TRANSITION_PRE_DELAY);
+        return new ActivityOptionsWrapper(
+                ActivityOptionsCompat.makeRemoteAnimation(adapterCompat),
+                onEndCallback);
     }
 
     /**
      * Composes the animations for a launch from the recents list if possible.
      */
-    private AnimatorSet composeRecentsLaunchAnimator(TaskView taskView,
+    private AnimatorSet  composeRecentsLaunchAnimator(TaskView taskView,
             RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets) {
         AnimatorSet target = new AnimatorSet();
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 646c5a0..ec585cc 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -149,10 +149,14 @@
      * accordingly. This should be called before `finish`
      * @param taskId for which the leash should be updated
      * @param destinationBounds bounds of the final PiP window
+     * @param windowCrop bounds to crop as part of final transform.
+     * @param float9 An array of 9 floats to be used as matrix transform.
      */
-    public void setFinishTaskBounds(int taskId, Rect destinationBounds) {
+    public void setFinishTaskBounds(int taskId, Rect destinationBounds, Rect windowCrop,
+            float[] float9) {
         UI_HELPER_EXECUTOR.execute(
-                () -> mController.setFinishTaskBounds(taskId, destinationBounds));
+                () -> mController.setFinishTaskBounds(taskId, destinationBounds, windowCrop,
+                        float9));
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index f99b7e6..458f45a 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -17,12 +17,13 @@
 
 import static android.content.Intent.ACTION_USER_UNLOCKED;
 
-import static com.android.launcher3.util.SettingsCache.ONE_HANDED_ENABLED;
-import static com.android.launcher3.util.SettingsCache.ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED;
 import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_ALL;
 import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_FRAME_DELAY;
+import static com.android.launcher3.util.SettingsCache.ONE_HANDED_ENABLED;
+import static com.android.launcher3.util.SettingsCache.ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
@@ -60,11 +61,11 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayHolder;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.SettingsCache;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.SysUINavigationMode.OneHandedModeChangeListener;
 import com.android.quickstep.util.NavBarPosition;
@@ -304,6 +305,13 @@
     }
 
     /**
+     * @return whether the current nav mode is 2-button-based.
+     */
+    public boolean isTwoButtonNavMode() {
+        return mMode == TWO_BUTTONS;
+    }
+
+    /**
      * @return whether the current nav mode is button-based.
      */
     public boolean isButtonNavMode() {
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 5668817..a70cc4c 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -45,7 +45,8 @@
 /**
  * Holds the reference to SystemUI.
  */
-public class SystemUiProxy implements ISystemUiProxy {
+public class SystemUiProxy implements ISystemUiProxy,
+        SysUINavigationMode.NavigationModeChangeListener {
     private static final String TAG = SystemUiProxy.class.getSimpleName();
 
     public static final MainThreadInitializedObject<SystemUiProxy> INSTANCE =
@@ -59,14 +60,21 @@
     // Used to dedupe calls to SystemUI
     private int mLastShelfHeight;
     private boolean mLastShelfVisible;
-    private float mLastBackButtonAlpha;
-    private boolean mLastBackButtonAnimate;
+    private float mLastNavButtonAlpha;
+    private boolean mLastNavButtonAnimate;
+    private boolean mHasNavButtonAlphaBeenSet = false;
 
     // TODO(141886704): Find a way to remove this
     private int mLastSystemUiStateFlags;
 
     public SystemUiProxy(Context context) {
-        // Do nothing
+        SysUINavigationMode.INSTANCE.get(context).addModeChangeListener(this);
+    }
+
+    @Override
+    public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
+        // Whenever the nav mode changes, force reset the nav button alpha
+        setNavBarButtonAlpha(1f, false);
     }
 
     @Override
@@ -149,28 +157,19 @@
         return null;
     }
 
-    @Override
-    public void setBackButtonAlpha(float alpha, boolean animate) {
-        boolean changed = Float.compare(alpha, mLastBackButtonAlpha) != 0
-                || animate != mLastBackButtonAnimate;
-        if (mSystemUiProxy != null && changed) {
-            mLastBackButtonAlpha = alpha;
-            mLastBackButtonAnimate = animate;
-            try {
-                mSystemUiProxy.setBackButtonAlpha(alpha, animate);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setBackButtonAlpha", e);
-            }
-        }
-    }
-
-    public float getLastBackButtonAlpha() {
-        return mLastBackButtonAlpha;
+    public float getLastNavButtonAlpha() {
+        return mLastNavButtonAlpha;
     }
 
     @Override
     public void setNavBarButtonAlpha(float alpha, boolean animate) {
-        if (mSystemUiProxy != null) {
+        boolean changed = Float.compare(alpha, mLastNavButtonAlpha) != 0
+                || animate != mLastNavButtonAnimate
+                || !mHasNavButtonAlphaBeenSet;
+        if (mSystemUiProxy != null && changed) {
+            mLastNavButtonAlpha = alpha;
+            mLastNavButtonAnimate = animate;
+            mHasNavButtonAlphaBeenSet = true;
             try {
                 mSystemUiProxy.setNavBarButtonAlpha(alpha, animate);
             } catch (RemoteException e) {
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 8636130..cd13200 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -36,11 +36,16 @@
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.quickstep.TaskShortcutFactory.SplitSelectSystemShortcut;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
@@ -57,11 +62,18 @@
  */
 public class TaskOverlayFactory implements ResourceBasedOverride {
 
-    public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView) {
+    public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView,
+            DeviceProfile deviceProfile) {
         final ArrayList<SystemShortcut> shortcuts = new ArrayList<>();
         final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
         for (TaskShortcutFactory menuOption : MENU_OPTIONS) {
             SystemShortcut shortcut = menuOption.getShortcut(activity, taskView);
+            if (menuOption == TaskShortcutFactory.SPLIT_SCREEN &&
+                    FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
+                addSplitOptions(shortcuts, activity, taskView, deviceProfile);
+                continue;
+            }
+
             if (shortcut != null) {
                 shortcuts.add(shortcut);
             }
@@ -91,6 +103,18 @@
         return shortcuts;
     }
 
+
+    public static void addSplitOptions(List<SystemShortcut> outShortcuts,
+            BaseDraggingActivity activity, TaskView taskView, DeviceProfile deviceProfile) {
+        PagedOrientationHandler orientationHandler =
+                taskView.getRecentsView().getPagedOrientationHandler();
+        List<SplitPositionOption> positions =
+                orientationHandler.getSplitPositionOptions(deviceProfile);
+        for (SplitPositionOption option : positions) {
+            outShortcuts.add(new SplitSelectSystemShortcut(activity, taskView, option));
+        }
+    }
+
     public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) {
         return new TaskOverlay(thumbnailView);
     }
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 65bb0f3..c06e9a9 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -34,12 +34,13 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
 import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.popup.SystemShortcut.AppInfo;
-import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.InstantAppResolver;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskThumbnailView;
 import com.android.quickstep.views.TaskView;
@@ -54,13 +55,11 @@
 
 import java.util.Collections;
 import java.util.List;
-import java.util.function.Consumer;
 
 /**
  * Represents a system shortcut that can be shown for a recent task.
  */
 public interface TaskShortcutFactory {
-
     SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView view);
 
     TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, view.getItemInfo());
@@ -95,6 +94,23 @@
         }
     }
 
+    class SplitSelectSystemShortcut extends SystemShortcut {
+        private final TaskView mTaskView;
+        private SplitPositionOption mSplitPositionOption;
+        public SplitSelectSystemShortcut(BaseDraggingActivity target, TaskView taskView,
+                SplitPositionOption option) {
+            super(option.mIconResId, option.mTextResId, target, taskView.getItemInfo());
+            mTaskView = taskView;
+            mSplitPositionOption = option;
+            setEnabled(taskView.getRecentsView().getTaskViewCount() > 1);
+        }
+
+        @Override
+        public void onClick(View view) {
+            mTaskView.initiateSplitSelect(mSplitPositionOption);
+        }
+    }
+
     class MultiWindowSystemShortcut extends SystemShortcut {
 
         private Handler mHandler;
@@ -213,6 +229,16 @@
         }
 
         @Override
+        public SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView taskView) {
+            SystemShortcut shortcut = super.getShortcut(activity, taskView);
+            if (FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
+                // Disable if there's only one recent app for split screen
+                shortcut.setEnabled(taskView.getRecentsView().getTaskViewCount() > 1);
+            }
+            return shortcut;
+        }
+
+        @Override
         protected ActivityOptions makeLaunchOptions(Activity activity) {
             final ActivityCompat act = new ActivityCompat(activity);
             final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition(
@@ -281,15 +307,9 @@
 
         @Override
         public void onClick(View view) {
-            Consumer<Boolean> resultCallback = success -> {
-                if (success) {
-                    SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning(
-                            mTaskView.getTask().key.id);
-                } else {
-                    mTaskView.notifyTaskLaunchFailed(TAG);
-                }
-            };
-            mTaskView.launchTask(true, resultCallback, Executors.MAIN_EXECUTOR.getHandler());
+            if (mTaskView.launchTaskAnimated() != null) {
+                SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning(mTaskView.getTask().key.id);
+            }
             dismissTaskMenuView(mTarget);
             mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
                     .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 17822e6..7a428ce 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -18,11 +18,12 @@
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
 import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -37,6 +38,7 @@
 import android.content.Context;
 import android.graphics.Matrix;
 import android.graphics.Matrix.ScaleToFit;
+import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
 import android.view.View;
@@ -180,6 +182,7 @@
         boolean parallaxCenterAndAdjacentTask =
                 taskIndex != recentsView.getCurrentPage() && !(dp.isTablet
                         && FeatureFlags.ENABLE_OVERVIEW_GRID.get());
+        float gridProgress = recentsView.getGridProgress();
         float gridTranslationSecondary = recentsView.getGridTranslationSecondary(taskIndex);
         int startScroll = recentsView.getScrollOffset(taskIndex);
 
@@ -197,7 +200,7 @@
             tsv.setPreview(targets.apps[targets.apps.length - 1]);
             tsv.fullScreenProgress.value = 0;
             tsv.recentsViewScale.value = 1;
-            tsv.gridProgress.value = 1;
+            tsv.gridProgress.value = gridProgress;
             tsv.gridTranslationSecondary.value = gridTranslationSecondary;
             tsv.setScroll(startScroll);
 
@@ -294,6 +297,93 @@
         }
     }
 
+    /**
+     * TODO: This doesn't animate at present. Feel free to blow out everyhing in this method
+     * if needed
+     *
+     * We could manually try to animate the just the bounds for the leashes we get back, but we try
+     * to do it through TaskViewSimulator(TVS) since that handles a lot of the recents UI stuff for
+     * us.
+     *
+     * First you have to call TVS#setPreview() to indicate which leash it will operate one
+     * Then operations happen in TVS#apply() on each frame callback.
+     *
+     * TVS uses DeviceProfile to try to figure out things like task height and such based on if the
+     * device is in multiWindowMode or not. It's unclear given the two calls to startTask() when the
+     * device is considered in multiWindowMode and things like insets and stuff change
+     * and calculations have to be adjusted in the animations for that
+     */
+    public static void composeRecentsSplitLaunchAnimator(@NonNull AnimatorSet anim,
+            @NonNull TaskView v, @NonNull RemoteAnimationTargetCompat[] appTargets,
+            @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing,
+            @NonNull StateManager stateManager, @NonNull DepthController depthController,
+            int targetStage) {
+        PendingAnimation out = new PendingAnimation(RECENTS_LAUNCH_DURATION);
+        boolean isRunningTask = v.isRunningTask();
+        TransformParams params = null;
+        TaskViewSimulator tvs = null;
+        RecentsView recentsView = v.getRecentsView();
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
+            params = recentsView.getLiveTileParams();
+            tvs = recentsView.getLiveTileTaskViewSimulator();
+        }
+
+        boolean inLiveTileMode =
+                ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1;
+        final RemoteAnimationTargets targets =
+                new RemoteAnimationTargets(appTargets, wallpaperTargets,
+                        inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
+
+        if (params == null) {
+            SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
+            targets.addReleaseCheck(applier);
+
+            params = new TransformParams()
+                    .setSyncTransactionApplier(applier)
+                    .setTargetSet(targets);
+        }
+
+        Rect crop = new Rect();
+        Context context = v.getContext();
+        DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
+        if (tvs == null && targets.apps.length > 0) {
+            tvs = new TaskViewSimulator(recentsView.getContext(), recentsView.getSizeStrategy());
+            tvs.setDp(dp);
+
+            // RecentsView never updates the display rotation until swipe-up so the value may
+            // be stale. Use the display value instead.
+            int displayRotation = DisplayController.getDefaultDisplay(recentsView.getContext())
+                    .getInfo().rotation;
+            tvs.getOrientationState().update(displayRotation, displayRotation);
+
+            tvs.setPreview(targets.apps[targets.apps.length - 1]);
+            tvs.fullScreenProgress.value = 0;
+            tvs.recentsViewScale.value = 1;
+//            tvs.setScroll(startScroll);
+
+            // Fade in the task during the initial 20% of the animation
+            out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1,
+                    clampToProgress(LINEAR, 0, 0.2f));
+        }
+
+        TaskViewSimulator topMostSimulator = null;
+
+        if (tvs != null) {
+            out.setFloat(tvs.fullScreenProgress,
+                    AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
+            out.setFloat(tvs.recentsViewScale,
+                    AnimatedFloat.VALUE, tvs.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
+            out.setInt(tvs, TaskViewSimulator.SCROLL, 0, TOUCH_RESPONSE_INTERPOLATOR);
+
+            TaskViewSimulator finalTsv = tvs;
+            TransformParams finalParams = params;
+            out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
+            topMostSimulator = tvs;
+        }
+
+        anim.play(out.buildAnim());
+    }
+
     public static void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
             @NonNull RemoteAnimationTargetCompat[] appTargets,
             @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing,
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index fc805d0..8e6f663 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -24,6 +24,7 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
 import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
 
@@ -157,13 +158,23 @@
         @BinderThread
         public void onOverviewToggle() {
             TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
-            mOverviewCommandHelper.onOverviewToggle();
+            // If currently screen pinning, do not enter overview
+            if (mDeviceState.isScreenPinningActive()) {
+                return;
+            }
+            TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+            mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
         }
 
         @BinderThread
         @Override
         public void onOverviewShown(boolean triggeredFromAltTab) {
-            mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
+            if (triggeredFromAltTab) {
+                TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+                mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW_NEXT_FOCUS);
+            } else {
+                mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW);
+            }
         }
 
         @BinderThread
@@ -171,7 +182,7 @@
         public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
             if (triggeredFromAltTab && !triggeredFromHomeKey) {
                 // onOverviewShownFromAltTab hides the overview and ends at the target app
-                mOverviewCommandHelper.onOverviewHidden();
+                mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HIDE);
             }
         }
 
@@ -326,8 +337,8 @@
     public void onUserUnlocked() {
         mTaskAnimationManager = new TaskAnimationManager(this);
         mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
-        mOverviewCommandHelper = new OverviewCommandHelper(this, mDeviceState,
-                mOverviewComponentObserver);
+        mOverviewCommandHelper = new OverviewCommandHelper(this,
+                mOverviewComponentObserver, mTaskAnimationManager);
         mResetGestureInputConsumer = new ResetGestureInputConsumer(mTaskAnimationManager);
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
         mInputConsumer.registerInputConsumer();
@@ -555,7 +566,7 @@
         ProtoTracer.INSTANCE.get(this).scheduleFrameUpdate();
     }
 
-    private GestureState createGestureState(GestureState previousGestureState) {
+    public GestureState createGestureState(GestureState previousGestureState) {
         GestureState gestureState = new GestureState(mOverviewComponentObserver,
                 ActiveGestureLog.INSTANCE.generateAndSetLogId());
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
@@ -704,16 +715,15 @@
         }
     }
 
+    public AbsSwipeUpHandler.Factory getSwipeUpHandlerFactory() {
+        return !mOverviewComponentObserver.isHomeAndOverviewSame()
+                ? mFallbackSwipeHandlerFactory : mLauncherSwipeHandlerFactory;
+    }
+
     private InputConsumer createOtherActivityInputConsumer(GestureState gestureState,
             MotionEvent event) {
 
-        final AbsSwipeUpHandler.Factory factory;
-        if (!mOverviewComponentObserver.isHomeAndOverviewSame()) {
-            factory = mFallbackSwipeHandlerFactory;
-        } else {
-            factory = mLauncherSwipeHandlerFactory;
-        }
-
+        final AbsSwipeUpHandler.Factory factory = getSwipeUpHandlerFactory();
         final boolean shouldDefer = !mOverviewComponentObserver.isHomeAndOverviewSame()
                 || gestureState.getActivityInterface().deferStartingActivity(mDeviceState, event);
         final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
@@ -886,15 +896,17 @@
     }
 
     private AbsSwipeUpHandler createLauncherSwipeHandler(
-            GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
+            GestureState gestureState, long touchTimeMs) {
         return new LauncherSwipeHandlerV2(this, mDeviceState, mTaskAnimationManager,
-                gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
+                gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
+                mInputConsumer);
     }
 
     private AbsSwipeUpHandler createFallbackSwipeHandler(
-            GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
+            GestureState gestureState, long touchTimeMs) {
         return new FallbackSwipeHandler(this, mDeviceState, mTaskAnimationManager,
-                gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
+                gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
+                mInputConsumer);
     }
 
     protected boolean shouldNotifyBackGesture() {
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 24a7610..54f6ce6 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -25,6 +25,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
@@ -77,11 +78,12 @@
 
     private void setProperties(RecentsState state, StateAnimationConfig config,
             PropertySetter setter) {
-        float buttonAlpha = state.hasButtons() ? 1 : 0;
+        float clearAllButtonAlpha = state.hasClearAllButton() ? 1 : 0;
         setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
-                buttonAlpha, LINEAR);
+                clearAllButtonAlpha, LINEAR);
+        float overviewButtonAlpha =  state.hasOverviewActions(mActivity) ? 1 : 0;
         setter.setFloat(mActivity.getActionsView().getVisibilityAlpha(),
-                MultiValueAlpha.VALUE, buttonAlpha, LINEAR);
+                MultiValueAlpha.VALUE, overviewButtonAlpha, LINEAR);
 
         float[] scaleAndOffset = state.getOverviewScaleAndOffset(mActivity);
         setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
@@ -94,5 +96,7 @@
         setter.setFloat(mRecentsView, TASK_MODALNESS, state.getOverviewModalness(),
                 config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
         setter.setFloat(mRecentsView, FULLSCREEN_PROGRESS, state.isFullScreen() ? 1 : 0, LINEAR);
+        setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
+                state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()) ? 1f : 0f, LINEAR);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 13f6137..e075045 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -34,6 +34,7 @@
 import com.android.quickstep.RecentsActivity;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.SplitPlaceholderView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -56,8 +57,8 @@
     }
 
     @Override
-    public void init(OverviewActionsView actionsView) {
-        super.init(actionsView);
+    public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
+        super.init(actionsView, splitPlaceholderView);
         setOverviewStateEnabled(true);
         setOverlayEnabled(true);
     }
@@ -65,6 +66,7 @@
     @Override
     public void startHome() {
         mActivity.startHome();
+        mActivity.getStateManager().goToState(RecentsState.HOME);
     }
 
     /**
@@ -154,6 +156,11 @@
     }
 
     @Override
+    protected boolean isHomeTask(TaskView taskView) {
+        return mHomeTaskInfo != null && taskView.hasTaskId(mHomeTaskInfo.taskId);
+    }
+
+    @Override
     public void setModalStateEnabled(boolean isModalState) {
         super.setModalStateEnabled(isModalState);
         if (isModalState) {
@@ -168,6 +175,8 @@
     @Override
     public void onStateTransitionStart(RecentsState toState) {
         setOverviewStateEnabled(true);
+        setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
+        setOverviewFullscreenEnabled(toState.isFullScreen());
         setFreezeViewVisibility(true);
     }
 
@@ -182,7 +191,7 @@
         super.setOverviewStateEnabled(enabled);
         if (enabled) {
             RecentsState state = mActivity.getStateManager().getState();
-            setDisallowScrollToClearAll(!state.hasButtons());
+            setDisallowScrollToClearAll(!state.hasClearAllButton());
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index f15a9de..a9856d2 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -20,6 +20,8 @@
 
 import android.content.Context;
 
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.quickstep.RecentsActivity;
 
@@ -29,14 +31,19 @@
 public class RecentsState implements BaseState<RecentsState> {
 
     private static final int FLAG_MODAL = BaseState.getFlag(0);
-    private static final int FLAG_HAS_BUTTONS = BaseState.getFlag(1);
+    private static final int FLAG_CLEAR_ALL_BUTTON = BaseState.getFlag(1);
     private static final int FLAG_FULL_SCREEN = BaseState.getFlag(2);
+    private static final int FLAG_OVERVIEW_ACTIONS = BaseState.getFlag(3);
+    private static final int FLAG_SHOW_AS_GRID = BaseState.getFlag(4);
 
-    public static final RecentsState DEFAULT = new RecentsState(0, FLAG_HAS_BUTTONS);
+    public static final RecentsState DEFAULT = new RecentsState(0,
+            FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID);
     public static final RecentsState MODAL_TASK = new ModalState(1,
-            FLAG_DISABLE_RESTORE | FLAG_HAS_BUTTONS | FLAG_MODAL);
+            FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
+                    | FLAG_SHOW_AS_GRID);
     public static final RecentsState BACKGROUND_APP = new BackgroundAppState(2,
             FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN);
+    public static final RecentsState HOME = new RecentsState(3, 0);
 
     public final int ordinal;
     private final int mFlags;
@@ -82,14 +89,35 @@
         return hasFlag(FLAG_FULL_SCREEN);
     }
 
-    public boolean hasButtons() {
-        return hasFlag(FLAG_HAS_BUTTONS);
+    /**
+     * For this state, whether clear all button should be shown.
+     */
+    public boolean hasClearAllButton() {
+        return hasFlag(FLAG_CLEAR_ALL_BUTTON);
+    }
+
+    /**
+     * For this state, whether overview actions should be shown.
+     */
+    public boolean hasOverviewActions(RecentsActivity activity) {
+        return hasFlag(FLAG_OVERVIEW_ACTIONS) && !showAsGrid(activity.getDeviceProfile());
     }
 
     public float[] getOverviewScaleAndOffset(RecentsActivity activity) {
         return new float[] { NO_SCALE, NO_OFFSET };
     }
 
+    /**
+     * For this state, whether tasks should layout as a grid rather than a list.
+     */
+    public boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
+        return hasFlag(FLAG_SHOW_AS_GRID) && showAsGrid(deviceProfile);
+    }
+
+    private boolean showAsGrid(DeviceProfile deviceProfile) {
+        return deviceProfile.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
+    }
+
     private static class ModalState extends RecentsState {
 
         public ModalState(int id, int flags) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 5baf518..9878d45 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -31,7 +31,6 @@
 import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
 import static com.android.quickstep.GestureState.STATE_OVERSCROLL_WINDOW_CREATED;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -65,7 +64,6 @@
 import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.RotationTouchHelper;
 import com.android.quickstep.TaskAnimationManager;
-import com.android.quickstep.TaskUtils;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.CachedEventDispatcher;
@@ -381,9 +379,6 @@
         // Once we detect the gesture, we can enable batching to reduce further updates
         mInputEventReceiver.setBatchingEnabled(true);
 
-        mActivityInterface.closeOverlay();
-        TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-
         // Notify the handler that the gesture has actually started
         mInteractionHandler.onGestureStarted(isLikelyToStartNewTask);
     }
@@ -391,8 +386,7 @@
     private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
         ActiveGestureLog.INSTANCE.addLog("startRecentsAnimation");
 
-        mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs,
-                mTaskAnimationManager.isRecentsAnimationRunning());
+        mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs);
         mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished);
         mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler.getMotionPauseListener());
         Intent intent = new Intent(mInteractionHandler.getLaunchIntent());
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index cee3363..fa9e0ec 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -24,6 +24,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Utilities;
+import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
@@ -38,7 +39,7 @@
 /**
  * Input consumer for handling touch on the recents/Launcher activity.
  */
-public class OverviewInputConsumer<T extends StatefulActivity<?>>
+public class OverviewInputConsumer<S extends BaseState<S>, T extends StatefulActivity<S>>
         implements InputConsumer {
 
     private final T mActivity;
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
index d022085..0f2d778 100644
--- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -99,7 +99,14 @@
             .putExtra(Intent.EXTRA_STREAM, uri)
             .putExtra(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId())
             .setClipData(clipdata);
-        context.startActivity(intent);
+
+        if (context.getUserId() != appTarget.getUser().getIdentifier()) {
+            intent.prepareToLeaveUser(context.getUserId());
+            intent.fixUris(context.getUserId());
+            context.startActivityAsUser(intent, appTarget.getUser());
+        } else {
+            context.startActivity(intent);
+        }
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 215f05a..188efad 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -23,9 +23,9 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
-import static com.android.launcher3.util.SettingsCache.ROTATION_SETTING_URI;
 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.SettingsCache.ROTATION_SETTING_URI;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -47,11 +47,12 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.views.TaskView;
 
 import java.lang.annotation.Retention;
 import java.util.function.IntConsumer;
@@ -367,8 +368,12 @@
      */
     public float getFullScreenScaleAndPivot(Rect taskView, DeviceProfile dp, PointF outPivot) {
         Rect insets = dp.getInsets();
-        float fullWidth = dp.widthPx - insets.left - insets.right;
-        float fullHeight = dp.heightPx - insets.top - insets.bottom;
+        float fullWidth = dp.widthPx;
+        float fullHeight = dp.heightPx;
+        if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
+            fullWidth -= insets.left + insets.right;
+            fullHeight -= insets.top + insets.bottom;
+        }
 
         if (dp.isMultiWindowMode) {
             WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(mContext);
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index 3adb459..5c6da16 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -21,7 +21,6 @@
 import android.os.Handler;
 
 import com.android.launcher3.LauncherAnimationRunner;
-import com.android.launcher3.LauncherAnimationRunner.AnimationResult;
 import com.android.launcher3.WrappedAnimationRunnerImpl;
 import com.android.launcher3.WrappedLauncherAnimationRunner;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
@@ -36,23 +35,10 @@
             RemoteAnimationTargetCompat[] wallpaperTargets);
 
     ActivityOptions toActivityOptions(Handler handler, long duration, Context context) {
-        mAnimationRunner = new WrappedAnimationRunnerImpl() {
-            @Override
-            public Handler getHandler() {
-                return handler;
-            }
-
-            @Override
-            public void onCreateAnimation(int transit,
-                    RemoteAnimationTargetCompat[] appTargets,
-                    RemoteAnimationTargetCompat[] wallpaperTargets,
-                    RemoteAnimationTargetCompat[] nonApps,
-                    AnimationResult result) {
+        mAnimationRunner = (transit, appTargets, wallpaperTargets, nonApps, result) ->
                 result.setAnimation(createWindowAnimation(appTargets, wallpaperTargets), context);
-            }
-        };
         final LauncherAnimationRunner wrapper = new WrappedLauncherAnimationRunner(
-                mAnimationRunner, false /* startAtFrontOfQueue */);
+                handler, mAnimationRunner, false /* startAtFrontOfQueue */);
         return ActivityOptionsCompat.makeRemoteAnimation(
                 new RemoteAnimationAdapterCompat(wrapper, duration, 0));
     }
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
new file mode 100644
index 0000000..d9154ed
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2021 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.AnimatorSet;
+import android.app.ActivityOptions;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Pair;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.LauncherAnimationRunner;
+import com.android.launcher3.WrappedAnimationRunnerImpl;
+import com.android.launcher3.WrappedLauncherAnimationRunner;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskViewUtils;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
+import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * Represent data needed for the transient state when user has selected one app for split screen
+ * and is in the process of either a) selecting a second app or b) exiting intention to invoke split
+ */
+public class SplitSelectStateController {
+
+    private final SystemUiProxy mSystemUiProxy;
+    private TaskView mInitialTaskView;
+    private SplitPositionOption mInitialPosition;
+
+    public SplitSelectStateController(SystemUiProxy systemUiProxy) {
+        mSystemUiProxy = systemUiProxy;
+    }
+
+    /**
+     * To be called after first task selected
+     */
+    public void setInitialTaskSelect(TaskView taskView, SplitPositionOption positionOption) {
+        mInitialTaskView = taskView;
+        mInitialPosition = positionOption;
+    }
+
+    /**
+     * To be called after second task selected
+     */
+    public void setSecondTaskId(TaskView taskView) {
+        // Assume initial mInitialTaskId is for top/left part of screen
+        WrappedAnimationRunnerImpl initialSplitRunnerWrapped =  new SplitLaunchAnimationRunner(
+                mInitialTaskView, 0);
+        WrappedAnimationRunnerImpl secondarySplitRunnerWrapped =  new SplitLaunchAnimationRunner(
+                taskView, 1);
+        RemoteAnimationRunnerCompat initialSplitRunner = new WrappedLauncherAnimationRunner(
+                new Handler(Looper.getMainLooper()), initialSplitRunnerWrapped,
+                true /* startAtFrontOfQueue */);
+        RemoteAnimationRunnerCompat secondarySplitRunner = new WrappedLauncherAnimationRunner(
+                new Handler(Looper.getMainLooper()), secondarySplitRunnerWrapped,
+                true /* startAtFrontOfQueue */);
+        ActivityOptions initialOptions = ActivityOptionsCompat.makeRemoteAnimation(
+                new RemoteAnimationAdapterCompat(initialSplitRunner, 300, 150));
+        ActivityOptions secondaryOptions = ActivityOptionsCompat.makeRemoteAnimation(
+                new RemoteAnimationAdapterCompat(secondarySplitRunner, 300, 150));
+        mSystemUiProxy.startTask(mInitialTaskView.getTask().key.id, mInitialPosition.mStageType,
+                mInitialPosition.mStagePosition,
+                /*null*/ initialOptions.toBundle());
+        Pair<Integer, Integer> compliment = getComplimentaryStageAndPosition(mInitialPosition);
+        mSystemUiProxy.startTask(taskView.getTask().key.id, compliment.first,
+                compliment.second,
+                /*null*/ secondaryOptions.toBundle());
+        // After successful launch, call resetState
+        resetState();
+    }
+
+    @Nullable
+    public SplitPositionOption getActiveSplitPositionOption() {
+        return mInitialPosition;
+    }
+
+    /**
+     * @return the opposite stage and position from the {@param position} provided as first and
+     *         second object, respectively
+     * Ex. If position is has stage = Main and position = Top/Left, this will return
+     * Pair(stage=Side, position=Bottom/Left)
+     */
+    private Pair<Integer, Integer> getComplimentaryStageAndPosition(SplitPositionOption position) {
+        // Right now this is as simple as flipping between 0 and 1
+        int complimentStageType = position.mStageType ^ 1;
+        int complimentStagePosition = position.mStagePosition ^ 1;
+        return new Pair<>(complimentStageType, complimentStagePosition);
+    }
+
+    /**
+     * Remote animation runner for animation to launch an app.
+     */
+    private class SplitLaunchAnimationRunner implements WrappedAnimationRunnerImpl {
+
+        private final TaskView mV;
+        private final int mTargetState;
+
+        SplitLaunchAnimationRunner(TaskView v, int targetState) {
+            mV = v;
+            mTargetState = targetState;
+        }
+
+        @Override
+        public void onCreateAnimation(int transit,
+                RemoteAnimationTargetCompat[] appTargets,
+                RemoteAnimationTargetCompat[] wallpaperTargets,
+                RemoteAnimationTargetCompat[] nonAppTargets,
+                LauncherAnimationRunner.AnimationResult result) {
+            AnimatorSet anim = new AnimatorSet();
+            BaseQuickstepLauncher activity = BaseActivity.fromContext(mV.getContext());
+            TaskViewUtils.composeRecentsSplitLaunchAnimator(anim, mV,
+                    appTargets, wallpaperTargets, true, activity.getStateManager(),
+                    activity.getDepthController(), mTargetState);
+            result.setAnimation(anim, activity);
+        }
+    }
+
+
+    /**
+     * To be called if split select was cancelled
+     */
+    public void resetState() {
+        mInitialTaskView = null;
+        mInitialPosition = null;
+    }
+
+    public boolean isSplitSelectActive() {
+        return mInitialTaskView != null;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 932ff27..ae644cd 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
@@ -84,7 +85,7 @@
         Workspace workspace = launcher.getWorkspace();
         CellLayout cellLayout = (CellLayout) workspace.getChildAt(workspace.getCurrentPage());
         ShortcutAndWidgetContainer currentPage = cellLayout.getShortcutsAndWidgets();
-        ViewGroup hotseat = launcher.getHotseat();
+        Hotseat hotseat = launcher.getHotseat();
 
         boolean workspaceClipChildren = workspace.getClipChildren();
         boolean workspaceClipToPadding = workspace.getClipToPadding();
@@ -124,11 +125,7 @@
                 addStaggeredAnimationForView(child, grid.inv.numRows + 1, totalRows);
             }
 
-            if (launcher.getAppsView().getSearchUiManager()
-                    .isQsbVisible(NORMAL.getVisibleElements(launcher))) {
-                addStaggeredAnimationForView(launcher.getAppsView().getSearchView(),
-                        grid.inv.numRows + 2, totalRows);
-            }
+            addStaggeredAnimationForView(hotseat.getQsb(), grid.inv.numRows + 2, totalRows);
         }
 
         if (animateOverviewScrim) {
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 0ce5072..0a1a6e8 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -134,8 +134,9 @@
 
             @Override
             public void onAnimationEnd(Animator animation) {
-                if (!mHasAnimationEnded) super.onAnimationEnd(animation);
-                SwipePipToHomeAnimator.this.onAnimationEnd();
+                if (mHasAnimationEnded) return;
+                super.onAnimationEnd(animation);
+                mHasAnimationEnded = true;
             }
         });
         addUpdateListener(this);
@@ -223,14 +224,34 @@
         return mDestinationBounds;
     }
 
-    private void onAnimationEnd() {
-        if (mHasAnimationEnded) return;
+    /**
+     * @return {@link Rect} of the final window crop in destination orientation.
+     */
+    public Rect getFinishWindowCrop() {
+        final Rect windowCrop = new Rect(mAppBounds);
+        if (mSourceHintRectInsets != null) {
+            windowCrop.inset(mSourceHintRectInsets);
+        }
+        return windowCrop;
+    }
 
-        final SurfaceControl.Transaction tx =
-                PipSurfaceTransactionHelper.newSurfaceControlTransaction();
-        mSurfaceTransactionHelper.reset(tx, mLeash, mDestinationBoundsTransformed, mFromRotation);
-        tx.apply();
-        mHasAnimationEnded = true;
+    /**
+     * @return Array of 9 floats represents the final transform in destination orientation.
+     */
+    public float[] getFinishTransform() {
+        final Matrix transform = new Matrix();
+        final float[] float9 = new float[9];
+        if (mSourceHintRectInsets == null) {
+            transform.setRectToRect(new RectF(mAppBounds), new RectF(mDestinationBounds),
+                    Matrix.ScaleToFit.FILL);
+        } else {
+            final float scale = mAppBounds.width() <= mAppBounds.height()
+                    ? (float) mDestinationBounds.width() / mAppBounds.width()
+                    : (float) mDestinationBounds.height() / mAppBounds.height();
+            transform.setScale(scale, scale);
+        }
+        transform.getValues(float9);
+        return float9;
     }
 
     private RotatedPosition getRotatedPosition(float fraction) {
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 9537247..8b5d498 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -79,6 +79,7 @@
     private final boolean mIsRecentsRtl;
 
     private final Rect mTaskRect = new Rect();
+    private final Rect mGridRect = new Rect();
     private boolean mDrawsBelowRecents;
     private final PointF mPivot = new PointF();
     private DeviceProfile mDp;
@@ -103,6 +104,7 @@
     public final AnimatedFloat recentsViewScale = new AnimatedFloat();
     public final AnimatedFloat fullScreenProgress = new AnimatedFloat();
     public final AnimatedFloat recentsViewSecondaryTranslation = new AnimatedFloat();
+    public final AnimatedFloat recentsViewPrimaryTranslation = new AnimatedFloat();
     public final AnimatedFloat gridProgress = new AnimatedFloat();
     private final ScrollState mScrollState = new ScrollState();
 
@@ -124,7 +126,7 @@
         Resources resources = context.getResources();
         mIsRecentsRtl = mOrientationState.getOrientationHandler().getRecentsRtlSetting(resources);
         mTaskThumbnailPadding = (int) resources.getDimension(R.dimen.task_thumbnail_top_margin);
-        mRowSpacing = (int) resources.getDimension(R.dimen.recents_row_spacing);
+        mRowSpacing = (int) resources.getDimension(R.dimen.overview_grid_row_spacing);
     }
 
     /**
@@ -266,12 +268,15 @@
             mOrientationStateId = mOrientationState.getStateId();
 
             getFullScreenScale();
+            mSizeStrategy.calculateGridSize(mContext, mDp, mGridRect);
             mThumbnailData.rotation = mOrientationState.getDisplayRotation();
 
+            // mIsRecentsRtl is the inverse of TaskView RTL.
+            boolean isRtlEnabled = !mIsRecentsRtl;
             mPositionHelper.updateThumbnailMatrix(
                     mThumbnailPosition, mThumbnailData,
                     mTaskRect.width(), mTaskRect.height(),
-                    mDp, mOrientationState.getRecentsActivityRotation());
+                    mDp, mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
             mPositionHelper.getMatrix().invert(mInversePositionMatrix);
 
             PagedOrientationHandler poh = mOrientationState.getOrientationHandler();
@@ -304,24 +309,34 @@
         mMatrix.postTranslate(insets.left, insets.top);
         mMatrix.postScale(scale, scale);
 
+        // Apply TaskView matrix: gridProgress related properties
         float interpolatedGridProgress = ACCEL_DEACCEL.getInterpolation(gridProgress.value);
-
-        // Apply TaskView matrix: gridProgress
         final int boxLength = (int) Math.max(taskWidth, taskHeight);
-        float availableHeight =
-                mTaskThumbnailPadding + taskHeight + mSizeStrategy.getOverviewActionsHeight(
-                        mContext);
+        float availableHeight = mGridRect.height();
         float rowHeight = (availableHeight - mRowSpacing) / 2;
         float gridScale = rowHeight / (boxLength + mTaskThumbnailPadding);
         scale = Utilities.mapRange(interpolatedGridProgress, 1f, gridScale);
         mMatrix.postScale(scale, scale, mIsRecentsRtl ? 0 : taskWidth, 0);
-        float taskWidthDiff = taskWidth * (1 - gridScale);
-        float taskWidthOffset = mIsRecentsRtl ? taskWidthDiff : -taskWidthDiff;
-        mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
-                Utilities.mapRange(interpolatedGridProgress, 0, taskWidthOffset));
         mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
                 Utilities.mapRange(interpolatedGridProgress, 0, gridTranslationSecondary.value));
 
+        // Apply TaskView matrix: task rect and grid rect difference
+        float scaledWidth = taskWidth * gridScale;
+        float taskGridHorizontalDiff;
+        if (mIsRecentsRtl) {
+            float taskRight = mTaskRect.left + scaledWidth;
+            taskGridHorizontalDiff = mGridRect.right - taskRight;
+        } else {
+            float taskLeft = mTaskRect.right - scaledWidth;
+            taskGridHorizontalDiff = mGridRect.left - taskLeft;
+        }
+        float taskGridVerticalDiff =
+                mGridRect.top + mTaskThumbnailPadding * gridScale - mTaskRect.top;
+        mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
+                Utilities.mapRange(interpolatedGridProgress, 0, taskGridHorizontalDiff));
+        mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
+                Utilities.mapRange(interpolatedGridProgress, 0, taskGridVerticalDiff));
+
         // Apply TaskView matrix: translate, scroll
         mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
         mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
@@ -335,6 +350,8 @@
         mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);
         mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
                 recentsViewSecondaryTranslation.value);
+        mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
+                recentsViewPrimaryTranslation.value);
         applyWindowToHomeRotation(mMatrix);
 
         // Crop rect is the inverse of thumbnail matrix
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index c62f3e2..e042b35 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -15,18 +15,13 @@
  */
 package com.android.quickstep.views;
 
-import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
 import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.os.Build;
@@ -41,7 +36,6 @@
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.quickstep.LauncherActivityInterface;
-import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.util.OverviewToHomeAnim;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.RecentsExtraCard;
@@ -86,8 +80,8 @@
     }
 
     @Override
-    public void init(OverviewActionsView actionsView) {
-        super.init(actionsView);
+    public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
+        super.init(actionsView, splitPlaceholderView);
         setContentAlpha(0);
     }
 
@@ -104,31 +98,6 @@
         }
     }
 
-    /**
-     * Animates adjacent tasks and translate hotseat off screen as well.
-     */
-    @Override
-    public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv) {
-        AnimatorSet anim = super.createAdjacentPageAnimForTaskLaunch(tv);
-
-        if (!SysUINavigationMode.getMode(mActivity).hasGestures) {
-            // Hotseat doesn't move when opening recents with the button,
-            // so don't animate it here either.
-            return anim;
-        }
-
-        float allAppsProgressOffscreen = ALL_APPS_PROGRESS_OFF_SCREEN;
-        LauncherState state = mActivity.getStateManager().getState();
-        if ((state.getVisibleElements(mActivity) & ALL_APPS_HEADER_EXTRA) != 0) {
-            float maxShiftRange = mActivity.getDeviceProfile().heightPx;
-            float currShiftRange = mActivity.getAllAppsController().getShiftRange();
-            allAppsProgressOffscreen = 1f + (maxShiftRange - currShiftRange) / maxShiftRange;
-        }
-        anim.play(ObjectAnimator.ofFloat(
-                mActivity.getAllAppsController(), ALL_APPS_PROGRESS, allAppsProgressOffscreen));
-        return anim;
-    }
-
     @Override
     protected void onTaskLaunchAnimationEnd(boolean success) {
         if (success) {
@@ -150,6 +119,8 @@
     @Override
     public void onStateTransitionStart(LauncherState toState) {
         setOverviewStateEnabled(toState.overviewUi);
+        setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
+        setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
         setFreezeViewVisibility(true);
     }
 
@@ -160,8 +131,6 @@
             reset();
         }
         setOverlayEnabled(finalState == OVERVIEW || finalState == OVERVIEW_MODAL_TASK);
-        setOverviewGridEnabled(finalState.displayOverviewTasksAsGrid(mActivity));
-        setOverviewFullscreenEnabled(finalState.getOverviewFullscreenProgress() == 1);
         setFreezeViewVisibility(false);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index bdd0a36..882241e 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -26,6 +26,7 @@
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
+import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.Utilities.squaredHypot;
@@ -47,6 +48,7 @@
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
 
+import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
 import android.animation.LayoutTransition.TransitionListener;
@@ -113,6 +115,7 @@
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.OverScroller;
 import com.android.launcher3.util.ResourceBasedOverride.Overrides;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.ViewPool;
 import com.android.quickstep.AnimatedFloat;
@@ -210,11 +213,17 @@
                 }
             };
 
+    /**
+     * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they
+     * are currently both used to apply secondary translation. Should their use cases change to be
+     * more specific, we'd want to create a similar FloatProperty just for a TaskView's
+     * offsetX/Y property
+     */
     public static final FloatProperty<RecentsView> TASK_SECONDARY_TRANSLATION =
             new FloatProperty<RecentsView>("taskSecondaryTranslation") {
                 @Override
                 public void setValue(RecentsView recentsView, float v) {
-                    recentsView.setTaskViewsSecondaryTranslation(v);
+                    recentsView.setTaskViewsResistanceTranslation(v);
                 }
 
                 @Override
@@ -223,6 +232,25 @@
                 }
             };
 
+    /**
+     * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they
+     * are currently both used to apply secondary translation. Should their use cases change to be
+     * more specific, we'd want to create a similar FloatProperty just for a TaskView's
+     * offsetX/Y property
+     */
+    public static final FloatProperty<RecentsView> TASK_PRIMARY_TRANSLATION =
+            new FloatProperty<RecentsView>("taskPrimaryTranslation") {
+                @Override
+                public void setValue(RecentsView recentsView, float v) {
+                    recentsView.setTaskViewsPrimaryTranslation(v);
+                }
+
+                @Override
+                public Float get(RecentsView recentsView) {
+                    return recentsView.mTaskViewsPrimaryTranslation;
+                }
+            };
+
     /** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */
     public static final FloatProperty<RecentsView> RECENTS_SCALE_PROPERTY =
             new FloatProperty<RecentsView>("recentsScale") {
@@ -233,7 +261,8 @@
                     view.mLastComputedTaskPushOutDistance = null;
                     view.mLiveTileTaskViewSimulator.recentsViewScale.value = scale;
                     view.updatePageOffsets();
-                    view.setTaskViewsSecondaryTranslation(view.mTaskViewsSecondaryTranslation);
+                    view.setTaskViewsResistanceTranslation(view.mTaskViewsSecondaryTranslation);
+                    view.setTaskViewsPrimaryTranslation(view.mTaskViewsPrimaryTranslation);
                 }
 
                 @Override
@@ -264,6 +293,7 @@
     protected final TransformParams mLiveTileParams = new TransformParams();
     protected final TaskViewSimulator mLiveTileTaskViewSimulator;
     protected final Rect mLastComputedTaskSize = new Rect();
+    protected final Rect mLastComputedGridSize = new Rect();
     // How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot.
     protected Float mLastComputedTaskPushOutDistance = null;
     protected boolean mEnableDrawingLiveTile = false;
@@ -304,9 +334,12 @@
 
     private float mAdjacentPageOffset = 0;
     private float mTaskViewsSecondaryTranslation = 0;
+    private float mTaskViewsPrimaryTranslation = 0;
     // Progress from 0 to 1 where 0 is a carousel and 1 is a 2 row grid.
     private float mGridProgress = 0;
-    private boolean mShowAsGrid;
+
+    // The GestureEndTarget that is still in progress.
+    private GestureState.GestureEndTarget mCurrentGestureEndTarget;
 
     /**
      * TODO: Call reloadIdNeeded in onTaskStackChanged.
@@ -425,6 +458,28 @@
     private OnEmptyMessageUpdatedListener mOnEmptyMessageUpdatedListener;
     private Layout mEmptyTextLayout;
 
+    /**
+     * Placeholder view indicating where the first split screen selected app will be placed
+     */
+    private SplitPlaceholderView mSplitPlaceholderView;
+    /**
+     * The first task that split screen selection was initiated with. When split select state is
+     * initialized, we create a
+     * {@link #createTaskDismissAnimation(TaskView, boolean, boolean, long)} for this TaskView but
+     * don't actually remove the task since the user might back out. As such, we also ensure this
+     * View doesn't go back into the {@link #mTaskViewPool}, see {@link #onViewRemoved(View)}
+     */
+    private TaskView mSplitHiddenTaskView;
+    /**
+     * Keeps track of the index of the TaskView that split screen was initialized with so we know
+     * where to insert it back into list of taskViews in case user backs out of entering split
+     * screen.
+     * NOTE: This index is the index while {@link #mSplitHiddenTaskView} was a child of recentsView,
+     * this doesn't get adjusted to reflect the new child count after the taskView is dismissed/
+     * removed from recentsView
+     */
+    private int mSplitHiddenTaskViewIndex;
+
     // Keeps track of the index where the first TaskView should be
     private int mTaskViewStartIndex = 0;
     private OverviewActionsView mActionsView;
@@ -471,7 +526,7 @@
         setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
         mTaskTopMargin = getResources()
                 .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
-        mRowSpacing = (int) getResources().getDimension(R.dimen.recents_row_spacing);
+        mRowSpacing = getResources().getDimensionPixelSize(R.dimen.overview_grid_row_spacing);
         mSquaredTouchSlop = squaredTouchSlop(context);
 
         mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
@@ -501,11 +556,6 @@
         mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
         mLiveTileTaskViewSimulator.setOrientationState(mOrientationState);
         mLiveTileTaskViewSimulator.setDrawsBelowRecents(true);
-
-        mShowAsGrid =
-                mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
-        mActivity.addOnDeviceProfileChangeListener(newDp ->
-                mShowAsGrid = newDp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get());
     }
 
     public OverScroller getScroller() {
@@ -564,9 +614,6 @@
     @Override
     protected void onWindowVisibilityChanged(int visibility) {
         super.onWindowVisibilityChanged(visibility);
-        if (visibility != VISIBLE && LIVE_TILE.get()) {
-            finishRecentsAnimation(true /* toRecents */, null);
-        }
         updateTaskStackListenerState();
     }
 
@@ -580,9 +627,19 @@
         loadVisibleTaskData();
     }
 
-    public void init(OverviewActionsView actionsView) {
+    public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
         mActionsView = actionsView;
         mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
+        mSplitPlaceholderView = splitPlaceholderView;
+
+    }
+
+    public SplitPlaceholderView getSplitPlaceholder() {
+        return mSplitPlaceholderView;
+    }
+
+    public boolean isSplitSelectionActive() {
+        return mSplitPlaceholderView.getSplitController().isSplitSelectActive();
     }
 
     @Override
@@ -626,8 +683,9 @@
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
 
-        // Clear the task data for the removed child if it was visible
-        if (child instanceof TaskView) {
+        // Clear the task data for the removed child if it was visible unless it's the initial
+        // taskview for entering split screen, we only pretend to dismiss the task
+        if (child instanceof TaskView && child != mSplitHiddenTaskView) {
             TaskView taskView = (TaskView) child;
             mHasVisibleTaskData.delete(taskView.getTask().key.id);
             mTaskViewPool.recycle(taskView);
@@ -667,7 +725,7 @@
     }
 
     public boolean isTaskViewVisible(TaskView tv) {
-        if (mShowAsGrid) {
+        if (showAsGrid()) {
             int screenStart = mOrientationHandler.getPrimaryScroll(this);
             int screenEnd = screenStart + mOrientationHandler.getMeasuredSize(this);
             return isTaskViewWithinBounds(tv, screenStart, screenEnd);
@@ -679,9 +737,9 @@
 
     private boolean isTaskViewWithinBounds(TaskView tv, int start, int end) {
         int taskStart = mOrientationHandler.getChildStart(tv) + (int) tv.getOffsetAdjustment(
-                mOverviewFullscreenEnabled, mOverviewGridEnabled);
+                mOverviewFullscreenEnabled, showAsGrid());
         int taskSize = (int) (mOrientationHandler.getMeasuredSize(tv) * tv.getSizeAdjustment(
-                mOverviewFullscreenEnabled, mOverviewGridEnabled));
+                mOverviewFullscreenEnabled, showAsGrid()));
         int taskEnd = taskStart + taskSize;
         return (taskStart >= start && taskStart <= end) || (taskEnd >= start
                 && taskEnd <= end);
@@ -705,6 +763,9 @@
             // Reset the running task when leaving overview since it can still have a reference to
             // its thumbnail
             mTmpRunningTask = null;
+            if (mSplitPlaceholderView.getSplitController().isSplitSelectActive()) {
+                cancelSplitSelect(false);
+            }
         }
     }
 
@@ -746,7 +807,7 @@
     public boolean onTouchEvent(MotionEvent ev) {
         super.onTouchEvent(ev);
 
-        if (mShowAsGrid) {
+        if (showAsGrid()) {
             int taskCount = getTaskViewCount();
             for (int i = 0; i < taskCount; i++) {
                 TaskView taskView = getTaskViewAt(i);
@@ -813,7 +874,7 @@
 
     @Override
     protected boolean snapToPageInFreeScroll() {
-        return !mShowAsGrid;
+        return !showAsGrid();
     }
 
     @Override
@@ -1012,6 +1073,10 @@
         setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
                 dp.widthPx - mInsets.right - mTempRect.right,
                 dp.heightPx - mInsets.bottom - mTempRect.bottom);
+
+        mSizeStrategy.calculateGridSize(mActivity, mActivity.getDeviceProfile(),
+                mLastComputedGridSize);
+
         // Force TaskView to update size from thumbnail
         updateTaskSize();
     }
@@ -1020,14 +1085,35 @@
      * Updates TaskView scaling and translation required to support variable width.
      */
     private void updateTaskSize() {
-        float accumulatedTranslationX = 0;
         final int taskCount = getTaskViewCount();
+        float accumulatedTranslationX = 0;
+        float[] fullscreenTranslations = new float[taskCount];
+        int firstNonHomeTaskIndex = 0;
         for (int i = 0; i < taskCount; i++) {
             TaskView taskView = getTaskViewAt(i);
+            if (isHomeTask(taskView)) {
+                if (firstNonHomeTaskIndex == i) {
+                    firstNonHomeTaskIndex++;
+                }
+                continue;
+            }
+
             taskView.updateTaskSize();
-            taskView.setAccumulatedFullscreenTranslationX(accumulatedTranslationX);
-            accumulatedTranslationX += taskView.getFullscreenTranslationX();
+            fullscreenTranslations[i] += accumulatedTranslationX;
+            float widthDiff =
+                    taskView.getLayoutParams().width * (1 - taskView.getFullscreenScale());
+            float fullscreenTranslationX = mIsRtl ? widthDiff : -widthDiff;
+            fullscreenTranslations[i] += fullscreenTranslationX;
+            accumulatedTranslationX += fullscreenTranslationX;
         }
+
+        // We need to maintain first non-home task's full screen translation at 0, now shift
+        // translation of all the TaskViews to achieve that.
+        for (int i = firstNonHomeTaskIndex; i < taskCount; i++) {
+            getTaskViewAt(i).setFullscreenTranslationX(
+                    fullscreenTranslations[i] - fullscreenTranslations[firstNonHomeTaskIndex]);
+        }
+
         updateGridProperties();
     }
 
@@ -1133,7 +1219,7 @@
         int upper = 0;
         int visibleStart = 0;
         int visibleEnd = 0;
-        if (mShowAsGrid) {
+        if (showAsGrid()) {
             int screenStart = mOrientationHandler.getPrimaryScroll(this);
             int pageOrientedSize = mOrientationHandler.getMeasuredSize(this);
             int halfScreenSize = pageOrientedSize / 2;
@@ -1153,7 +1239,7 @@
             Task task = taskView.getTask();
             int index = indexOfChild(taskView);
             boolean visible;
-            if (mShowAsGrid) {
+            if (showAsGrid()) {
                 visible = isTaskViewWithinBounds(taskView, visibleStart, visibleEnd);
             } else {
                 visible = lower <= index && index <= upper;
@@ -1333,7 +1419,7 @@
      * Called when a gesture from an app has finished, and an end target has been determined.
      */
     public void onGestureEndTargetCalculated(GestureState.GestureEndTarget endTarget) {
-
+        mCurrentGestureEndTarget = endTarget;
     }
 
     /**
@@ -1354,9 +1440,11 @@
         animateUpRunningTaskIconScale();
 
         // TODO: This should be tied to whether there is a focus app on overview.
-        if (!mShowAsGrid) {
+        if (!showAsGrid()) {
             animateActionsViewIn();
         }
+
+        mCurrentGestureEndTarget = null;
     }
 
     /**
@@ -1447,29 +1535,6 @@
         }
     }
 
-    public void showNextTask() {
-        final TaskView runningTaskView = getRunningTaskView();
-        final TaskView targetTask;
-
-        if (runningTaskView == null) {
-            // Launch the first task
-            if (getTaskViewCount() > 0) {
-                targetTask = getTaskViewAt(0);
-            } else {
-                return;
-            }
-        } else {
-            final TaskView nextTask = getNextTaskView();
-            if (nextTask != null) {
-                targetTask = nextTask;
-            } else {
-                targetTask = runningTaskView;
-            }
-        }
-        targetTask.setEndQuickswitchCuj(true);
-        targetTask.launchTask(true);
-    }
-
     public void setRunningTaskIconScaledDown(boolean isScaledDown) {
         if (mRunningTaskIconScaledDown != isScaledDown) {
             mRunningTaskIconScaledDown = isScaledDown;
@@ -1521,38 +1586,43 @@
         }
 
         final int boxLength = Math.max(mTaskWidth, mTaskHeight);
-
-        float availableHeight =
-                mTaskTopMargin + mTaskHeight + mSizeStrategy.getOverviewActionsHeight(mContext);
+        float availableHeight = mLastComputedGridSize.height();
         float rowHeight = (availableHeight - mRowSpacing) / 2;
         float gridScale = rowHeight / (boxLength + mTaskTopMargin);
 
-        TaskView firstTask = getTaskViewAt(0);
-        float firstTaskWidthOffset;
-        if (mIsRtl) {
-            // Move the first task to the right edge.
-            firstTaskWidthOffset = mTaskWidth - firstTask.getLayoutParams().width * gridScale;
-        } else {
-            // Move the first task to the left edge.
-            firstTaskWidthOffset = -firstTask.getLayoutParams().width * (1 - gridScale);
-        }
-
         int topRowWidth = 0;
         int bottomRowWidth = 0;
         float topAccumulatedTranslationX = 0;
         float bottomAccumulatedTranslationX = 0;
         IntSet topSet = new IntSet();
+        IntSet bottomSet = new IntSet();
         float[] gridTranslations = new float[taskCount];
+        int firstNonHomeTaskIndex = 0;
         for (int i = 0; i < taskCount; i++) {
             TaskView taskView = getTaskViewAt(i);
+            if (isHomeTask(taskView)) {
+                if (firstNonHomeTaskIndex == i) {
+                    firstNonHomeTaskIndex++;
+                }
+                continue;
+            }
+
             taskView.setGridScale(gridScale);
 
-            float taskWidthDiff = mTaskWidth - taskView.getLayoutParams().width * gridScale;
-            float taskWidthOffset = mIsRtl ? taskWidthDiff : -taskWidthDiff;
-            // Visually we want to move all task by firstTaskWidthOffset, but calculate page scroll
-            // according to right edge (or left in nonRtl) of TaskView.
-            gridTranslations[i] = firstTaskWidthOffset - taskWidthOffset;
-            taskView.setGridOffsetTranslationX(taskWidthOffset);
+            float scaledWidth = taskView.getLayoutParams().width * gridScale;
+            float taskGridHorizontalDiff;
+            if (mIsRtl) {
+                float taskRight = mLastComputedTaskSize.left + scaledWidth;
+                taskGridHorizontalDiff = mLastComputedGridSize.right - taskRight;
+            } else {
+                float taskLeft = mLastComputedTaskSize.right - scaledWidth;
+                taskGridHorizontalDiff = mLastComputedGridSize.left - taskLeft;
+            }
+            gridTranslations[i] -= taskGridHorizontalDiff;
+            taskView.setGridOffsetTranslationX(taskGridHorizontalDiff);
+
+            float taskGridVerticalDiff = mLastComputedGridSize.top + mTaskTopMargin * gridScale
+                    - mLastComputedTaskSize.top;
 
             // Off-set gap due to task scaling.
             float widthDiff = taskView.getLayoutParams().width * (1 - gridScale);
@@ -1567,26 +1637,26 @@
                 topRowWidth += taskView.getLayoutParams().width * gridScale + mPageSpacing;
                 topSet.add(i);
 
-                taskView.setGridTranslationY(0);
+                taskView.setGridTranslationY(taskGridVerticalDiff);
 
                 // Move horizontally into empty space.
                 float widthOffset = 0;
-                for (int j = i - 1; !topSet.contains(j) && j >= 0; j--) {
+                for (int j = i - 1; bottomSet.contains(j); j--) {
                     widthOffset += getTaskViewAt(j).getLayoutParams().width * gridScale
                             + mPageSpacing;
                 }
 
                 float gridTranslationX = mIsRtl ? widthOffset : -widthOffset;
                 gridTranslations[i] += gridTranslationX;
-                topAccumulatedTranslationX += gridTranslationX + gridScaleTranslationX;
-                bottomAccumulatedTranslationX += gridScaleTranslationX;
+                topAccumulatedTranslationX += gridTranslationX;
             } else {
                 gridTranslations[i] += bottomAccumulatedTranslationX;
                 bottomRowWidth += taskView.getLayoutParams().width * gridScale + mPageSpacing;
+                bottomSet.add(i);
 
                 // Move into bottom row.
                 float heightOffset = (boxLength + mTaskTopMargin) * gridScale + mRowSpacing;
-                taskView.setGridTranslationY(heightOffset);
+                taskView.setGridTranslationY(heightOffset + taskGridVerticalDiff);
 
                 // Move horizontally into empty space.
                 float widthOffset = 0;
@@ -1597,9 +1667,10 @@
 
                 float gridTranslationX = mIsRtl ? widthOffset : -widthOffset;
                 gridTranslations[i] += gridTranslationX;
-                topAccumulatedTranslationX += gridScaleTranslationX;
-                bottomAccumulatedTranslationX += gridTranslationX + gridScaleTranslationX;
+                bottomAccumulatedTranslationX += gridTranslationX;
             }
+            topAccumulatedTranslationX += gridScaleTranslationX;
+            bottomAccumulatedTranslationX += gridScaleTranslationX;
         }
 
         // Use the accumulated translation of the longer row.
@@ -1622,44 +1693,47 @@
         float clearAllShorterRowCompensation =
                 mIsRtl ? -shorterRowCompensation : shorterRowCompensation;
 
-        // If the total width is shorter than one task's width, move ClearAllButton further away
+        // If the total width is shorter than one grid's width, move ClearAllButton further away
         // accordingly.
         float clearAllShortTotalCompensation = 0;
         float longRowWidth = Math.max(topRowWidth, bottomRowWidth);
-        if (longRowWidth < mTaskWidth) {
-            float shortTotalCompensation = mTaskWidth - longRowWidth;
+        if (longRowWidth < mLastComputedGridSize.width()) {
+            float shortTotalCompensation = mLastComputedGridSize.width() - longRowWidth;
             clearAllShortTotalCompensation =
                     mIsRtl ? -shortTotalCompensation : shortTotalCompensation;
         }
 
-        float clearAllTotalTranslationX = firstTaskWidthOffset + clearAllAccumulatedTranslation
-                + clearAllShorterRowCompensation + clearAllShortTotalCompensation;
+        float clearAllTotalTranslationX =
+                clearAllAccumulatedTranslation + clearAllShorterRowCompensation
+                        + clearAllShortTotalCompensation;
 
-        // We need to maintain first task's grid translation at 0, now shift translation of all
-        // the TaskViews to achieve that.
-        for (int i = 0; i < taskCount; i++) {
-            getTaskViewAt(i).setGridTranslationX(gridTranslations[i] - gridTranslations[0]);
+        // We need to maintain first non-home task's grid translation at 0, now shift translation
+        // of all the TaskViews to achieve that.
+        for (int i = firstNonHomeTaskIndex; i < taskCount; i++) {
+            getTaskViewAt(i).setGridTranslationX(
+                    gridTranslations[i] - gridTranslations[firstNonHomeTaskIndex]);
         }
-        mClearAllButton.setGridTranslationPrimary(clearAllTotalTranslationX - gridTranslations[0]);
+        mClearAllButton.setGridTranslationPrimary(
+                clearAllTotalTranslationX - gridTranslations[firstNonHomeTaskIndex]);
 
         setGridProgress(mGridProgress);
     }
 
+    protected boolean isHomeTask(TaskView taskView) {
+        return false;
+    }
+
     /**
      * Moves TaskView and ClearAllButton between carousel and 2 row grid.
      *
      * @param gridProgress 0 = carousel; 1 = 2 row grid.
      */
-    public void setGridProgress(float gridProgress) {
+    private void setGridProgress(float gridProgress) {
         int taskCount = getTaskViewCount();
         if (taskCount == 0) {
             return;
         }
 
-        if (!mShowAsGrid) {
-            gridProgress = 0;
-        }
-
         mGridProgress = gridProgress;
 
         for (int i = 0; i < taskCount; i++) {
@@ -1760,7 +1834,7 @@
         // alpha is set to 0 so that it can be recycled in the view pool properly
         anim.setFloat(taskView, VIEW_ALPHA, 0, ACCEL_2);
         FloatProperty<TaskView> secondaryViewTranslate =
-                taskView.getDismissTaskTranslationProperty();
+                taskView.getSecondaryDissmissTranslationProperty();
         int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
         int verticalFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
 
@@ -1803,7 +1877,8 @@
                 if (animateTaskView) {
                     addDismissedTaskAnimations(taskView, duration, anim);
                 }
-            } else if (!mShowAsGrid) {  // Don't animate other tasks when dismissing in grid for now
+            } else if (!showAsGrid()) {
+                // For grid layout, don't animate other tasks when dismissing in grid for now.
                 // If we just take newScroll - oldScroll, everything to the right of dragged task
                 // translates to the left. We need to offset this in some cases:
                 // - In RTL, add page offset to all pages, since we want pages to move to the right
@@ -1823,10 +1898,23 @@
                         offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
                     }
                 }
+
+                // Additional offset for fake landscape, if the pinning happens to the right or
+                // left, we need to scroll all the tasks away from the direction of the splaceholder
+                // view
+                if (isSplitSelectionActive()) {
+                    int splitPosition = getSplitPlaceholder().getSplitController()
+                            .getActiveSplitPositionOption().mStagePosition;
+                    int direction = mOrientationHandler
+                            .getSplitTranslationDirectionFactor(splitPosition);
+                    int splitOffset = mOrientationHandler.getSplitAnimationTranslation(
+                            mSplitPlaceholderView.getHeight(), mActivity.getDeviceProfile());
+                    offset += direction * splitOffset;
+                }
                 int scrollDiff = newScroll[i] - oldScroll[i] + offset;
                 if (scrollDiff != 0) {
                     FloatProperty translationProperty = child instanceof TaskView
-                            ? ((TaskView) child).getFillDismissGapTranslationProperty()
+                            ? ((TaskView) child).getPrimaryDismissTranslationProperty()
                             : mOrientationHandler.getPrimaryViewTranslate();
 
                     ResourceProvider rp = DynamicResource.provider(mActivity);
@@ -1904,6 +1992,12 @@
                     onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
                 }
                 resetTaskVisuals();
+                if (mActivity.isInState(OVERVIEW_SPLIT_SELECT)) {
+                    // We want to keep the tasks translations in this temporary state
+                    // after resetting the rest above
+                    setTaskViewsResistanceTranslation(mTaskViewsSecondaryTranslation);
+                    setTaskViewsPrimaryTranslation(mTaskViewsPrimaryTranslation);
+                }
                 mPendingAnimation = null;
             }
         });
@@ -2295,7 +2389,7 @@
         return distanceToOffscreen * offsetProgress;
     }
 
-    private void setTaskViewsSecondaryTranslation(float translation) {
+    private void setTaskViewsResistanceTranslation(float translation) {
         mTaskViewsSecondaryTranslation = translation;
         for (int i = 0; i < getTaskViewCount(); i++) {
             TaskView task = getTaskViewAt(i);
@@ -2304,6 +2398,15 @@
         mLiveTileTaskViewSimulator.recentsViewSecondaryTranslation.value = translation;
     }
 
+    private void setTaskViewsPrimaryTranslation(float translation) {
+        mTaskViewsPrimaryTranslation = translation;
+        for (int i = 0; i < getTaskViewCount(); i++) {
+            TaskView task = getTaskViewAt(i);
+            task.getPrimaryDismissTranslationProperty().set(task, translation / getScaleY());
+        }
+        mLiveTileTaskViewSimulator.recentsViewPrimaryTranslation.value = translation;
+    }
+
     /**
      * TODO: Do not assume motion across X axis for adjacent page
      */
@@ -2321,6 +2424,111 @@
         }
     }
 
+    public void initiateSplitSelect(TaskView taskView, SplitPositionOption splitPositionOption) {
+        mSplitHiddenTaskView = taskView;
+        mSplitPlaceholderView.getSplitController().setInitialTaskSelect(taskView,
+                splitPositionOption);
+        mSplitHiddenTaskViewIndex = indexOfChild(taskView);
+        mActivity.getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
+    }
+
+    public PendingAnimation createSplitSelectInitAnimation() {
+        int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
+        return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration);
+    }
+
+    public void confirmSplitSelect(TaskView taskView) {
+        mSplitPlaceholderView.getSplitController().setSecondTaskId(taskView);
+        resetTaskVisuals();
+        setTranslationY(0);
+    }
+
+    public PendingAnimation cancelSplitSelect(boolean animate) {
+        mSplitPlaceholderView.getSplitController().resetState();
+        int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
+        PendingAnimation pendingAnim = new PendingAnimation(duration);
+        if (!animate) {
+            resetFromSplitSelectionState();
+            return pendingAnim;
+        }
+
+        addViewInLayout(mSplitHiddenTaskView, mSplitHiddenTaskViewIndex,
+                mSplitHiddenTaskView.getLayoutParams());
+        mSplitHiddenTaskView.setAlpha(0);
+        int[] oldScroll = new int[getChildCount()];
+        getPageScrolls(oldScroll, false,
+                view -> view.getVisibility() != GONE && view != mSplitHiddenTaskView);
+
+        // x is correct, y is before tasks move up
+        int[] locationOnScreen = mSplitHiddenTaskView.getLocationOnScreen();
+        int[] newScroll = new int[getChildCount()];
+        getPageScrolls(newScroll, false, SIMPLE_SCROLL_LOGIC);
+
+        boolean needsCurveUpdates = false;
+        for (int i = mSplitHiddenTaskViewIndex; i >= 0; i--) {
+            View child = getChildAt(i);
+            if (child == mSplitHiddenTaskView) {
+
+                int left = newScroll[i] + getPaddingStart();
+                int topMargin = mSplitHiddenTaskView.getThumbnailTopMargin();
+                int top = -mSplitHiddenTaskView.getHeight() - locationOnScreen[1];
+                mSplitHiddenTaskView.layout(left, top,
+                        left + mSplitHiddenTaskView.getWidth(),
+                        top + mSplitHiddenTaskView.getHeight());
+                pendingAnim.add(ObjectAnimator.ofFloat(mSplitHiddenTaskView, TRANSLATION_Y,
+                        -top + mSplitPlaceholderView.getHeight() - topMargin));
+                pendingAnim.add(ObjectAnimator.ofFloat(mSplitHiddenTaskView, ALPHA, 1));
+            } else {
+                // If insertion is on last index (furthest from clear all), we directly add the view
+                // else we translate all views to the right of insertion index further right,
+                // ignore views to left
+                int scrollDiff = newScroll[i] - oldScroll[i];
+                if (scrollDiff != 0) {
+                    FloatProperty translationProperty = child instanceof TaskView
+                            ? ((TaskView) child).getPrimaryDismissTranslationProperty()
+                            : mOrientationHandler.getPrimaryViewTranslate();
+
+                    ResourceProvider rp = DynamicResource.provider(mActivity);
+                    SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_END)
+                            .setDampingRatio(
+                                    rp.getFloat(R.dimen.dismiss_task_trans_x_damping_ratio))
+                            .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_x_stiffness));
+                    pendingAnim.add(ObjectAnimator.ofFloat(child, translationProperty, scrollDiff)
+                            .setDuration(duration), ACCEL, sp);
+                    needsCurveUpdates = true;
+                }
+            }
+        }
+
+        if (needsCurveUpdates) {
+            pendingAnim.addOnFrameCallback(this::updateCurveProperties);
+        }
+
+        pendingAnim.addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                resetFromSplitSelectionState();
+            }
+        });
+
+        return pendingAnim;
+    }
+
+    private void resetFromSplitSelectionState() {
+        mSplitHiddenTaskView.setTranslationY(0);
+        int pageToSnapTo = mCurrentPage;
+        if (mSplitHiddenTaskViewIndex <= pageToSnapTo) {
+            pageToSnapTo += 1;
+        } else {
+            pageToSnapTo = mSplitHiddenTaskViewIndex;
+        }
+        snapToPageImmediately(pageToSnapTo);
+        onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
+        resetTaskVisuals();
+        mSplitHiddenTaskView = null;
+        mSplitHiddenTaskViewIndex = -1;
+    }
+
     private void updateDeadZoneRects() {
         // Get the deadzone rect surrounding the clear all button to not dismiss overview to home
         mClearAllButtonDeadZoneRect.setEmpty();
@@ -2494,17 +2702,11 @@
         }
         mPendingAnimation.addEndListener(isSuccess -> {
             if (isSuccess) {
-                Consumer<Boolean> onLaunchResult = (result) -> {
-                    onTaskLaunchAnimationEnd(result);
-                    if (!result) {
-                        tv.notifyTaskLaunchFailed(TAG);
-                    }
-                };
                 if (LIVE_TILE.get()) {
                     finishRecentsAnimation(false /* toRecents */, null);
-                    onLaunchResult.accept(true /* success */);
+                    onTaskLaunchAnimationEnd(true /* success */);
                 } else {
-                    tv.launchTask(false, onLaunchResult, getHandler());
+                    tv.launchTask(this::onTaskLaunchAnimationEnd);
                 }
                 Task task = tv.getTask();
                 if (task != null) {
@@ -2669,9 +2871,9 @@
             float scrollDiff = 0;
             if (child instanceof TaskView) {
                 scrollDiff = ((TaskView) child).getScrollAdjustment(mOverviewFullscreenEnabled,
-                        mOverviewGridEnabled);
+                        showAsGrid());
             } else if (child instanceof ClearAllButton) {
-                scrollDiff = ((ClearAllButton) child).getScrollAdjustment(mOverviewGridEnabled);
+                scrollDiff = ((ClearAllButton) child).getScrollAdjustment(showAsGrid());
             }
 
             if (scrollDiff != 0) {
@@ -2688,9 +2890,9 @@
         View child = getChildAt(index);
         if (child instanceof TaskView) {
             childOffset += ((TaskView) child).getOffsetAdjustment(mOverviewFullscreenEnabled,
-                    mOverviewGridEnabled);
+                    showAsGrid());
         } else if (child instanceof ClearAllButton) {
-            childOffset += ((ClearAllButton) child).getOffsetAdjustment(mOverviewGridEnabled);
+            childOffset += ((ClearAllButton) child).getOffsetAdjustment(showAsGrid());
         }
         return childOffset;
     }
@@ -2702,7 +2904,7 @@
             return super.getChildVisibleSize(index);
         }
         return (int) (super.getChildVisibleSize(index) * taskView.getSizeAdjustment(
-                mOverviewFullscreenEnabled, mOverviewGridEnabled));
+                mOverviewFullscreenEnabled, showAsGrid()));
     }
 
     @Override
@@ -2773,6 +2975,15 @@
                 taskView.getGridTranslationY());
     }
 
+    /**
+     * Returns the progress of forming a grid from carousel.
+     *
+     * @return A float from 0 to 1 where 0 is a carousel and 1 is a 2 row grid.
+     */
+    public float getGridProgress() {
+        return mGridProgress;
+    }
+
     public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
         float degreesRotated;
         if (navbarRotation == 0) {
@@ -2904,6 +3115,12 @@
         return mSizeStrategy;
     }
 
+    private boolean showAsGrid() {
+        return mOverviewGridEnabled || (mCurrentGestureEndTarget != null
+                && mSizeStrategy.stateFromGestureEndTarget(
+                mCurrentGestureEndTarget).displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
+    }
+
     /**
      * Used to register callbacks for when our empty message state changes.
      *
diff --git a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
new file mode 100644
index 0000000..fb9be81
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2021 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.util.AttributeSet;
+import android.util.FloatProperty;
+import android.view.View;
+
+import com.android.quickstep.util.SplitSelectStateController;
+
+public class SplitPlaceholderView extends View {
+
+    public static final FloatProperty<SplitPlaceholderView> ALPHA_FLOAT =
+            new FloatProperty<SplitPlaceholderView>("SplitViewAlpha") {
+                @Override
+                public void setValue(SplitPlaceholderView splitPlaceholderView, float v) {
+                    splitPlaceholderView.setVisibility(v != 0 ? VISIBLE : GONE);
+                    splitPlaceholderView.setAlpha(v);
+                }
+
+                @Override
+                public Float get(SplitPlaceholderView splitPlaceholderView) {
+                    return splitPlaceholderView.getAlpha();
+                }
+            };
+
+    private SplitSelectStateController mSplitController;
+
+    public SplitPlaceholderView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void init(SplitSelectStateController controller) {
+        this.mSplitController = controller;
+    }
+
+    public SplitSelectStateController getSplitController() {
+        return mSplitController;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 2315147..a5b7a5b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -23,12 +23,14 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
+import android.graphics.Outline;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
@@ -41,10 +43,10 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskUtils;
+import com.android.quickstep.util.TaskCornerRadius;
 
 /**
  * Contains options for a recent task when long-pressing its icon.
@@ -72,6 +74,7 @@
 
         mActivity = BaseDraggingActivity.fromContext(context);
         mThumbnailTopMargin = getResources().getDimension(R.dimen.task_thumbnail_top_margin);
+        setClipToOutline(true);
     }
 
     @Override
@@ -108,6 +111,17 @@
         return (type & TYPE_TASK_MENU) != 0;
     }
 
+    @Override
+    public ViewOutlineProvider getOutlineProvider() {
+        return new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(),
+                        TaskCornerRadius.get(view.getContext()));
+            }
+        };
+    }
+
     public void setPosition(float x, float y, PagedOrientationHandler pagedOrientationHandler) {
         float adjustedY = y + mThumbnailTopMargin;
         // Changing pivot to make computations easier
@@ -172,7 +186,8 @@
         mTaskName.setText(TaskUtils.getTitle(getContext(), taskView.getTask()));
         mTaskName.setOnClickListener(v -> close(true));
 
-        TaskOverlayFactory.getEnabledShortcuts(taskView).forEach(this::addMenuOption);
+        TaskOverlayFactory.getEnabledShortcuts(taskView, mActivity.getDeviceProfile())
+                .forEach(this::addMenuOption);
     }
 
     private void addMenuOption(SystemShortcut menuOption) {
@@ -182,6 +197,8 @@
                 menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
         LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
         mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp);
+        menuOptionView.setEnabled(menuOption.isEnabled());
+        menuOptionView.setAlpha(menuOption.isEnabled() ? 1 : 0.5f);
         menuOptionView.setOnClickListener(view -> {
             if (LIVE_TILE.get()) {
                 RecentsView recentsView = mTaskView.getRecentsView();
@@ -260,7 +277,7 @@
     }
 
     private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
-        float radius = Themes.getDialogCornerRadius(getContext());
+        float radius = TaskCornerRadius.get(mContext);
         Rect fromRect = new Rect(0, 0, getWidth(), 0);
         Rect toRect = new Rect(0, 0, getWidth(), getHeight());
         return new RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect);
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 4c21745..17d075f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -212,13 +212,6 @@
         return mDimAlpha;
     }
 
-    public Rect getInsets(Rect fallback) {
-        if (mThumbnailData != null) {
-            return mThumbnailData.insets;
-        }
-        return fallback;
-    }
-
     /**
      * Get the scaled insets that are being used to draw the task view. This is a subsection of
      * the full snapshot.
@@ -230,6 +223,10 @@
             return Insets.NONE;
         }
 
+        if (!TaskView.CLIP_STATUS_AND_NAV_BARS) {
+            return Insets.NONE;
+        }
+
         RectF bitmapRect = new RectF(
                 0, 0,
                 mThumbnailData.thumbnail.getWidth(), mThumbnailData.thumbnail.getHeight());
@@ -382,9 +379,10 @@
                     mThumbnailData.thumbnail.getHeight());
             int currentRotation = getTaskView().getRecentsView().getPagedViewOrientedState()
                     .getRecentsActivityRotation();
+            boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
             mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
                     getMeasuredWidth(), getMeasuredHeight(), mActivity.getDeviceProfile(),
-                    currentRotation);
+                    currentRotation, isRtl);
 
             mBitmapShader.setLocalMatrix(mPreviewPositionHelper.mMatrix);
             mPaint.setShader(mBitmapShader);
@@ -459,7 +457,6 @@
         // Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
         private final RectF mClippedInsets = new RectF();
         private final Matrix mMatrix = new Matrix();
-        private float mClipBottom = -1;
         private boolean mIsOrientationChanged;
 
         public Matrix getMatrix() {
@@ -470,13 +467,15 @@
          * Updates the matrix based on the provided parameters
          */
         public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
-                int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation) {
+                int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation,
+                boolean isRtl) {
             boolean isRotated = false;
             boolean isOrientationDifferent;
 
             int thumbnailRotation = thumbnailData.rotation;
             int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
-            RectF thumbnailClipHint = new RectF(thumbnailData.insets);
+            RectF thumbnailClipHint = TaskView.CLIP_STATUS_AND_NAV_BARS
+                    ? new RectF(thumbnailData.insets) : new RectF();
 
             float scale = thumbnailData.scale;
             final float thumbnailScale;
@@ -503,6 +502,17 @@
                 float availableHeight = surfaceHeight
                         - (thumbnailClipHint.top + thumbnailClipHint.bottom);
 
+                if (isRotated) {
+                    float canvasAspect = canvasWidth / (float) canvasHeight;
+                    float availableAspect = availableHeight / availableWidth;
+                    // Do not rotate thumbnail if it would not improve fit
+                    if (Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect,
+                            availableAspect, 0.1f)) {
+                        isRotated = false;
+                        isOrientationDifferent = false;
+                    }
+                }
+
                 final float targetW, targetH;
                 if (isOrientationDifferent) {
                     targetW = canvasHeight;
@@ -538,28 +548,25 @@
                     }
                 }
 
-                // Update the clip hints
-                float halfExtraW = (availableWidth - croppedWidth) / 2;
-                thumbnailClipHint.left += halfExtraW;
-                thumbnailClipHint.right += halfExtraW;
-                if (thumbnailClipHint.left < 0) {
-                    thumbnailClipHint.right += thumbnailClipHint.left;
-                    thumbnailClipHint.left = 0;
-                } else if (thumbnailClipHint.right < 0) {
-                    thumbnailClipHint.left += thumbnailClipHint.right;
+                // Update the clip hints. Align to 0,0, crop the remaining.
+                if (isRtl) {
+                    if (thumbnailClipHint.right < 0) {
+                        thumbnailClipHint.left += thumbnailClipHint.right;
+                    }
                     thumbnailClipHint.right = 0;
+                    thumbnailClipHint.left += availableWidth - croppedWidth;
+                } else {
+                    if (thumbnailClipHint.left < 0) {
+                        thumbnailClipHint.right += thumbnailClipHint.left;
+                    }
+                    thumbnailClipHint.left = 0;
+                    thumbnailClipHint.right += availableWidth - croppedWidth;
                 }
-
-                float halfExtraH = (availableHeight - croppedHeight) / 2;
-                thumbnailClipHint.top += halfExtraH;
-                thumbnailClipHint.bottom += halfExtraH;
                 if (thumbnailClipHint.top < 0) {
                     thumbnailClipHint.bottom += thumbnailClipHint.top;
-                    thumbnailClipHint.top = 0;
-                } else if (thumbnailClipHint.bottom < 0) {
-                    thumbnailClipHint.top += thumbnailClipHint.bottom;
-                    thumbnailClipHint.bottom = 0;
                 }
+                thumbnailClipHint.top = 0;
+                thumbnailClipHint.bottom += availableHeight - croppedHeight;
 
                 thumbnailScale = targetW / (croppedWidth * scale);
             }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 88545c6..809adcb 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -27,7 +27,8 @@
 import static android.view.Surface.ROTATION_90;
 import static android.widget.Toast.LENGTH_SHORT;
 
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
+import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.Utilities.comp;
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
@@ -37,6 +38,7 @@
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 
@@ -52,7 +54,6 @@
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-import android.os.Handler;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Log;
@@ -66,6 +67,8 @@
 import android.widget.FrameLayout;
 import android.widget.Toast;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
@@ -79,7 +82,10 @@
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.TransformingTouchDelegate;
 import com.android.launcher3.util.ViewPool.Reusable;
 import com.android.quickstep.RecentsModel;
@@ -117,6 +123,12 @@
      */
     public static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
 
+    /**
+     * Should the TaskView display clip off the status and navigation bars in recents. When this
+     * is false the overview shows the whole screen scaled down instead.
+     */
+    public static final boolean CLIP_STATUS_AND_NAV_BARS = false;
+
     private static final float EDGE_SCALE_DOWN_FACTOR_CAROUSEL = 0.03f;
     private static final float EDGE_SCALE_DOWN_FACTOR_GRID = 0.00f;
 
@@ -263,7 +275,6 @@
     private float mTaskResistanceTranslationY;
     // The following translation variables should only be used in the same orientation as Launcher.
     private float mFullscreenTranslationX;
-    private float mAccumulatedFullscreenTranslationX;
     private float mBoxTranslationY;
     // The following grid translations scales with mGridProgress.
     private float mGridTranslationX;
@@ -335,7 +346,12 @@
                 });
                 anim.start();
             } else {
-                launchTask(true /* animate */);
+                if (mActivity.isInState(OVERVIEW_SPLIT_SELECT)) {
+                    // User tapped to select second split screen app
+                    getRecentsView().confirmSplitSelect(this);
+                } else {
+                    launchTaskAnimated();
+                }
             }
             mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
                     .log(LAUNCHER_TASK_LAUNCH_TAP);
@@ -483,63 +499,62 @@
                 .createPlaybackController();
     }
 
-    public void launchTask(boolean animate) {
-        launchTask(animate, false /* freezeTaskList */);
-    }
-
-    public void launchTask(boolean animate, boolean freezeTaskList) {
-        launchTask(animate, freezeTaskList, (result) -> {
-            if (!result) {
-                notifyTaskLaunchFailed(TAG);
-            }
-        }, getHandler());
-    }
-
-    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) {
+    /**
+     * Starts the task associated with this view and animates the startup.
+     * @return CompletionStage to indicate the animation completion or null if the launch failed.
+     */
+    public RunnableList launchTaskAnimated() {
         if (mTask != null) {
-            final ActivityOptions opts;
             TestLogging.recordEvent(
                     TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
-            if (animate) {
-                opts = mActivity.getActivityLaunchOptions(this);
-                if (freezeTaskList) {
-                    ActivityOptionsCompat.setFreezeRecentTasksList(opts);
-                }
-                ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key,
-                        opts, resultCallback, resultCallbackHandler);
+            ActivityOptionsWrapper opts =  mActivity.getActivityLaunchOptions(this);
+            if (ActivityManagerWrapper.getInstance()
+                    .startActivityFromRecents(mTask.key, opts.options)) {
+                return opts.onEndCallback;
             } else {
-                opts = ActivityOptionsCompat.makeCustomAnimation(getContext(), 0, 0, () -> {
-                    if (resultCallback != null) {
-                        // Only post the animation start after the system has indicated that the
-                        // transition has started
-                        resultCallbackHandler.post(() -> resultCallback.accept(true));
-                    }
-                }, resultCallbackHandler);
-                if (freezeTaskList) {
-                    ActivityOptionsCompat.setFreezeRecentTasksList(opts);
-                }
-                UI_HELPER_EXECUTOR.execute(
-                        () -> ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(
-                                mTask.key,
-                                opts,
-                                (success) -> {
-                                    if (resultCallback != null && !success) {
-                                        // If the call to start activity failed, then post the
-                                        // result
-                                        // immediately, otherwise, wait for the animation start
-                                        // callback
-                                        // from the activity options above
-                                        resultCallbackHandler.post(
-                                                () -> resultCallback.accept(false));
-                                    }
-                                }, resultCallbackHandler));
+                notifyTaskLaunchFailed(TAG);
+                return null;
             }
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Starts the task associated with this view without any animation
+     */
+    public void launchTask(@NonNull Consumer<Boolean> callback) {
+        launchTask(callback, false /* freezeTaskList */);
+    }
+
+    /**
+     * Starts the task associated with this view without any animation
+     */
+    public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
+        if (mTask != null) {
+            TestLogging.recordEvent(
+                    TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
+
+            // Indicate success once the system has indicated that the transition has started
+            ActivityOptions opts = ActivityOptionsCompat.makeCustomAnimation(
+                    getContext(), 0, 0, () -> callback.accept(true), MAIN_EXECUTOR.getHandler());
+            if (freezeTaskList) {
+                ActivityOptionsCompat.setFreezeRecentTasksList(opts);
+            }
+            Task.TaskKey key = mTask.key;
+            UI_HELPER_EXECUTOR.execute(() -> {
+                if (!ActivityManagerWrapper.getInstance().startActivityFromRecents(key, opts)) {
+                    // If the call to start activity failed, then post the result immediately,
+                    // otherwise, wait for the animation start callback from the activity options
+                    // above
+                    MAIN_EXECUTOR.post(() -> {
+                        notifyTaskLaunchFailed(TAG);
+                        callback.accept(false);
+                    });
+                }
+            });
+        } else {
+            callback.accept(false);
         }
     }
 
@@ -582,6 +597,11 @@
     }
 
     private boolean showTaskMenu() {
+        if (getRecentsView().mActivity.isInState(OVERVIEW_SPLIT_SELECT)) {
+            // Don't show menu when selecting second split screen app
+            return true;
+        }
+
         if (!getRecentsView().isClearAllHidden()) {
             getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
         } else {
@@ -610,6 +630,10 @@
         }
     }
 
+    public int getThumbnailTopMargin() {
+        return (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
+    }
+
     public void setOrientationState(RecentsOrientedState orientationState) {
         PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
         boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
@@ -617,12 +641,11 @@
         int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
         int taskIconMargin = (int) getResources().getDimension(R.dimen.task_icon_top_margin);
         int taskIconHeight = (int) getResources().getDimension(R.dimen.task_thumbnail_icon_size);
-        int iconTopMargin = taskIconMargin - taskIconHeight + thumbnailPadding;
         LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
         switch (orientationHandler.getRotation()) {
             case ROTATION_90:
                 iconParams.gravity = (isRtl ? START : END) | CENTER_VERTICAL;
-                iconParams.rightMargin = -thumbnailPadding;
+                iconParams.rightMargin = -taskIconHeight - taskIconMargin / 2;
                 iconParams.leftMargin = 0;
                 iconParams.topMargin = snapshotParams.topMargin / 2;
                 break;
@@ -630,11 +653,11 @@
                 iconParams.gravity = BOTTOM | CENTER_HORIZONTAL;
                 iconParams.bottomMargin = -thumbnailPadding;
                 iconParams.leftMargin = iconParams.rightMargin = 0;
-                iconParams.topMargin = iconTopMargin;
+                iconParams.topMargin = taskIconMargin;
                 break;
             case ROTATION_270:
                 iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
-                iconParams.leftMargin = -thumbnailPadding;
+                iconParams.leftMargin = -taskIconHeight - taskIconMargin / 2;
                 iconParams.rightMargin = 0;
                 iconParams.topMargin = snapshotParams.topMargin / 2;
                 break;
@@ -642,7 +665,7 @@
             default:
                 iconParams.gravity = TOP | CENTER_HORIZONTAL;
                 iconParams.leftMargin = iconParams.rightMargin = 0;
-                iconParams.topMargin = iconTopMargin;
+                iconParams.topMargin = taskIconMargin;
                 break;
         }
         mIconView.setLayoutParams(iconParams);
@@ -726,9 +749,8 @@
 
     @Override
     public void onRecycle() {
-        mFullscreenTranslationX = mAccumulatedFullscreenTranslationX = mGridTranslationX =
-                mGridTranslationY =
-                        mGridOffsetTranslationX = mBoxTranslationY = mNonRtlVisibleOffset = 0f;
+        mFullscreenTranslationX = mGridTranslationX = mGridTranslationY =
+                mGridOffsetTranslationX = mBoxTranslationY = mNonRtlVisibleOffset = 0f;
         resetViewTransforms();
         // Clear any references to the thumbnail (it will be re-read either from the cache or the
         // system on next bind)
@@ -769,7 +791,7 @@
         }
         if (view != null) {
             mContextualChipWrapper = view;
-            LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
+            LayoutParams layoutParams = new LayoutParams(((View) getParent()).getMeasuredWidth(),
                     LayoutParams.WRAP_CONTENT);
             layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL;
             int expectedChipHeight = getExpectedViewHeight(view);
@@ -840,6 +862,10 @@
         applyScale();
     }
 
+    public float getFullscreenScale() {
+        return mFullscreenScale;
+    }
+
     public void setGridScale(float gridScale) {
         mGridScale = gridScale;
         applyScale();
@@ -897,20 +923,11 @@
         applyTranslationY();
     }
 
-    private void setFullscreenTranslationX(float fullscreenTranslationX) {
+    public void setFullscreenTranslationX(float fullscreenTranslationX) {
         mFullscreenTranslationX = fullscreenTranslationX;
         applyTranslationX();
     }
 
-    public float getFullscreenTranslationX() {
-        return mFullscreenTranslationX;
-    }
-
-    public void setAccumulatedFullscreenTranslationX(float accumulatedFullscreenTranslationX) {
-        mAccumulatedFullscreenTranslationX = accumulatedFullscreenTranslationX;
-        applyTranslationX();
-    }
-
     public void setGridTranslationX(float gridTranslationX) {
         mGridTranslationX = gridTranslationX;
         applyTranslationX();
@@ -941,7 +958,7 @@
     public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
         float scrollAdjustment = 0;
         if (fullscreenEnabled) {
-            scrollAdjustment += mFullscreenTranslationX + mAccumulatedFullscreenTranslationX;
+            scrollAdjustment += mFullscreenTranslationX;
         }
         if (gridEnabled) {
             scrollAdjustment += mGridTranslationX;
@@ -975,7 +992,7 @@
 
     private void applyTranslationX() {
         setTranslationX(mDismissTranslationX + mTaskOffsetTranslationX + mTaskResistanceTranslationX
-                + getFullscreenTrans(mFullscreenTranslationX + mAccumulatedFullscreenTranslationX)
+                + getFullscreenTrans(mFullscreenTranslationX)
                 + getGridTrans(mGridTranslationX + mGridOffsetTranslationX));
     }
 
@@ -990,12 +1007,12 @@
         return Utilities.mapRange(progress, 0, endTranslation);
     }
 
-    public FloatProperty<TaskView> getFillDismissGapTranslationProperty() {
+    public FloatProperty<TaskView> getPrimaryDismissTranslationProperty() {
         return getPagedOrientationHandler().getPrimaryValue(
                 DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
     }
 
-    public FloatProperty<TaskView> getDismissTaskTranslationProperty() {
+    public FloatProperty<TaskView> getSecondaryDissmissTranslationProperty() {
         return getPagedOrientationHandler().getSecondaryValue(
                 DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
     }
@@ -1073,7 +1090,8 @@
                         getContext().getText(R.string.accessibility_close)));
 
         final Context context = getContext();
-        for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) {
+        for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
+                mActivity.getDeviceProfile())) {
             info.addAction(s.createAccessibilityAction(context));
         }
 
@@ -1105,7 +1123,8 @@
             return true;
         }
 
-        for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) {
+        for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
+                mActivity.getDeviceProfile())) {
             if (s.hasHandlerForAction(action)) {
                 s.onClick(this);
                 return true;
@@ -1123,7 +1142,7 @@
         return getRecentsView().mOrientationState.getOrientationHandler();
     }
 
-    public void notifyTaskLaunchFailed(String tag) {
+    private void notifyTaskLaunchFailed(String tag) {
         String msg = "Failed to launch task";
         if (mTask != null) {
             msg += " (task=" + mTask.key.baseIntent + " userId=" + mTask.key.userId + ")";
@@ -1188,7 +1207,8 @@
 
             int expectedWidth;
             int expectedHeight;
-            float thumbnailRatio = mTask != null ? mTask.getVisibleThumbnailRatio() : 0f;
+            float thumbnailRatio = mTask != null ? mTask.getVisibleThumbnailRatio(
+                    TaskView.CLIP_STATUS_AND_NAV_BARS) : 0f;
             if (isRunningTask() || thumbnailRatio == 0f) {
                 expectedWidth = taskWidth;
                 expectedHeight = taskHeight + thumbnailPadding;
@@ -1216,10 +1236,6 @@
             }
             setFullscreenScale(fullscreenScale);
 
-            float widthDiff = params.width * (1 - mFullscreenScale);
-            setFullscreenTranslationX(
-                    getLayoutDirection() == LAYOUT_DIRECTION_RTL ? -widthDiff : widthDiff);
-
             if (params.width != expectedWidth || params.height != expectedHeight) {
                 params.width = expectedWidth;
                 params.height = expectedHeight;
@@ -1227,7 +1243,6 @@
             }
         } else {
             setBoxTranslationY(0);
-            setFullscreenTranslationX(0);
             setFullscreenScale(1);
             if (params.width != ViewGroup.LayoutParams.MATCH_PARENT) {
                 params.width = ViewGroup.LayoutParams.MATCH_PARENT;
@@ -1265,6 +1280,12 @@
         mSnapshotView.setOverlayEnabled(overlayEnabled);
     }
 
+    public void initiateSplitSelect(SplitPositionOption splitPositionOption) {
+        RecentsView rv = getRecentsView();
+        getMenuView().close(false);
+        rv.initiateSplitSelect(this, splitPositionOption);
+    }
+
     /**
      * We update and subsequently draw these in {@link #setFullscreenProgress(float)}.
      */
diff --git a/res/drawable/ic_expand_less.xml b/res/drawable/ic_expand_less.xml
index 8360cee..cc16083 100644
--- a/res/drawable/ic_expand_less.xml
+++ b/res/drawable/ic_expand_less.xml
@@ -18,7 +18,7 @@
     android:height="24dp"
     android:viewportWidth="24"
     android:viewportHeight="24"
-    android:tint="?android:attr/textColorHint">
+    android:tint="?android:attr/textColorSecondary">
     <path
         android:fillColor="#FF000000"
         android:pathData="M18.59,16.41L20,15l-8,-8 -8,8 1.41,1.41L12,9.83"/>
diff --git a/res/drawable/ic_expand_more.xml b/res/drawable/ic_expand_more.xml
index 49e24f6..ecbce7f 100644
--- a/res/drawable/ic_expand_more.xml
+++ b/res/drawable/ic_expand_more.xml
@@ -18,7 +18,7 @@
     android:height="24dp"
     android:viewportWidth="24"
     android:viewportHeight="24"
-    android:tint="?android:attr/textColorHint">
+    android:tint="?android:attr/textColorSecondary">
     <path
         android:fillColor="#FF000000"
         android:pathData="M5.41,7.59L4,9l8,8 8,-8 -1.41,-1.41L12,14.17"/>
diff --git a/res/drawable/ic_gm_close_24.xml b/res/drawable/ic_gm_close_24.xml
new file mode 100644
index 0000000..2c9c932
--- /dev/null
+++ b/res/drawable/ic_gm_close_24.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="?android:attr/textColorTertiary"
+        android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12 19,6.41z"/>
+</vector>
diff --git a/quickstep/res/drawable/ic_split_screen.xml b/res/drawable/ic_split_screen.xml
similarity index 100%
rename from quickstep/res/drawable/ic_split_screen.xml
rename to res/drawable/ic_split_screen.xml
diff --git a/res/drawable/widgets_list_bottom_ripple.xml b/res/drawable/widgets_list_bottom_ripple.xml
new file mode 100644
index 0000000..3a26091
--- /dev/null
+++ b/res/drawable/widgets_list_bottom_ripple.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2021, 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.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners
+                android:topLeftRadius="@dimen/widget_list_content_corner_radius"
+                android:topRightRadius="@dimen/widget_list_content_corner_radius"
+                android:bottomLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+                android:bottomRightRadius="@dimen/widget_list_top_bottom_corner_radius" />
+        </shape>
+    </item>
+    <item android:id="@android:id/background">
+        <shape android:shape="rectangle">
+            <solid android:color="?android:attr/colorBackground" />
+            <corners
+                android:topLeftRadius="@dimen/widget_list_content_corner_radius"
+                android:topRightRadius="@dimen/widget_list_content_corner_radius"
+                android:bottomLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+                android:bottomRightRadius="@dimen/widget_list_top_bottom_corner_radius" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/res/drawable/widgets_list_middle_ripple.xml b/res/drawable/widgets_list_middle_ripple.xml
new file mode 100644
index 0000000..da025d7
--- /dev/null
+++ b/res/drawable/widgets_list_middle_ripple.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2021, 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.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners
+                android:topLeftRadius="@dimen/widget_list_content_corner_radius"
+                android:topRightRadius="@dimen/widget_list_content_corner_radius"
+                android:bottomLeftRadius="@dimen/widget_list_content_corner_radius"
+                android:bottomRightRadius="@dimen/widget_list_content_corner_radius" />
+        </shape>
+    </item>
+
+    <item android:id="@android:id/background">
+        <shape android:shape="rectangle">
+            <solid android:color="?android:attr/colorBackground" />
+            <corners
+                android:topLeftRadius="@dimen/widget_list_content_corner_radius"
+                android:topRightRadius="@dimen/widget_list_content_corner_radius"
+                android:bottomLeftRadius="@dimen/widget_list_content_corner_radius"
+                android:bottomRightRadius="@dimen/widget_list_content_corner_radius" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/res/drawable/widgets_list_top_ripple.xml b/res/drawable/widgets_list_top_ripple.xml
new file mode 100644
index 0000000..6efc3e1
--- /dev/null
+++ b/res/drawable/widgets_list_top_ripple.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2021, 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.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners
+                android:topLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+                android:topRightRadius="@dimen/widget_list_top_bottom_corner_radius"
+                android:bottomLeftRadius="@dimen/widget_list_content_corner_radius"
+                android:bottomRightRadius="@dimen/widget_list_content_corner_radius" />
+        </shape>
+    </item>
+
+    <item android:id="@android:id/background">
+        <shape android:shape="rectangle">
+            <solid android:color="?android:attr/colorBackground" />
+            <corners
+                android:topLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+                android:topRightRadius="@dimen/widget_list_top_bottom_corner_radius"
+                android:bottomLeftRadius="@dimen/widget_list_content_corner_radius"
+                android:bottomRightRadius="@dimen/widget_list_content_corner_radius" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 8ed16c7..24d764e 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -44,7 +44,6 @@
     </com.android.launcher3.allapps.FloatingHeaderView>
 
     <include
-        android:id="@id/search_container_all_apps"
         layout="@layout/search_container_all_apps"/>
 
     <include layout="@layout/all_apps_fast_scroller" />
diff --git a/res/layout/app_widget_resize_frame.xml b/res/layout/app_widget_resize_frame.xml
index dfce946..2e476df 100644
--- a/res/layout/app_widget_resize_frame.xml
+++ b/res/layout/app_widget_resize_frame.xml
@@ -34,6 +34,7 @@
 
         <!-- Left -->
         <ImageView
+            android:id="@+id/widget_resize_left_handle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="left|center_vertical"
@@ -43,6 +44,7 @@
 
         <!-- Top -->
         <ImageView
+            android:id="@+id/widget_resize_top_handle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="top|center_horizontal"
@@ -52,6 +54,7 @@
 
         <!-- Right -->
         <ImageView
+            android:id="@+id/widget_resize_right_handle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="right|center_vertical"
@@ -61,6 +64,7 @@
 
         <!-- Bottom -->
         <ImageView
+            android:id="@+id/widget_resize_bottom_handle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="bottom|center_horizontal"
diff --git a/res/layout/search_container_hotseat.xml b/res/layout/search_container_hotseat.xml
new file mode 100644
index 0000000..8f12ca0
--- /dev/null
+++ b/res/layout/search_container_hotseat.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2021 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.
+-->
+<View
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="0dp" />
\ No newline at end of file
diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml
index 65a49ab..50908a4 100644
--- a/res/layout/widget_cell_content.xml
+++ b/res/layout/widget_cell_content.xml
@@ -36,7 +36,7 @@
         android:gravity="center_horizontal"
         android:singleLine="true"
         android:maxLines="1"
-        android:textColor="?android:attr/textColorSecondary"
+        android:textColor="?android:attr/textColorPrimary"
         android:textSize="14sp" />
 
     <!-- The original dimensions of the widget (can't be the same text as above due to different
@@ -46,7 +46,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:gravity="center_horizontal"
-        android:textColor="?android:attr/textColorSecondary"
+        android:textColor="?android:attr/textColorTertiary"
         android:textSize="14sp"
         android:alpha="0.8" />
 
@@ -55,7 +55,8 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:gravity="center_horizontal"
-        android:textSize="12sp"
+        android:textSize="14sp"
+        android:textColor="?android:attr/textColorTertiary"
         android:maxLines="2"
         android:ellipsize="end"
         android:fadingEdge="horizontal" />
diff --git a/res/layout/widgets_bottom_sheet.xml b/res/layout/widgets_bottom_sheet.xml
index c1b2cbf..d18ba56 100644
--- a/res/layout/widgets_bottom_sheet.xml
+++ b/res/layout/widgets_bottom_sheet.xml
@@ -19,12 +19,17 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:paddingTop="28dp"
+    android:paddingTop="16dp"
     android:background="@drawable/top_round_rect_primary"
     android:elevation="@dimen/deep_shortcuts_elevation"
     android:layout_gravity="bottom"
     android:theme="?attr/widgetsTheme">
-
+    <View
+        android:layout_width="48dp"
+        android:layout_height="2dp"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginBottom="16dp"
+        android:background="?android:attr/textColorSecondary"/>
     <TextView
         style="@style/TextHeadline"
         android:id="@+id/title"
@@ -48,8 +53,8 @@
         android:id="@+id/widgets_table_scroll_view"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginTop="45dp"
-        android:layout_marginBottom="40dp">
+        android:fadeScrollbars="false"
+        android:layout_marginVertical="16dp">
         <include layout="@layout/widgets_table_container"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index 28a8c6f..172284b 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -15,6 +15,7 @@
 -->
 <com.android.launcher3.widget.picker.WidgetsFullSheet
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
@@ -24,9 +25,17 @@
         android:id="@+id/container"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:background="?android:attr/colorPrimary"
+        android:background="?android:attr/colorBackgroundFloating"
         android:elevation="4dp">
 
+        <TextView
+            android:id="@+id/no_widgets_text"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center"
+            android:visibility="gone"
+            tools:text="No widgets available" />
+
         <!-- Fast scroller popup -->
         <TextView
             android:id="@+id/fast_scroller_popup"
@@ -42,5 +51,13 @@
             android:layout_alignParentEnd="true"
             android:layout_alignParentTop="true"
             android:layout_marginEnd="@dimen/fastscroll_end_margin" />
+
+        <com.android.launcher3.widget.picker.WidgetsRecyclerView
+            android:id="@+id/search_widgets_list_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+            android:clipToPadding="false" />
+
     </com.android.launcher3.views.TopRoundedCornerView>
 </com.android.launcher3.widget.picker.WidgetsFullSheet>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet_search_and_recommendations.xml b/res/layout/widgets_full_sheet_search_and_recommendations.xml
index 9a6f922..1219f57 100644
--- a/res/layout/widgets_full_sheet_search_and_recommendations.xml
+++ b/res/layout/widgets_full_sheet_search_and_recommendations.xml
@@ -25,7 +25,7 @@
         android:layout_width="48dp"
         android:layout_height="2dp"
         android:layout_gravity="center_horizontal"
-        android:background="@color/popup_color_primary_dark"/>
+        android:background="?android:attr/textColorSecondary"/>
     <TextView
         android:id="@+id/title"
         android:layout_width="match_parent"
@@ -33,17 +33,7 @@
         android:gravity="center_horizontal"
         android:textSize="24sp"
         android:layout_marginTop="16dp"
+        android:textColor="?android:attr/textColorSecondary"
         android:text="@string/widget_button_text"/>
-    <!-- Disable the search bar because it has not been implemented. -->
-    <EditText
-        android:id="@+id/widgets_search_bar"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        android:layout_marginTop="16dp"
-        android:background="@drawable/bg_widgets_searchbox"
-        android:drawablePadding="8dp"
-        android:drawableStart="@drawable/ic_allapps_search"
-        android:hint="@string/widgets_full_sheet_search_bar_hint"
-        android:padding="12dp" />
+    <include layout="@layout/widgets_search_bar"/>
 </LinearLayout>
diff --git a/res/layout/widgets_list_row_header.xml b/res/layout/widgets_list_row_header.xml
index 041e007..62345b3 100644
--- a/res/layout/widgets_list_row_header.xml
+++ b/res/layout/widgets_list_row_header.xml
@@ -18,8 +18,10 @@
     android:id="@+id/widgets_list_header"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:background="?android:attr/selectableItemBackground"
-    android:paddingVertical="20dp"
+    android:layout_marginHorizontal="8dp"
+    android:background="@drawable/widgets_list_middle_ripple"
+    android:layout_marginBottom="@dimen/widget_list_entry_bottom_margin"
+    android:paddingVertical="@dimen/widget_list_header_view_vertical_padding"
     android:orientation="horizontal">
 
     <ImageView
@@ -52,6 +54,9 @@
             android:id="@+id/app_subtitle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:maxLines="1"
+            android:textColor="?android:attr/textColorTertiary"
             tools:text="m widgets, n shortcuts" />
 
     </LinearLayout>
diff --git a/res/layout/widgets_search_bar.xml b/res/layout/widgets_search_bar.xml
new file mode 100644
index 0000000..cf693bb
--- /dev/null
+++ b/res/layout/widgets_search_bar.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.launcher3.widget.picker.search.LauncherWidgetsSearchBar
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/widgets_search_bar"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:layout_marginTop="16dp"
+    android:background="@drawable/bg_widgets_searchbox">
+
+    <com.android.launcher3.ExtendedEditText
+        android:id="@+id/widgets_search_bar_edit_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="12dp"
+        android:drawablePadding="8dp"
+        android:drawableStart="@drawable/ic_allapps_search"
+        android:background="@null"
+        android:hint="@string/widgets_full_sheet_search_bar_hint"
+        android:maxLines="1"
+        android:layout_weight="1"
+        android:inputType="text"/>
+
+    <ImageButton
+        android:id="@+id/widgets_search_cancel_button"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:src="@drawable/ic_gm_close_24"
+        android:background="?android:selectableItemBackground"
+        android:layout_gravity="center"
+        android:visibility="gone"/>
+</com.android.launcher3.widget.picker.search.LauncherWidgetsSearchBar>
\ No newline at end of file
diff --git a/res/layout/widgets_table_container.xml b/res/layout/widgets_table_container.xml
index c4dfe7e..0b5f0b9 100644
--- a/res/layout/widgets_table_container.xml
+++ b/res/layout/widgets_table_container.xml
@@ -19,4 +19,5 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_marginHorizontal="8dp"
-    android:background="?android:attr/colorPrimaryDark" />
+    android:background="@drawable/widgets_list_middle_ripple"
+    android:layout_marginBottom="@dimen/widget_list_entry_bottom_margin"/>
diff --git a/res/values/config.xml b/res/values/config.xml
index 89415b8..65e2ab3 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -61,7 +61,6 @@
     <!-- Various classes overriden by projects/build flavors. -->
     <string name="folder_name_provider_class" translatable="false"></string>
     <string name="stats_log_manager_class" translatable="false"></string>
-    <string name="app_transition_manager_class" translatable="false"></string>
     <string name="instant_app_resolver_class" translatable="false"></string>
     <string name="main_process_initializer_class" translatable="false"></string>
     <string name="app_launch_tracker_class" translatable="false"></string>
@@ -90,6 +89,7 @@
     <string name="wallpaper_picker_package" translatable="false"></string>
     <string name="calendar_component_name" translatable="false"></string>
     <string name="clock_component_name" translatable="false"></string>
+    <string name="local_colors_extraction_class" translatable="false"></string>
 
     <!-- Accessibility actions -->
     <item type="id" name="action_remove" />
@@ -188,4 +188,8 @@
     </string-array>
 
     <string-array name="filtered_components" ></string-array>
+
+    <!-- Name of the class used to generate colors from the wallpaper colors. Must be implementing the LauncherAppWidgetHostView.ColorGenerator interface. -->
+    <string name="color_generator_class" translatable="false"/>
+
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index da43758..1bace48 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -29,6 +29,8 @@
     <dimen name="dynamic_grid_cell_layout_padding">5.5dp</dimen>
     <dimen name="dynamic_grid_cell_padding_x">8dp</dimen>
 
+    <dimen name="two_panel_home_side_padding">18dp</dimen>
+
     <!-- Hotseat -->
     <dimen name="dynamic_grid_hotseat_top_padding">8dp</dimen>
     <dimen name="dynamic_grid_hotseat_bottom_padding">2dp</dimen>
@@ -108,6 +110,13 @@
     <dimen name="widget_cell_vertical_padding">8dp</dimen>
     <dimen name="widget_cell_horizontal_padding">16dp</dimen>
 
+
+    <dimen name="widget_list_top_bottom_corner_radius">28dp</dimen>
+    <dimen name="widget_list_content_corner_radius">4dp</dimen>
+
+    <dimen name="widget_list_header_view_vertical_padding">20dp</dimen>
+    <dimen name="widget_list_entry_bottom_margin">2dp</dimen>
+
     <dimen name="widget_preview_shadow_blur">0.5dp</dimen>
     <dimen name="widget_preview_key_shadow_distance">1dp</dimen>
     <dimen name="widget_preview_corner_radius">2dp</dimen>
diff --git a/res/values/id.xml b/res/values/id.xml
new file mode 100644
index 0000000..39c49bd
--- /dev/null
+++ b/res/values/id.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2020 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>
+    <item type="id" name="view_type_widgets_list" />
+    <item type="id" name="view_type_widgets_header" />
+    <item type="id" name="view_type_widgets_search_header" />
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 44b5ee7..1eb123b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -38,11 +38,18 @@
     <!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] -->
     <string name="home_screen">Home</string>
 
+    <!-- Options for recent tasks -->
+    <!-- Title for an option to enter split screen mode for a given app -->
+    <string name="recent_task_option_split_screen">Split screen</string>
+    <string translatable="false" name="split_screen_position_top">Pin to top</string>
+    <string translatable="false" name="split_screen_position_left">Pin to left</string>
+    <string translatable="false" name="split_screen_position_right">Pin to right</string>
+
     <!-- Widgets -->
     <!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
-    <string name="long_press_widget_to_add">Touch &amp; hold to pick up a widget.</string>
+    <string name="long_press_widget_to_add">Touch &amp; hold to move a widget.</string>
     <!-- Accessibility spoken hint message in widget picker, which allows user to add a widget. Custom action is the label for additional accessibility actions available in this mode [CHAR_LIMIT=100] -->
-    <string name="long_accessible_way_to_add">Double-tap &amp; hold to pick up a widget or use custom actions.</string>
+    <string name="long_accessible_way_to_add">Double-tap &amp; hold to move a widget or use custom actions.</string>
     <!-- The format string for the dimensions of a widget in the drawer -->
     <!-- There is a special version of this format string for Farsi -->
     <string name="widget_dims_format">%1$d \u00d7 %2$d</string>
@@ -74,12 +81,16 @@
     <!-- Search bar text shown in the popup view showing all available widgets installed on the
          device. [CHAR_LIMIT=50] -->
     <string name="widgets_full_sheet_search_bar_hint">Search</string>
+    <!-- Text shown when there is no widgets shown in the popup view showing all available widgets
+         installed on the device. [CHAR_LIMIT=none] -->
+    <string name="no_widgets_available">No widgets available</string>
+    <!-- Text shown when there are no matching widget search results for user's search query.
+         [CHAR_LIMIT=none] -->
+    <string name="no_search_results">No search results</string>
 
     <!-- All Apps -->
     <!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
     <string name="all_apps_search_bar_hint">Search apps</string>
-    <!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
-    <string name="all_apps_on_device_search_bar_hint">Search this phone and more…</string>
     <!-- Loading apps text. [CHAR_LIMIT=50] -->
     <string name="all_apps_loading_message">Loading apps&#8230;</string>
     <!-- No-search-results text. [CHAR_LIMIT=50] -->
@@ -101,9 +112,9 @@
 
     <!-- Drag and drop -->
     <!-- Message to tell the user to press and hold on a shortcut to add it [CHAR_LIMIT=50] -->
-    <string name="long_press_shortcut_to_add">Touch &amp; hold to pick up a shortcut.</string>
+    <string name="long_press_shortcut_to_add">Touch &amp; hold to move a shortcut.</string>
     <!-- Accessibility spoken hint message in deep shortcut menu, which allows user to add a shortcut. Custom action is the label for additional accessibility actions available in this mode [CHAR_LIMIT=200] -->
-    <string name="long_accessible_way_to_add_shortcut">Double-tap &amp; hold to pick up a shortcut or use custom actions.</string>
+    <string name="long_accessible_way_to_add_shortcut">Double-tap &amp; hold to move a shortcut or use custom actions.</string>
 
     <skip />
     <!-- Error message when user has filled a home screen -->
diff --git a/robolectric_tests/Android.bp b/robolectric_tests/Android.bp
index c738df9..50309b7 100644
--- a/robolectric_tests/Android.bp
+++ b/robolectric_tests/Android.bp
@@ -16,27 +16,31 @@
 // Launcher Robolectric test target.
 //
 //        "robolectric_android-all-stub", not needed, we write our own stubs
+filegroup {
+    name: "launcher3-robolectric-resources",
+    path: "resources",
+    srcs: ["resources/*"],
+}
+
+filegroup {
+    name: "launcher3-robolectric-src",
+    srcs: ["src/**/*.java"],
+}
+
 android_robolectric_test {
     name: "LauncherRoboTests",
     srcs: [
-        "src/**/*.java",
+        ":launcher3-robolectric-src",
+        ":launcher3-test-src-common",
     ],
-    java_resource_dirs: [
-        "resources",
-        "res",
-        "config",
-    ],
+    java_resources: [":launcher3-robolectric-resources"],
     static_libs: [
         "truth-prebuilt",
-        "Launcher3TestCommon",
         "androidx.test.runner",
         "androidx.test.rules",
         "mockito-robolectric-prebuilt",
     ],
-    //robolectric_prebuilt_version: "4.4",
-    libs: [
-        "platform-robolectric-4.4-prebuilt",
-    ],
+    robolectric_prebuilt_version: "4.5.1",
     instrumentation_for: "Launcher3",
 
     test_options: {
diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/resources/robolectric.properties
similarity index 98%
rename from robolectric_tests/config/robolectric.properties
rename to robolectric_tests/resources/robolectric.properties
index 1b170e1..abb6968 100644
--- a/robolectric_tests/config/robolectric.properties
+++ b/robolectric_tests/resources/robolectric.properties
@@ -1,4 +1,4 @@
-sdk=29
+sdk=30
 
 shadows= \
     com.android.launcher3.shadows.LShadowAppPredictionManager \
diff --git a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
index 34cb2ad..4d151f1 100644
--- a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
+++ b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
@@ -54,6 +54,7 @@
  */
 @RunWith(RobolectricTestRunner.class)
 @LooperMode(Mode.PAUSED)
+@org.junit.Ignore
 public class LauncherUIScrollTest {
 
     private Context mTargetContext;
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
index b972c6f..cc36f63 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
@@ -221,6 +221,27 @@
         assertThat(currentList).containsExactlyElementsIn(newList);
     }
 
+    @Test
+    public void headersContentsMix_headerWidgetsModified_shouldInvokeCorrectCallbacks() {
+        // GIVEN the current list has app headers [A, B, E content].
+        ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
+                List.of(mHeaderA, mHeaderB, mContentE));
+        // GIVEN the new list has one of the headers widgets list modified.
+        List<WidgetsListBaseEntry> newList = List.of(
+                new WidgetsListHeaderEntry(
+                        mHeaderA.mPkgItem, mHeaderA.mTitleSectionName,
+                        mHeaderA.mWidgets.subList(0, 1)),
+                mHeaderB, mContentE);
+
+        // WHEN computing the list difference.
+        mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
+
+        // THEN notify "A" has been changed.
+        verify(mAdapter).notifyItemChanged(/* position= */ 0);
+        // THEN the current list contains all elements from the new list.
+        assertThat(currentList).containsExactlyElementsIn(newList);
+    }
+
 
     private WidgetsListHeaderEntry createWidgetsHeaderEntry(String packageName, String appName,
             int numOfWidgets) {
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
index a7c8d92..e1214ff 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
@@ -26,6 +26,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.os.Process;
+import android.os.UserHandle;
 import android.view.LayoutInflater;
 
 import androidx.recyclerview.widget.RecyclerView;
@@ -37,6 +39,7 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
@@ -67,6 +70,7 @@
 
     private WidgetsListAdapter mAdapter;
     private InvariantDeviceProfile mTestProfile;
+    private UserHandle mUserHandle;
     private Context mContext;
 
     @Before
@@ -76,6 +80,7 @@
         mTestProfile = new InvariantDeviceProfile();
         mTestProfile.numRows = 5;
         mTestProfile.numColumns = 5;
+        mUserHandle = Process.myUserHandle();
         mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
                 mIconCache, null, null);
         mAdapter.registerAdapterDataObserver(mListener);
@@ -126,7 +131,8 @@
         mAdapter.setWidgets(generateSampleMap(3));
 
         // WHEN com.google.test.1 header is expanded.
-        mAdapter.onHeaderClicked(/* isExpanded= */ true, TEST_PACKAGE_PLACEHOLDER + 1);
+        mAdapter.onHeaderClicked(/* showWidgets= */ true,
+                new PackageUserKey(TEST_PACKAGE_PLACEHOLDER + 1, mUserHandle));
 
         // THEN the visible entries list becomes:
         // [com.google.test0, com.google.test1, com.google.test1 content, com.google.test2]
@@ -143,7 +149,8 @@
         // GIVEN test com.google.test1 is expanded.
         // Visible entries in the adapter are:
         // [com.google.test0, com.google.test1, com.google.test1 content]
-        mAdapter.onHeaderClicked(/* isExpanded= */ true, TEST_PACKAGE_PLACEHOLDER + 1);
+        mAdapter.onHeaderClicked(/* showWidgets= */ true,
+                new PackageUserKey(TEST_PACKAGE_PLACEHOLDER + 1, mUserHandle));
         Mockito.reset(mListener);
 
         // WHEN the adapter is updated with the same list of apps but com.google.test1 has 2 widgets
@@ -200,6 +207,30 @@
         verify(mListener).onItemRangeRemoved(/* positionStart= */ 3, /* itemCount= */ 1);
     }
 
+    @Test
+    public void setWidgetsOnSearch_expandedApp_shouldResetExpandedApp() {
+        // GIVEN a list of widgets entries:
+        // [com.google.test0, com.google.test0 content,
+        //  com.google.test1, com.google.test1 content,
+        //  com.google.test2, com.google.test2 content]
+        // The visible widgets entries: [com.google.test0, com.google.test1, com.google.test2].
+        ArrayList<WidgetsListBaseEntry> allEntries = generateSampleMap(2);
+        mAdapter.setWidgetsOnSearch(allEntries);
+        // GIVEN com.google.test.1 header is expanded. The visible entries list becomes:
+        // [com.google.test0, com.google.test1, com.google.test1 content, com.google.test2]
+        mAdapter.onHeaderClicked(/* showWidgets= */ true,
+                new PackageUserKey(TEST_PACKAGE_PLACEHOLDER + 1, mUserHandle));
+        Mockito.reset(mListener);
+
+        // WHEN same widget entries are set again.
+        mAdapter.setWidgetsOnSearch(allEntries);
+
+        // THEN expanded app is reset and the visible entries list becomes:
+        // [com.google.test0, com.google.test1, com.google.test2]
+        verify(mListener).onItemRangeChanged(eq(1), eq(1), isNull());
+        verify(mListener).onItemRangeRemoved(/* positionStart= */ 2, /* itemCount= */ 1);
+    }
+
     /**
      * Generates a list of sample widget entries.
      *
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
index 848630e..84a03d5 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -18,7 +18,9 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
 import static org.robolectric.Shadows.shadowOf;
 
 import android.appwidget.AppWidgetProviderInfo;
@@ -26,25 +28,22 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.view.LayoutInflater;
-import android.view.View;
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
-import androidx.annotation.Nullable;
-
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.R;
+import com.android.launcher3.WidgetPreviewLoader;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.ComponentWithLabel;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.testing.TestActivity;
+import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.widget.WidgetCell;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
-import com.android.launcher3.widget.picker.WidgetsListHeaderViewHolderBinder.OnHeaderClickListener;
 
 import org.junit.After;
 import org.junit.Before;
@@ -74,12 +73,15 @@
     // testing.
     private ActivityController<TestActivity> mActivityController;
     private TestActivity mTestActivity;
-    private FakeOnHeaderClickListener mFakeOnHeaderClickListener = new FakeOnHeaderClickListener();
 
     @Mock
     private IconCache mIconCache;
     @Mock
     private DeviceProfile mDeviceProfile;
+    @Mock
+    private WidgetPreviewLoader mWidgetPreviewLoader;
+    @Mock
+    private OnHeaderClickListener mOnHeaderClickListener;
 
     @Before
     public void setUp() {
@@ -98,9 +100,14 @@
             return componentWithLabel.getComponent().getShortClassName();
         }).when(mIconCache).getTitleNoCache(any());
 
-        mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
+        WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
                 LayoutInflater.from(mTestActivity),
-                mFakeOnHeaderClickListener);
+                mWidgetPreviewLoader,
+                mIconCache,
+                /* iconClickListener= */ view -> {},
+                /* iconLongClickListener= */ view -> false);
+        mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
+                LayoutInflater.from(mTestActivity), mOnHeaderClickListener, widgetsListAdapter);
     }
 
     @After
@@ -117,7 +124,7 @@
                 APP_NAME,
                 TEST_PACKAGE,
                 /* numOfWidgets= */ 3);
-        mViewHolderBinder.bindViewHolder(viewHolder, entry);
+        mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
 
         TextView appTitle = widgetsListHeader.findViewById(R.id.app_title);
         TextView appSubtitle = widgetsListHeader.findViewById(R.id.app_subtitle);
@@ -125,6 +132,23 @@
         assertThat(appSubtitle.getText()).isEqualTo("3 widgets");
     }
 
+    @Test
+    public void bindViewHolder_shouldAttachOnHeaderClickListener() {
+        WidgetsListHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
+                new FrameLayout(mTestActivity));
+        WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+        WidgetsListHeaderEntry entry = generateSampleAppHeader(
+                APP_NAME,
+                TEST_PACKAGE,
+                /* numOfWidgets= */ 3);
+
+        mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+        widgetsListHeader.callOnClick();
+
+        verify(mOnHeaderClickListener).onHeaderClicked(eq(true),
+                eq(new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)));
+    }
+
     private WidgetsListHeaderEntry generateSampleAppHeader(String appName, String packageName,
             int numOfWidgets) {
         PackageItemInfo appInfo = new PackageItemInfo(packageName);
@@ -152,22 +176,4 @@
         }
         return widgetItems;
     }
-
-    private void assertWidgetCellWithLabel(View view, String label) {
-        assertThat(view).isInstanceOf(WidgetCell.class);
-        TextView widgetLabel = (TextView) view.findViewById(R.id.widget_name);
-        assertThat(widgetLabel.getText()).isEqualTo(label);
-    }
-
-    private final class FakeOnHeaderClickListener implements OnHeaderClickListener {
-
-        boolean mShowWidgets = false;
-        @Nullable  String mHeaderClickedPackage = null;
-
-        @Override
-        public void onHeaderClicked(boolean showWidgets, String packageName) {
-            mShowWidgets = showWidgets;
-            mHeaderClickedPackage = packageName;
-        }
-    }
 }
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
new file mode 100644
index 0000000..075c58d
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2021 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.widget.picker;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.view.LayoutInflater;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.testing.TestActivity;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public final class WidgetsListSearchHeaderViewHolderBinderTest {
+    private static final String TEST_PACKAGE = "com.google.test";
+    private static final String APP_NAME = "Test app";
+
+    private Context mContext;
+    private WidgetsListSearchHeaderViewHolderBinder mViewHolderBinder;
+    private InvariantDeviceProfile mTestProfile;
+    // Replace ActivityController with ActivityScenario, which is the recommended way for activity
+    // testing.
+    private ActivityController<TestActivity> mActivityController;
+    private TestActivity mTestActivity;
+
+    @Mock
+    private IconCache mIconCache;
+    @Mock
+    private DeviceProfile mDeviceProfile;
+    @Mock
+    private WidgetPreviewLoader mWidgetPreviewLoader;
+    @Mock
+    private OnHeaderClickListener mOnHeaderClickListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mTestProfile = new InvariantDeviceProfile();
+        mTestProfile.numRows = 5;
+        mTestProfile.numColumns = 5;
+
+        mActivityController = Robolectric.buildActivity(TestActivity.class);
+        mTestActivity = mActivityController.setup().get();
+        mTestActivity.setDeviceProfile(mDeviceProfile);
+
+        doAnswer(invocation -> {
+            ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
+            return componentWithLabel.getComponent().getShortClassName();
+        }).when(mIconCache).getTitleNoCache(any());
+
+        WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
+                LayoutInflater.from(mTestActivity),
+                mWidgetPreviewLoader,
+                mIconCache,
+                /* iconClickListener= */ view -> {},
+                /* iconLongClickListener= */ view -> false);
+        mViewHolderBinder = new WidgetsListSearchHeaderViewHolderBinder(
+                LayoutInflater.from(mTestActivity), mOnHeaderClickListener, widgetsListAdapter);
+    }
+
+    @After
+    public void tearDown() {
+        mActivityController.destroy();
+    }
+
+    @Test
+    public void bindViewHolder_appWith3Widgets_shouldShowTheCorrectAppNameAndSubtitle() {
+        WidgetsListSearchHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
+                new FrameLayout(mTestActivity));
+        WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+        WidgetsListSearchHeaderEntry entry = generateSampleSearchHeader(
+                APP_NAME,
+                TEST_PACKAGE,
+                /* numOfWidgets= */ 3);
+        mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+
+        TextView appTitle = widgetsListHeader.findViewById(R.id.app_title);
+        TextView appSubtitle = widgetsListHeader.findViewById(R.id.app_subtitle);
+        assertThat(appTitle.getText()).isEqualTo(APP_NAME);
+        assertThat(appSubtitle.getText())
+                .isEqualTo(".SampleWidget0, .SampleWidget1, .SampleWidget2");
+    }
+
+    @Test
+    public void bindViewHolder_shouldAttachOnHeaderClickListener() {
+        WidgetsListSearchHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
+                new FrameLayout(mTestActivity));
+        WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+        WidgetsListSearchHeaderEntry entry = generateSampleSearchHeader(
+                APP_NAME,
+                TEST_PACKAGE,
+                /* numOfWidgets= */ 3);
+
+        mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+        widgetsListHeader.callOnClick();
+
+        verify(mOnHeaderClickListener).onHeaderClicked(eq(true),
+                eq(new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)));
+    }
+
+    private WidgetsListSearchHeaderEntry generateSampleSearchHeader(String appName,
+            String packageName, int numOfWidgets) {
+        PackageItemInfo appInfo = new PackageItemInfo(packageName);
+        appInfo.title = appName;
+        appInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
+
+        return new WidgetsListSearchHeaderEntry(appInfo,
+                /* titleSectionName= */ "",
+                generateWidgetItems(packageName, numOfWidgets));
+    }
+
+    private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
+        ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
+        ArrayList<WidgetItem> widgetItems = new ArrayList<>();
+        for (int i = 0; i < numOfWidgets; i++) {
+            ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
+            AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
+            widgetInfo.provider = cn;
+            ReflectionHelpers.setField(widgetInfo, "providerInfo",
+                    packageManager.addReceiverIfNotPresent(cn));
+
+            widgetItems.add(new WidgetItem(
+                    LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
+                    mTestProfile, mIconCache));
+        }
+        return widgetItems;
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 8a0cf34..0c6e717 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -106,12 +106,19 @@
             return componentWithLabel.getComponent().getShortClassName();
         }).when(mIconCache).getTitleNoCache(any());
 
+        WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
+                LayoutInflater.from(mTestActivity),
+                mWidgetPreviewLoader,
+                mIconCache,
+                /* iconClickListener= */ view -> {},
+                /* iconLongClickListener= */ view -> false);
         mViewHolderBinder = new WidgetsListTableViewHolderBinder(
                 mContext,
                 LayoutInflater.from(mTestActivity),
                 mOnIconClickListener,
                 mOnLongClickListener,
-                mWidgetPreviewLoader);
+                mWidgetPreviewLoader,
+                widgetsListAdapter);
     }
 
     @After
@@ -127,7 +134,7 @@
                 APP_NAME,
                 TEST_PACKAGE,
                 /* numOfWidgets= */ 3);
-        mViewHolderBinder.bindViewHolder(viewHolder, entry);
+        mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
         shadowOf(getMainLooper()).idle();
 
         // THEN the table container has one row, which contains 3 widgets.
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
new file mode 100644
index 0000000..c2bf1ae
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2021 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.widget.picker.search;
+
+import static android.os.Looper.getMainLooper;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.matches;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
+
+import com.android.launcher3.search.SearchCallback;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+@RunWith(RobolectricTestRunner.class)
+public class SimpleWidgetsSearchAlgorithmTest {
+
+    private SimpleWidgetsSearchAlgorithm mSimpleWidgetsSearchAlgorithm;
+    @Mock
+    private WidgetsPickerSearchPipeline mSearchPipeline;
+    @Mock
+    private SearchCallback<WidgetsListBaseEntry> mSearchCallback;
+    @Captor
+    private ArgumentCaptor<Consumer<List<WidgetsListBaseEntry>>> mConsumerCaptor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mSimpleWidgetsSearchAlgorithm = new SimpleWidgetsSearchAlgorithm(mSearchPipeline);
+    }
+
+    @Test
+    public void doSearch_shouldQueryPipeline() {
+        mSimpleWidgetsSearchAlgorithm.doSearch("abc", mSearchCallback);
+
+        verify(mSearchPipeline).query(eq("abc"), any());
+    }
+
+    @Test
+    public void doSearch_shouldInformSearchCallbackOnQueryResult() {
+        ArrayList<WidgetsListBaseEntry> baseEntries = new ArrayList<>();
+
+        mSimpleWidgetsSearchAlgorithm.doSearch("abc", mSearchCallback);
+
+        verify(mSearchPipeline).query(eq("abc"), mConsumerCaptor.capture());
+        mConsumerCaptor.getValue().accept(baseEntries);
+        shadowOf(getMainLooper()).idle();
+        // Verify SearchCallback#onSearchResult receives a query token along with the search
+        // results. The query token is the original query string concatenated with the query
+        // timestamp.
+        verify(mSearchCallback).onSearchResult(matches("abc\t\\d*"), eq(baseEntries));
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipelineTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipelineTest.java
new file mode 100644
index 0000000..17ededd
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipelineTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2021 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.widget.picker.search;
+
+import static android.os.Looper.getMainLooper;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.UserHandle;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class SimpleWidgetsSearchPipelineTest {
+    @Mock private IconCache mIconCache;
+
+    private InvariantDeviceProfile mTestProfile;
+    private WidgetsListHeaderEntry mCalendarHeaderEntry;
+    private WidgetsListContentEntry mCalendarContentEntry;
+    private WidgetsListHeaderEntry mCameraHeaderEntry;
+    private WidgetsListContentEntry mCameraContentEntry;
+    private WidgetsListHeaderEntry mClockHeaderEntry;
+    private WidgetsListContentEntry mClockContentEntry;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doAnswer(invocation -> {
+            ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
+            return componentWithLabel.getComponent().getShortClassName();
+        }).when(mIconCache).getTitleNoCache(any());
+        mTestProfile = new InvariantDeviceProfile();
+        mTestProfile.numRows = 5;
+        mTestProfile.numColumns = 5;
+        mContext = RuntimeEnvironment.application;
+
+        mCalendarHeaderEntry =
+                createWidgetsHeaderEntry("com.example.android.Calendar", "Calendar", 2);
+        mCalendarContentEntry =
+                createWidgetsContentEntry("com.example.android.Calendar", "Calendar", 2);
+        mCameraHeaderEntry = createWidgetsHeaderEntry("com.example.android.Camera", "Camera", 11);
+        mCameraContentEntry = createWidgetsContentEntry("com.example.android.Camera", "Camera", 11);
+        mClockHeaderEntry = createWidgetsHeaderEntry("com.example.android.Clock", "Clock", 3);
+        mClockContentEntry = createWidgetsContentEntry("com.example.android.Clock", "Clock", 3);
+    }
+
+    @Test
+    public void query_shouldMatchOnAppName() {
+        SimpleWidgetsSearchPipeline pipeline = new SimpleWidgetsSearchPipeline(
+                List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
+                        mCameraContentEntry, mClockHeaderEntry, mClockContentEntry));
+
+        pipeline.query("Ca", results ->
+                assertEquals(results,
+                        List.of(
+                                new WidgetsListSearchHeaderEntry(
+                                        mCalendarHeaderEntry.mPkgItem,
+                                        mCalendarHeaderEntry.mTitleSectionName,
+                                        mCalendarHeaderEntry.mWidgets),
+                                mCalendarContentEntry,
+                                new WidgetsListSearchHeaderEntry(
+                                        mCameraHeaderEntry.mPkgItem,
+                                        mCameraHeaderEntry.mTitleSectionName,
+                                        mCameraHeaderEntry.mWidgets),
+                                mCameraContentEntry)));
+        shadowOf(getMainLooper()).idle();
+    }
+
+    @Test
+    public void query_shouldMatchOnWidgetLabel() {
+        SimpleWidgetsSearchPipeline pipeline = new SimpleWidgetsSearchPipeline(
+                List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
+                        mCameraContentEntry));
+
+        pipeline.query("Widget1", results ->
+                assertEquals(results,
+                        List.of(
+                                new WidgetsListSearchHeaderEntry(
+                                        mCalendarHeaderEntry.mPkgItem,
+                                        mCalendarHeaderEntry.mTitleSectionName,
+                                        mCalendarHeaderEntry.mWidgets.subList(1, 2)),
+                                new WidgetsListContentEntry(
+                                        mCalendarHeaderEntry.mPkgItem,
+                                        mCalendarHeaderEntry.mTitleSectionName,
+                                        mCalendarHeaderEntry.mWidgets.subList(1, 2)),
+                                new WidgetsListSearchHeaderEntry(
+                                        mCameraHeaderEntry.mPkgItem,
+                                        mCameraHeaderEntry.mTitleSectionName,
+                                        mCameraHeaderEntry.mWidgets.subList(1, 3)),
+                                new WidgetsListContentEntry(
+                                        mCameraHeaderEntry.mPkgItem,
+                                        mCameraHeaderEntry.mTitleSectionName,
+                                        mCameraHeaderEntry.mWidgets.subList(1, 3)))));
+        shadowOf(getMainLooper()).idle();
+    }
+
+    private WidgetsListHeaderEntry createWidgetsHeaderEntry(String packageName, String appName,
+            int numOfWidgets) {
+        List<WidgetItem> widgetItems = generateWidgetItems(packageName, numOfWidgets);
+        PackageItemInfo pInfo = createPackageItemInfo(packageName, appName,
+                widgetItems.get(0).user);
+
+        return new WidgetsListHeaderEntry(pInfo, /* titleSectionName= */ "", widgetItems);
+    }
+
+    private WidgetsListContentEntry createWidgetsContentEntry(String packageName, String appName,
+            int numOfWidgets) {
+        List<WidgetItem> widgetItems = generateWidgetItems(packageName, numOfWidgets);
+        PackageItemInfo pInfo = createPackageItemInfo(packageName, appName,
+                widgetItems.get(0).user);
+
+        return new WidgetsListContentEntry(pInfo, /* titleSectionName= */ "", widgetItems);
+    }
+
+    private PackageItemInfo createPackageItemInfo(String packageName, String appName,
+            UserHandle userHandle) {
+        PackageItemInfo pInfo = new PackageItemInfo(packageName);
+        pInfo.title = appName;
+        pInfo.user = userHandle;
+        pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
+        return pInfo;
+    }
+
+    private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
+        ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
+        ArrayList<WidgetItem> widgetItems = new ArrayList<>();
+        for (int i = 0; i < numOfWidgets; i++) {
+            ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
+            AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
+            widgetInfo.provider = cn;
+            ReflectionHelpers.setField(widgetInfo, "providerInfo",
+                    packageManager.addReceiverIfNotPresent(cn));
+
+            WidgetItem widgetItem = new WidgetItem(
+                    LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
+                    mTestProfile, mIconCache);
+            widgetItems.add(widgetItem);
+        }
+        return widgetItems;
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
new file mode 100644
index 0000000..4e6f17c
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2021 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.widget.picker.search;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.ImageButton;
+
+import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+
+@RunWith(RobolectricTestRunner.class)
+public class WidgetsSearchBarControllerTest {
+
+    private WidgetsSearchBarController mController;
+    private Context mContext;
+    private ExtendedEditText mEditText;
+    private ImageButton mCancelButton;
+    @Mock
+    private SearchModeListener mSearchModeListener;
+    @Mock
+    private SearchAlgorithm<WidgetsListBaseEntry> mSearchAlgorithm;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mEditText = new ExtendedEditText(mContext);
+        mCancelButton = new ImageButton(mContext);
+        mController = new WidgetsSearchBarController(
+                mSearchAlgorithm, mEditText, mCancelButton, mSearchModeListener);
+    }
+
+    @Test
+    public void onSearchResult_shouldInformSearchModeListener() {
+        ArrayList<WidgetsListBaseEntry> entries = new ArrayList<>();
+        mController.onSearchResult("abc", entries);
+
+        verify(mSearchModeListener).onSearchResults(entries);
+    }
+
+    @Test
+    public void afterTextChanged_shouldInformSearchModeListenerToEnterSearch() {
+        mEditText.setText("abc");
+
+        verify(mSearchModeListener).enterSearchMode();
+        verifyNoMoreInteractions(mSearchModeListener);
+    }
+
+    @Test
+    public void afterTextChanged_shouldDoSearch() {
+        mEditText.setText("abc");
+
+        verify(mSearchAlgorithm).doSearch(eq("abc"), any());
+    }
+
+    @Test
+    public void afterTextChanged_shouldShowCancelButton() {
+        mEditText.setText("abc");
+
+        assertEquals(mCancelButton.getVisibility(), View.VISIBLE);
+    }
+
+    @Test
+    public void afterTextChanged_empty_shouldInformSearchModeListenerToExitSearch() {
+        mEditText.setText("");
+
+        verify(mSearchModeListener).exitSearchMode();
+        verifyNoMoreInteractions(mSearchModeListener);
+    }
+
+    @Test
+    public void afterTextChanged_empty_shouldCancelSearch() {
+        mEditText.setText("");
+
+        verify(mSearchAlgorithm).cancel(true);
+        verifyNoMoreInteractions(mSearchAlgorithm);
+    }
+
+    @Test
+    public void afterTextChanged_empty_shouldHideCancelButton() {
+        mEditText.setText("");
+
+        assertEquals(mCancelButton.getVisibility(), View.GONE);
+    }
+
+    @Test
+    public void cancelSearch_shouldInformSearchModeListenerToClearResultsAndExitSearch() {
+        mCancelButton.performClick();
+
+        verify(mSearchModeListener).exitSearchMode();
+    }
+
+    @Test
+    public void cancelSearch_shouldCancelSearch() {
+        mCancelButton.performClick();
+
+        verify(mSearchAlgorithm).cancel(true);
+        verifyNoMoreInteractions(mSearchAlgorithm);
+    }
+
+    @Test
+    public void cancelSearch_shouldClearSearchBar() {
+        mCancelButton.performClick();
+
+        assertEquals(mEditText.getText().toString(), "");
+    }
+}
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 95cdbdd..d894bb4 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -98,6 +98,13 @@
     public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
             | TYPE_SNACKBAR | TYPE_WIDGET_RESIZE_FRAME | TYPE_LISTENER;
 
+    // When these types of floating views are open, hide the taskbar hotseat and show the real one.
+    public static final int TYPE_REPLACE_TASKBAR_WITH_HOTSEAT = TYPE_FOLDER | TYPE_ACTION_POPUP;
+
+    // Hide the taskbar when these types of floating views are open.
+    public static final int TYPE_HIDE_TASKBAR = TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGETS_FULL_SHEET
+            | TYPE_ON_BOARD_POPUP;
+
     public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER
             & ~TYPE_ALL_APPS_EDU;
 
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 9d6af9f..8071782 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -20,7 +20,6 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
 
 import androidx.annotation.Nullable;
 
@@ -139,10 +138,10 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        ViewGroup content = (ViewGroup) getChildAt(0);
-        for (int i = 0; i < HANDLE_COUNT; i ++) {
-            mDragHandles[i] = content.getChildAt(i);
-        }
+        mDragHandles[INDEX_LEFT] = findViewById(R.id.widget_resize_left_handle);
+        mDragHandles[INDEX_TOP] = findViewById(R.id.widget_resize_top_handle);
+        mDragHandles[INDEX_RIGHT] = findViewById(R.id.widget_resize_right_handle);
+        mDragHandles[INDEX_BOTTOM] = findViewById(R.id.widget_resize_bottom_handle);
     }
 
     @Override
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 062ab71..f77f7e8 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -279,7 +279,7 @@
     /**
      * Used to set the override visibility state, used only to handle the transition home with the
      * recents animation.
-     * @see QuickstepAppTransitionManagerImpl#createWallpaperOpenRunner
+     * @see QuickstepTransitionManager#createWallpaperOpenRunner
      */
     public void addForceInvisibleFlag(@InvisibilityFlags int flag) {
         mForceInvisible |= flag;
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 5bfde15..e38ab74 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -27,6 +27,7 @@
 import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Process;
 import android.os.StrictMode;
@@ -40,6 +41,7 @@
 import android.view.WindowMetrics;
 import android.widget.Toast;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -52,10 +54,12 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
+import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.WindowBounds;
@@ -76,6 +80,7 @@
     protected boolean mIsSafeModeEnabled;
 
     private Runnable mOnStartCallback;
+    private RunnableList mOnResumeCallbacks = new RunnableList();
 
     private int mThemeRes = R.style.AppTheme;
 
@@ -98,6 +103,16 @@
     }
 
     @Override
+    protected void onResume() {
+        super.onResume();
+        mOnResumeCallbacks.executeAllAndClear();
+    }
+
+    public void addOnResumeCallback(Runnable callback) {
+        mOnResumeCallbacks.add(callback);
+    }
+
+    @Override
     public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) {
         updateTheme();
     }
@@ -149,20 +164,35 @@
         return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight());
     }
 
-    public final Bundle getActivityLaunchOptionsAsBundle(View v) {
-        ActivityOptions activityOptions = getActivityLaunchOptions(v);
-        return activityOptions == null ? null : activityOptions.toBundle();
+    @NonNull
+    public ActivityOptionsWrapper getActivityLaunchOptions(View v) {
+        int left = 0, top = 0;
+        int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
+        if (v instanceof BubbleTextView) {
+            // Launch from center of icon, not entire view
+            Drawable icon = ((BubbleTextView) v).getIcon();
+            if (icon != null) {
+                Rect bounds = icon.getBounds();
+                left = (width - bounds.width()) / 2;
+                top = v.getPaddingTop();
+                width = bounds.width();
+                height = bounds.height();
+            }
+        }
+        ActivityOptions options =
+                ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
+        RunnableList callback = new RunnableList();
+        addOnResumeCallback(callback::executeAllAndDestroy);
+        return new ActivityOptionsWrapper(options, callback);
     }
 
-    public abstract ActivityOptions getActivityLaunchOptions(View v);
-
     public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item) {
         if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) {
             Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
             return false;
         }
 
-        Bundle optsBundle = (v != null) ? getActivityLaunchOptionsAsBundle(v) : null;
+        Bundle optsBundle = (v != null) ? getActivityLaunchOptions(v).toBundle() : null;
         UserHandle user = item == null ? null : item.user;
 
         // Prepare intent
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index e0be6de..2b58fb6 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -50,7 +50,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
-import com.android.launcher3.Launcher.OnResumeCallback;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dot.DotInfo;
@@ -81,7 +80,7 @@
  * because we want to make the bubble taller than the text and TextView's clip is
  * too aggressive.
  */
-public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
+public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
         IconLabelDotView, DraggableView, Reorderable {
 
     private static final int DISPLAY_WORKSPACE = 0;
@@ -431,13 +430,6 @@
         }
     }
 
-    @Override
-    public void onLauncherResume() {
-        // Reset the pressed state of icon that was locked in the press state while activity
-        // was launching
-        setStayPressed(false);
-    }
-
     void clearPressedBackground() {
         setPressed(false);
         setStayPressed(false);
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index b8833cf..cc4bfe8 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -60,6 +60,7 @@
 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.DragPreviewProvider;
@@ -180,6 +181,9 @@
     private final ArrayList<View> mIntersectingViews = new ArrayList<>();
     private final Rect mOccupiedRect = new Rect();
     private final int[] mDirectionVector = new int[2];
+    private final Workspace mWorkspace;
+    private final DeviceProfile mDeviceProfile;
+
     final int[] mPreviousReorderDirection = new int[2];
     private static final int INVALID_DIRECTION = -100;
 
@@ -209,15 +213,15 @@
         setWillNotDraw(false);
         setClipToPadding(false);
         mActivity = ActivityContext.lookupContext(context);
+        mWorkspace = Launcher.cast(mActivity).getWorkspace();
+        mDeviceProfile = mActivity.getDeviceProfile();
 
-        DeviceProfile grid = mActivity.getDeviceProfile();
-
-        mBorderSpacing = grid.cellLayoutBorderSpacingPx;
+        mBorderSpacing = mDeviceProfile.cellLayoutBorderSpacingPx;
         mCellWidth = mCellHeight = -1;
         mFixedCellWidth = mFixedCellHeight = -1;
 
-        mCountX = grid.inv.numColumns;
-        mCountY = grid.inv.numRows;
+        mCountX = mDeviceProfile.inv.numColumns;
+        mCountY = mDeviceProfile.inv.numRows;
         mOccupied =  new GridOccupancy(mCountX, mCountY);
         mTmpOccupied = new GridOccupancy(mCountX, mCountY);
 
@@ -234,7 +238,7 @@
         mBackground.setCallback(this);
         mBackground.setAlpha(0);
 
-        mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * grid.iconSizePx);
+        mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * mDeviceProfile.iconSizePx);
 
         // Initialize the data structures used for the drag visualization.
         mEaseOutInterpolator = Interpolators.DEACCEL_2_5; // Quint ease out
@@ -961,15 +965,18 @@
         final int oldDragCellX = mDragCell[0];
         final int oldDragCellY = mDragCell[1];
 
-        if (outlineProvider == null || outlineProvider.generatedDragOutline == null) {
-            return;
-        }
-
-        Bitmap dragOutline = outlineProvider.generatedDragOutline;
         if (cellX != oldDragCellX || cellY != oldDragCellY) {
             mDragCell[0] = cellX;
             mDragCell[1] = cellY;
 
+            applyColorExtraction(dragObject, mDragCell, spanX, spanY);
+
+            if (outlineProvider == null || outlineProvider.generatedDragOutline == null) {
+                return;
+            }
+
+            Bitmap dragOutline = outlineProvider.generatedDragOutline;
+
             final int oldIndex = mDragOutlineCurrent;
             mDragOutlineAnims[oldIndex].animateOut();
             mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
@@ -1011,6 +1018,20 @@
         }
     }
 
+    /** Applies the local color extraction to a dragging widget object. */
+    private void applyColorExtraction(DropTarget.DragObject dragObject, int[] targetCell, int spanX,
+            int spanY) {
+        // Apply local extracted color if the DragView is an AppWidgetHostViewDrawable.
+        Drawable drawable = dragObject.dragView.getDrawable();
+        if (drawable instanceof AppWidgetHostViewDrawable) {
+            int screenId = mWorkspace.getIdForScreen(this);
+            int pageId = mWorkspace.getPageIndexForScreenId(screenId);
+            AppWidgetHostViewDrawable hostViewDrawable = ((AppWidgetHostViewDrawable) drawable);
+            cellToRect(targetCell[0], targetCell[1], spanX, spanY, mTempRect);
+            hostViewDrawable.getAppWidgetHostView().handleDrag(mTempRect, pageId);
+        }
+    }
+
     @SuppressLint("StringFormatMatches")
     public String getItemMoveDescription(int cellX, int cellY) {
         if (mContainerType == HOTSEAT) {
@@ -2076,7 +2097,7 @@
     private void commitTempPlacement() {
         mTmpOccupied.copyTo(mOccupied);
 
-        int screenId = Launcher.cast(mActivity).getWorkspace().getIdForScreen(this);
+        int screenId = mWorkspace.getIdForScreen(this);
         int container = Favorites.CONTAINER_DESKTOP;
 
         if (mContainerType == HOTSEAT) {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index f2dd60e..02571a0 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -167,6 +167,8 @@
      // Taskbar
     public boolean isTaskbarPresent;
     public int taskbarSize;
+    // How much of the bottom inset is due to Taskbar rather than other system elements.
+    public int nonOverlappingTaskbarInset;
 
     DeviceProfile(Context context, InvariantDeviceProfile inv, Info info,
             Point minSize, Point maxSize, int width, int height, boolean isLandscape,
@@ -221,7 +223,7 @@
             WindowInsets windowInsets = DisplayController.INSTANCE.get(context).getHolder(mInfo.id)
                     .getDisplayContext().getSystemService(WindowManager.class)
                     .getCurrentWindowMetrics().getWindowInsets();
-            int nonOverlappingTaskbarInset =
+            nonOverlappingTaskbarInset =
                     taskbarSize - windowInsets.getSystemWindowInsetBottom();
             if (nonOverlappingTaskbarInset > 0) {
                 nonFinalAvailableHeightPx -= nonOverlappingTaskbarInset;
@@ -249,7 +251,12 @@
         int cellLayoutPadding = isScalableGrid
                 ? 0
                 : res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
-        if (isLandscape) {
+
+        if (FeatureFlags.ENABLE_TWO_PANEL_HOME.get() && isTablet) {
+            cellLayoutPaddingLeftRightPx =
+                    res.getDimensionPixelSize(R.dimen.two_panel_home_side_padding);
+            cellLayoutBottomPaddingPx = 0;
+        } else if (isLandscape) {
             cellLayoutPaddingLeftRightPx = 0;
             cellLayoutBottomPaddingPx = cellLayoutPadding;
         } else {
@@ -472,7 +479,7 @@
         final boolean isVerticalLayout = isVerticalBarLayout();
         float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
         iconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mInfo.metrics, scale));
-        iconTextSizePx = pxFromDp(inv.iconTextSize, mInfo.metrics, scale);
+        iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, mInfo.metrics) * scale);
         iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
 
         setCellLayoutBorderSpacing((int) (cellLayoutBorderSpacingOriginalPx * scale));
@@ -658,6 +665,10 @@
                         - (2 * inv.numRows * cellHeightPx) - hotseatVerticalPadding);
                 padding.set(availablePaddingX / 2, edgeMarginPx + availablePaddingY / 2,
                         availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
+
+                if (FeatureFlags.ENABLE_TWO_PANEL_HOME.get()) {
+                    padding.set(0, padding.top, 0, padding.bottom);
+                }
             } else {
                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
                 padding.set(desiredWorkspaceLeftRightMarginPx,
@@ -708,10 +719,11 @@
                     mInsets.top + availableHeightPx);
         } else {
             // Folders should only appear below the drop target bar and above the hotseat
+            int hotseatTop = isTaskbarPresent ? taskbarSize : hotseatBarSizePx;
             return new Rect(mInsets.left + edgeMarginPx,
                     mInsets.top + dropTargetBarSizePx + edgeMarginPx,
                     mInsets.left + availableWidthPx - edgeMarginPx,
-                    mInsets.top + availableHeightPx - hotseatBarSizePx
+                    mInsets.top + availableHeightPx - hotseatTop
                             - workspacePageIndicatorHeight - edgeMarginPx);
         }
     }
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 02c6162..c79dabe 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -131,10 +131,9 @@
     public void reset() {
         if (!TextUtils.isEmpty(getText())) {
             setText("");
-        } else {
-            if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
-                return;
-            }
+        }
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            return;
         }
         if (isFocused()) {
             View nextFocus = focusSearch(View.FOCUS_DOWN);
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index b2112ad..0e9de45 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -20,6 +20,7 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.Gravity;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
@@ -29,6 +30,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.MultiValueAlpha;
 
 import java.util.function.Consumer;
 
@@ -37,6 +39,13 @@
  */
 public class Hotseat extends CellLayout implements Insettable {
 
+    private static final int ALPHA_INDEX_STATE = 0;
+    private static final int ALPHA_INDEX_REPLACE_TASKBAR = 1;
+    private static final int NUM_ALPHA_CHANNELS = 2;
+
+    // Ratio of empty space, qsb should take up to appear visually centered.
+    public static final float QSB_CENTER_FACTOR = .325f;
+
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mHasVerticalHotseat;
     private Workspace mWorkspace;
@@ -44,6 +53,10 @@
     @Nullable
     private Consumer<Boolean> mOnVisibilityAggregatedCallback;
 
+    private final MultiValueAlpha mMultiValueAlpha;
+    private final View mQsb;
+    private final int mQsbHeight;
+
     public Hotseat(Context context) {
         this(context, null);
     }
@@ -54,6 +67,12 @@
 
     public Hotseat(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        mMultiValueAlpha = new MultiValueAlpha(this, NUM_ALPHA_CHANNELS, MultiValueAlpha.Mode.MAX);
+        mMultiValueAlpha.setUpdateVisibility(true);
+
+        mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
+        mQsbHeight = mQsb.getLayoutParams().height;
+        addView(mQsb);
     }
 
     /**
@@ -88,6 +107,7 @@
         DeviceProfile grid = mActivity.getDeviceProfile();
 
         if (grid.isVerticalBarLayout()) {
+            mQsb.setVisibility(View.GONE);
             lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
             if (grid.isSeascape()) {
                 lp.gravity = Gravity.LEFT;
@@ -97,12 +117,15 @@
                 lp.width = grid.hotseatBarSizePx + insets.right;
             }
         } else {
+            mQsb.setVisibility(View.VISIBLE);
             lp.gravity = Gravity.BOTTOM;
             lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
-            lp.height = grid.isTaskbarPresent
-                    ? grid.taskbarSize
-                    : grid.hotseatBarSizePx + insets.bottom;
+            lp.height = (grid.isTaskbarPresent
+                        ? grid.workspacePadding.bottom
+                        : grid.hotseatBarSizePx)
+                    + insets.bottom;
         }
+
         if (!grid.isTaskbarPresent) {
             // When taskbar is present, we set the padding separately to ensure a seamless visual
             // handoff between taskbar and hotseat during drag and drop.
@@ -168,10 +191,53 @@
         //Does nothing
     }
 
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        int width = getShortcutsAndWidgets().getMeasuredWidth();
+        mQsb.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(mQsbHeight, MeasureSpec.EXACTLY));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+
+        int qsbWidth = mQsb.getMeasuredWidth();
+        int left = (r - l - qsbWidth) / 2;
+        int right = left + qsbWidth;
+
+        DeviceProfile dp = mActivity.getDeviceProfile();
+        int freeSpace = dp.isTaskbarPresent
+                ? dp.workspacePadding.bottom
+                : dp.hotseatBarSizePx - dp.hotseatCellHeightPx - mQsbHeight;
+        int bottom = b - t
+                - (int) (freeSpace * QSB_CENTER_FACTOR)
+                - dp.getInsets().bottom;
+        int top = bottom - mQsbHeight;
+        mQsb.layout(left, top, right, bottom);
+    }
+
     /**
      * Returns the first View for which the given itemOperator returns true, or null.
      */
     public View getFirstItemMatch(Workspace.ItemOperator itemOperator) {
         return mWorkspace.getFirstMatch(new CellLayout[] { this }, itemOperator);
     }
+
+    public MultiValueAlpha.AlphaProperty getStateAlpha() {
+        return mMultiValueAlpha.getProperty(ALPHA_INDEX_STATE);
+    }
+
+    public MultiValueAlpha.AlphaProperty getReplaceTaskbarAlpha() {
+        return mMultiValueAlpha.getProperty(ALPHA_INDEX_REPLACE_TASKBAR);
+    }
+
+    /**
+     * Returns the QSB inside hotseat
+     */
+    public View getQsb() {
+        return mQsb;
+    }
 }
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index bb60557..348d9ee 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.Utilities.getDevicePrefs;
 import static com.android.launcher3.Utilities.getPointString;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TWO_PANEL_HOME;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 
@@ -235,6 +236,9 @@
     }
 
     public static String getCurrentGridName(Context context) {
+        if (ENABLE_TWO_PANEL_HOME.get()) {
+            return ENABLE_TWO_PANEL_HOME.key;
+        }
         if (ENABLE_FOUR_COLUMNS.get()) {
             return ENABLE_FOUR_COLUMNS.key;
         }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 80c80d7..c57f621 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -61,7 +61,6 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
-import android.app.ActivityOptions;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
 import android.content.ActivityNotFoundException;
@@ -114,7 +113,6 @@
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DiscoveryBounce;
-import com.android.launcher3.allapps.search.LiveSearchManager;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -276,11 +274,8 @@
 
     private static final int THEME_CROSS_FADE_ANIMATION_DURATION = 375;
 
-    private LauncherAppTransitionManager mAppTransitionManager;
     private Configuration mOldConfig;
 
-    private LiveSearchManager mLiveSearchManager;
-
     @Thunk
     Workspace mWorkspace;
     @Thunk
@@ -312,8 +307,6 @@
     @Thunk
     boolean mWorkspaceLoading = true;
 
-    private ArrayList<OnResumeCallback> mOnResumeCallbacks = new ArrayList<>();
-
     // Used to notify when an activity launch has been deferred because launcher is not yet resumed
     // TODO: See if we can remove this later
     private Runnable mOnDeferredActivityLaunchCallback;
@@ -405,8 +398,6 @@
         mAllAppsController = new AllAppsTransitionController(this);
         mStateManager = new StateManager<>(this, NORMAL);
 
-        mLiveSearchManager = new LiveSearchManager(this);
-
         mOnboardingPrefs = createOnboardingPrefs(mSharedPrefs);
 
         mAppWidgetManager = new WidgetManagerHelper(this);
@@ -419,9 +410,6 @@
         crossFadeWithPreviousAppearance();
         mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
 
-        mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);
-        mAppTransitionManager.registerRemoteAnimations();
-
         boolean internalStateHandled = ACTIVITY_TRACKER.handleCreate(this);
         if (internalStateHandled) {
             if (savedInstanceState != null) {
@@ -497,10 +485,6 @@
         }
     }
 
-    public LiveSearchManager getLiveSearchManager() {
-        return mLiveSearchManager;
-    }
-
     protected LauncherOverlayManager getDefaultOverlay() {
         return new LauncherOverlayManager() { };
     }
@@ -1090,15 +1074,6 @@
                 TraceHelper.FLAG_UI_EVENT);
         super.onResume();
 
-        if (!mOnResumeCallbacks.isEmpty()) {
-            final ArrayList<OnResumeCallback> resumeCallbacks = new ArrayList<>(mOnResumeCallbacks);
-            mOnResumeCallbacks.clear();
-            for (int i = resumeCallbacks.size() - 1; i >= 0; i--) {
-                resumeCallbacks.get(i).onLauncherResume();
-            }
-            resumeCallbacks.clear();
-        }
-
         if (mDeferOverlayCallbacks) {
             scheduleDeferredCheck();
         } else {
@@ -1609,10 +1584,7 @@
         LauncherAppState.getIDP(this).removeOnChangeListener(this);
 
         mOverlayManager.onActivityDestroyed(this);
-        mAppTransitionManager.unregisterRemoteAnimations();
-        mAppTransitionManager.unregisterRemoteTransitions();
         mUserChangedCallbackCloseable.close();
-        mLiveSearchManager.stop();
     }
 
     public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1936,16 +1908,6 @@
 
     @TargetApi(Build.VERSION_CODES.M)
     @Override
-    public ActivityOptions getActivityLaunchOptions(View v) {
-        return mAppTransitionManager.getActivityLaunchOptions(this, v);
-    }
-
-    public LauncherAppTransitionManager getAppTransitionManager() {
-        return mAppTransitionManager;
-    }
-
-    @TargetApi(Build.VERSION_CODES.M)
-    @Override
     protected boolean onErrorStartingShortcut(Intent intent, ItemInfo info) {
         // Due to legacy reasons, direct call shortcuts require Launchers to have the
         // corresponding permission. Show the appropriate permission prompt if that
@@ -1994,7 +1956,7 @@
             // state when we return to launcher.
             BubbleTextView btv = (BubbleTextView) v;
             btv.setStayPressed(true);
-            addOnResumeCallback(btv);
+            addOnResumeCallback(() -> btv.setStayPressed(false));
         }
         return success;
     }
@@ -2038,10 +2000,6 @@
         return result;
     }
 
-    public void addOnResumeCallback(OnResumeCallback callback) {
-        mOnResumeCallbacks.add(callback);
-    }
-
     /**
      * Persistant callback which notifies when an activity launch is deferred because the activity
      * was not yet resumed.
@@ -2815,15 +2773,6 @@
         return (T) activityContext;
     }
 
-
-    /**
-     * Callback for listening for onResume
-     */
-    public interface OnResumeCallback {
-
-        void onLauncherResume();
-    }
-
     /**
      * Cross-fades the launcher's updated appearance with its previous appearance.
      *
@@ -2866,6 +2815,10 @@
         return false;
     }
 
+    public boolean supportsAdaptiveIconAnimation(View clickedView) {
+        return false;
+    }
+
     public DragOptions getDefaultWorkspaceDragOptions() {
         return new DragOptions();
     }
diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java
deleted file mode 100644
index 0fa441a..0000000
--- a/src/com/android/launcher3/LauncherAppTransitionManager.java
+++ /dev/null
@@ -1,84 +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;
-
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-
-import com.android.launcher3.util.ResourceBasedOverride;
-
-/**
- * Manages the opening and closing app transitions from Launcher.
- */
-public class LauncherAppTransitionManager implements ResourceBasedOverride {
-
-    public static LauncherAppTransitionManager newInstance(Context context) {
-        return Overrides.getObject(LauncherAppTransitionManager.class,
-                context, R.string.app_transition_manager_class);
-    }
-
-    public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
-        int left = 0, top = 0;
-        int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
-        if (v instanceof BubbleTextView) {
-            // Launch from center of icon, not entire view
-            Drawable icon = ((BubbleTextView) v).getIcon();
-            if (icon != null) {
-                Rect bounds = icon.getBounds();
-                left = (width - bounds.width()) / 2;
-                top = v.getPaddingTop();
-                width = bounds.width();
-                height = bounds.height();
-            }
-        }
-        return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
-    }
-
-    public boolean supportsAdaptiveIconAnimation(View clickedView) {
-        return false;
-    }
-
-    /**
-     * Registers remote animations for certain system transitions.
-     */
-    public void registerRemoteAnimations() {
-        // Do nothing
-    }
-
-    /**
-     * Unregisters all remote animations.
-     */
-    public void unregisterRemoteAnimations() {
-        // Do nothing
-    }
-
-    /**
-     * Registers remote transitions for certain system transitions.
-     */
-    public void registerRemoteTransitions() {
-        // Do nothing
-    }
-
-    /**
-     * Unregisters all remote transitions.
-     */
-    public void unregisterRemoteTransitions() {
-        // Do nothing
-    }
-}
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 76c4518..83ddf64 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -41,8 +41,15 @@
     }
 
     private void handleSystemWindowInsets(Rect insets) {
-        // Update device profile before notifying th children.
-        mActivity.getDeviceProfile().updateInsets(insets);
+        DeviceProfile dp = mActivity.getDeviceProfile();
+
+        // Taskbar provides insets, but we don't want that for most Launcher elements so remove it.
+        mTempRect.set(insets);
+        insets = mTempRect;
+        insets.bottom = Math.max(0, insets.bottom - dp.nonOverlappingTaskbarInset);
+
+        // Update device profile before notifying the children.
+        dp.updateInsets(insets);
         boolean resetState = !insets.equals(mInsets);
         setInsets(insets);
 
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 21c40ef..e9a3495 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_MODAL_TASK_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.OVERVIEW_SPLIT_SELECT_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
@@ -29,7 +30,6 @@
 import android.content.Context;
 import android.view.animation.Interpolator;
 
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.states.HintState;
@@ -51,19 +51,13 @@
      */
     public static final int NONE = 0;
     public static final int HOTSEAT_ICONS = 1 << 0;
-    public static final int HOTSEAT_SEARCH_BOX = 1 << 1;
-    public static final int ALL_APPS_HEADER = 1 << 2;
-    public static final int ALL_APPS_HEADER_EXTRA = 1 << 3; // e.g. app predictions
-    public static final int ALL_APPS_CONTENT = 1 << 4;
-    public static final int VERTICAL_SWIPE_INDICATOR = 1 << 5;
-    public static final int OVERVIEW_ACTIONS = 1 << 6;
-    public static final int TASKBAR = 1 << 7;
-    public static final int CLEAR_ALL_BUTTON = 1 << 8;
-    public static final int WORKSPACE_PAGE_INDICATOR = 1 << 9;
-
-    /** Mask of all the items that are contained in the apps view. */
-    public static final int APPS_VIEW_ITEM_MASK =
-            HOTSEAT_SEARCH_BOX | ALL_APPS_HEADER | ALL_APPS_HEADER_EXTRA | ALL_APPS_CONTENT;
+    public static final int ALL_APPS_CONTENT = 1 << 1;
+    public static final int VERTICAL_SWIPE_INDICATOR = 1 << 2;
+    public static final int OVERVIEW_ACTIONS = 1 << 3;
+    public static final int TASKBAR = 1 << 4;
+    public static final int CLEAR_ALL_BUTTON = 1 << 5;
+    public static final int WORKSPACE_PAGE_INDICATOR = 1 << 6;
+    public static final int SPLIT_PLACHOLDER_VIEW = 1 << 7;
 
     // Flag indicating workspace has multiple pages visible.
     public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0);
@@ -126,6 +120,8 @@
             OverviewState.newSwitchState(QUICK_SWITCH_STATE_ORDINAL);
     public static final LauncherState BACKGROUND_APP =
             OverviewState.newBackgroundState(BACKGROUND_APP_STATE_ORDINAL);
+    public static final LauncherState OVERVIEW_SPLIT_SELECT =
+            OverviewState.newSplitSelectState(OVERVIEW_SPLIT_SELECT_ORDINAL);
 
     public final int ordinal;
 
@@ -191,9 +187,6 @@
     public int getVisibleElements(Launcher launcher) {
         DeviceProfile deviceProfile = launcher.getDeviceProfile();
         int flags = WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR | TASKBAR;
-        if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get() && !deviceProfile.isVerticalBarLayout()) {
-            flags |= HOTSEAT_SEARCH_BOX;
-        }
         if (!deviceProfile.isTaskbarPresent) {
             flags |= HOTSEAT_ICONS;
         }
@@ -234,10 +227,11 @@
     }
 
     /**
-     * For this state, whether tasks should layout as a grid rather than a list.
+     * For this state, how much additional vertical translation there should be for each of the
+     * child TaskViews.
      */
-    public boolean displayOverviewTasksAsGrid(Launcher launcher) {
-        return false;
+    public float getOverviewSecondaryTranslation(Launcher launcher) {
+        return 0;
     }
 
     /**
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 50f1e44..b084eb1 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -282,7 +282,32 @@
     private int validateNewPage(int newPage) {
         newPage = ensureWithinScrollBounds(newPage);
         // Ensure that it is clamped by the actual set of children in all cases
-        return Utilities.boundToRange(newPage, 0, getPageCount() - 1);
+        newPage = Utilities.boundToRange(newPage, 0, getPageCount() - 1);
+
+        if (getPanelCount() > 1) {
+            // Always return left panel as new page
+            newPage = getLeftmostVisiblePageForIndex(newPage);
+        }
+        return newPage;
+    }
+
+    private int getLeftmostVisiblePageForIndex(int pageIndex) {
+        int panelCount = getPanelCount();
+        return (pageIndex / panelCount) * panelCount;
+    }
+
+    /**
+     * Returns the number of pages that are shown at the same time.
+     */
+    protected int getPanelCount() {
+        return 1;
+    }
+
+    /**
+     * Returns true if the view is on one of the current pages, false otherwise.
+     */
+    public boolean isVisible(View child) {
+        return getLeftmostVisiblePageForIndex(indexOfChild(child)) == mCurrentPage;
     }
 
     /**
@@ -548,6 +573,10 @@
         super.forceLayout();
     }
 
+    private int getPageWidthSize(int widthSize) {
+        return (widthSize - mInsets.left - mInsets.right) / getPanelCount();
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         if (getChildCount() == 0) {
@@ -578,7 +607,7 @@
         if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
 
         int myWidthSpec = MeasureSpec.makeMeasureSpec(
-                widthSize - mInsets.left - mInsets.right, MeasureSpec.EXACTLY);
+                getPageWidthSize(widthSize), MeasureSpec.EXACTLY);
         int myHeightSpec = MeasureSpec.makeMeasureSpec(
                 heightSize - mInsets.top - mInsets.bottom, MeasureSpec.EXACTLY);
 
@@ -672,9 +701,11 @@
 
                 // In case the pages are of different width, align the page to left or right edge
                 // based on the orientation.
+                // In case we have multiple panels on the screen, scrollOffsetEnd is the scroll
+                // needed for the whole visible area, so we have to divide it by panelCount.
                 final int pageScroll = mIsRtl
-                    ? (childStart - scrollOffsetStart)
-                    : Math.max(0, childPrimaryEnd  - scrollOffsetEnd);
+                        ? (childStart - scrollOffsetStart)
+                        : Math.max(0, childPrimaryEnd - scrollOffsetEnd / getPanelCount());
                 if (outPageScrolls[i] != pageScroll) {
                     pageScrollChanged = true;
                     outPageScrolls[i] = pageScroll;
@@ -682,6 +713,19 @@
                 childStart += primaryDimension + mPageSpacing + getChildGap();
             }
         }
+
+        int panelCount = getPanelCount();
+        if (panelCount > 1) {
+            for (int i = 0; i < childCount; i++) {
+                // In case we have multiple panels, always use left panel's page scroll for all
+                // panels on the screen.
+                int adjustedScroll = outPageScrolls[getLeftmostVisiblePageForIndex(i)];
+                if (outPageScrolls[i] != adjustedScroll) {
+                    outPageScrolls[i] = adjustedScroll;
+                    pageScrollChanged = true;
+                }
+            }
+        }
         return pageScrollChanged;
     }
 
@@ -794,14 +838,16 @@
         }
         if (direction == View.FOCUS_LEFT) {
             if (getCurrentPage() > 0) {
-                snapToPage(getCurrentPage() - 1);
-                getChildAt(getCurrentPage() - 1).requestFocus(direction);
+                int nextPage = validateNewPage(getCurrentPage() - 1);
+                snapToPage(nextPage);
+                getChildAt(nextPage).requestFocus(direction);
                 return true;
             }
         } else if (direction == View.FOCUS_RIGHT) {
             if (getCurrentPage() < getPageCount() - 1) {
-                snapToPage(getCurrentPage() + 1);
-                getChildAt(getCurrentPage() + 1).requestFocus(direction);
+                int nextPage = validateNewPage(getCurrentPage() + 1);
+                snapToPage(nextPage);
+                getChildAt(nextPage).requestFocus(direction);
                 return true;
             }
         }
@@ -820,11 +866,13 @@
         }
         if (direction == View.FOCUS_LEFT) {
             if (mCurrentPage > 0) {
-                getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode);
+                int nextPage = validateNewPage(mCurrentPage - 1);
+                getPageAt(nextPage).addFocusables(views, direction, focusableMode);
             }
-        } else if (direction == View.FOCUS_RIGHT){
+        } else if (direction == View.FOCUS_RIGHT) {
             if (mCurrentPage < getPageCount() - 1) {
-                getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode);
+                int nextPage = validateNewPage(mCurrentPage + 1);
+                getPageAt(nextPage).addFocusables(views, direction, focusableMode);
             }
         }
     }
@@ -1255,12 +1303,14 @@
 
                     if (((isSignificantMove && !isDeltaLeft && !isFling) ||
                             (isFling && !isVelocityLeft)) && mCurrentPage > 0) {
-                        finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
+                        finalPage = returnToOriginalPage
+                                ? mCurrentPage : mCurrentPage - getPanelCount();
                         snapToPageWithVelocity(finalPage, velocity);
                     } else if (((isSignificantMove && isDeltaLeft && !isFling) ||
                             (isFling && isVelocityLeft)) &&
                             mCurrentPage < getChildCount() - 1) {
-                        finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
+                        finalPage = returnToOriginalPage
+                                ? mCurrentPage : mCurrentPage + getPanelCount();
                         snapToPageWithVelocity(finalPage, velocity);
                     } else {
                         snapToDestination();
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 4fd87cb..8bc5ad0 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -33,7 +33,6 @@
 import android.view.View;
 import android.widget.Toast;
 
-import com.android.launcher3.Launcher.OnResumeCallback;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.logging.FileLog;
@@ -228,7 +227,7 @@
             DeferredOnComplete deferred = (DeferredOnComplete) d.dragSource;
             if (target != null) {
                 deferred.mPackageName = target.getPackageName();
-                mLauncher.addOnResumeCallback(deferred);
+                mLauncher.addOnResumeCallback(deferred::onLauncherResume);
             } else {
                 deferred.sendFailure();
             }
@@ -311,7 +310,7 @@
      * A wrapper around {@link DragSource} which delays the {@link #onDropCompleted} action until
      * {@link #onLauncherResume}
      */
-    private class DeferredOnComplete implements DragSource, OnResumeCallback {
+    private class DeferredOnComplete implements DragSource {
 
         private final DragSource mOriginal;
         private final Context mContext;
@@ -330,7 +329,6 @@
             mDragObject = d;
         }
 
-        @Override
         public void onLauncherResume() {
             // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
             if (new PackageManagerHelper(mContext).getApplicationInfo(mPackageName,
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index c440303..94c6574 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -690,6 +690,16 @@
         };
     }
 
+    /**
+     * Compares the ratio of two quantities and returns whether that ratio is greater than the
+     * provided bound. Order of quantities does not matter. Bound should be a decimal representation
+     * of a percentage.
+     */
+    public static boolean isRelativePercentDifferenceGreaterThan(float first, float second,
+            float bound) {
+        return (Math.abs(first - second) / Math.abs((first + second) / 2.0f)) > bound;
+    }
+
     private static class FixedSizeEmptyDrawable extends ColorDrawable {
 
         private final int mSize;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 87fb6fb..c84724f 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -69,6 +69,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dot.FolderDotInfo;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
@@ -110,6 +111,7 @@
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.PendingAppWidgetHostView;
 import com.android.launcher3.widget.WidgetManagerHelper;
+import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
 
 import java.util.ArrayList;
@@ -186,7 +188,6 @@
     @Thunk final Launcher mLauncher;
     @Thunk DragController mDragController;
 
-    private final Rect mTempRect = new Rect();
     private final int[] mTempXY = new int[2];
     private final float[] mTempFXY = new float[2];
     @Thunk float[] mDragViewVisualCenter = new float[2];
@@ -310,8 +311,12 @@
         Rect padding = grid.workspacePadding;
         setPadding(padding.left, padding.top, padding.right, padding.bottom);
         mInsets.set(insets);
+        // Increase our bottom insets so we don't overlap with the taskbar.
+        mInsets.bottom += grid.nonOverlappingTaskbarInset;
 
-        if (mWorkspaceFadeInAdjacentScreens) {
+        if (isTwoPanelEnabled()) {
+            setPageSpacing(0); // we have two pages and we don't want any spacing
+        } else if (mWorkspaceFadeInAdjacentScreens) {
             // In landscape mode the page spacing is set to the default.
             setPageSpacing(grid.edgeMarginPx);
         } else {
@@ -323,12 +328,30 @@
             setPageSpacing(Math.max(maxInsets, maxPadding));
         }
 
-
         int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
         int paddingBottom = grid.cellLayoutBottomPaddingPx;
+        int twoPanelLandscapeSidePadding = paddingLeftRight * 2;
+        int twoPanelPortraitSidePadding = paddingLeftRight / 2;
+
+        int panelCount = getPanelCount();
         for (int i = mWorkspaceScreens.size() - 1; i >= 0; i--) {
-            mWorkspaceScreens.valueAt(i)
-                    .setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
+            int paddingLeft = paddingLeftRight;
+            int paddingRight = paddingLeftRight;
+            if (panelCount > 1) {
+                if (i % panelCount == 0) { // left side panel
+                    paddingLeft = grid.isLandscape ? twoPanelLandscapeSidePadding
+                            : twoPanelPortraitSidePadding;
+                    paddingRight = 0;
+                } else if (i % panelCount == panelCount - 1) { // right side panel
+                    paddingLeft = 0;
+                    paddingRight = grid.isLandscape ? twoPanelLandscapeSidePadding
+                            : twoPanelPortraitSidePadding;
+                } else { // middle panel
+                    paddingLeft = 0;
+                    paddingRight = 0;
+                }
+            }
+            mWorkspaceScreens.valueAt(i).setPadding(paddingLeft, 0, paddingRight, paddingBottom);
         }
     }
 
@@ -367,10 +390,19 @@
     }
 
     public float getWallpaperOffsetForCenterPage() {
-        int pageScroll = getScrollForPage(getPageNearestToCenterOfScreen());
+        return getWallpaperOffsetForPage(getPageNearestToCenterOfScreen());
+    }
+
+    private float getWallpaperOffsetForPage(int page) {
+        int pageScroll = getScrollForPage(page);
         return mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
     }
 
+    /** Returns the number of pages used for the wallpaper parallax. */
+    public int getNumPagesForWallpaperParallax() {
+        return mWallpaperOffset.getNumPagesForWallpaperParallax();
+    }
+
     public Rect estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan) {
         Rect r = new Rect();
         cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
@@ -388,15 +420,6 @@
             layout.markCellsAsUnoccupiedForView(mDragInfo.cell);
         }
 
-        if (mOutlineProvider != null) {
-            if (dragObject.dragView != null) {
-                Bitmap preview = dragObject.dragView.getPreviewBitmap();
-
-                // The outline is used to visualize where the item will land if dropped
-                mOutlineProvider.generateDragOutline(preview);
-            }
-        }
-
         updateChildrenLayersEnabled();
 
         // Do not add a new page if it is a accessible drag which was not started by the workspace.
@@ -435,6 +458,15 @@
                 .log(LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED);
     }
 
+    private boolean isTwoPanelEnabled() {
+        return mLauncher.mDeviceProfile.isTablet && FeatureFlags.ENABLE_TWO_PANEL_HOME.get();
+    }
+
+    @Override
+    protected int getPanelCount() {
+        return isTwoPanelEnabled() ? 2 : super.getPanelCount();
+    }
+
     public void deferRemoveExtraEmptyScreen() {
         mDeferRemoveExtraEmptyScreen = true;
     }
@@ -822,7 +854,7 @@
 
     private boolean shouldConsumeTouch(View v) {
         return !workspaceIconsCanBeDragged()
-                || (!workspaceInModalState() && indexOfChild(v) != mCurrentPage);
+                || (!workspaceInModalState() && !isVisible(v));
     }
 
     public boolean isSwitchingState() {
@@ -1496,10 +1528,10 @@
             draggableView = (DraggableView) child;
         }
 
-        // The drag bitmap follows the touch point around on the screen
-        final Bitmap b = previewProvider.createDragBitmap();
+        // The draggable drawable follows the touch point around on the screen
+        final Drawable drawable = previewProvider.createDrawable();
         int halfPadding = previewProvider.previewPadding / 2;
-        float scale = previewProvider.getScaleAndPosition(b, mTempXY);
+        float scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
         int dragLayerX = mTempXY[0];
         int dragLayerY = mTempXY[1];
 
@@ -1525,9 +1557,21 @@
             }
         }
 
-        DragView dv = mDragController.startDrag(b, draggableView, dragLayerX, dragLayerY, source,
-                dragObject, dragVisualizeOffset, dragRect, scale * iconScale,
-                scale, dragOptions);
+        if (drawable instanceof AppWidgetHostViewDrawable) {
+            mDragController.addDragListener(new AppWidgetHostViewDragListener(mLauncher));
+        }
+        DragView dv = mDragController.startDrag(
+                drawable,
+                draggableView,
+                dragLayerX,
+                dragLayerY,
+                source,
+                dragObject,
+                dragVisualizeOffset,
+                dragRect,
+                scale * iconScale,
+                scale,
+                dragOptions);
         dv.setIntrinsicIconScaleFactor(dragOptions.intrinsicIconScaleFactor);
         return dv;
     }
@@ -2249,19 +2293,27 @@
 
         int nextPage = getNextPage();
         if (layout == null && !isPageInTransition()) {
-            // Check if the item is dragged over left page
+            // Check if the item is dragged over currentPage - 1 page
             mTempTouchCoordinates[0] = Math.min(centerX, d.x);
             mTempTouchCoordinates[1] = d.y;
             layout = verifyInsidePage(nextPage + (mIsRtl ? 1 : -1), mTempTouchCoordinates);
         }
 
         if (layout == null && !isPageInTransition()) {
-            // Check if the item is dragged over right page
+            // Check if the item is dragged over currentPage + 1 page
             mTempTouchCoordinates[0] = Math.max(centerX, d.x);
             mTempTouchCoordinates[1] = d.y;
             layout = verifyInsidePage(nextPage + (mIsRtl ? -1 : 1), mTempTouchCoordinates);
         }
 
+        // If two panel is enabled, users can also drag items to currentPage + 2
+        if (isTwoPanelEnabled() && layout == null && !isPageInTransition()) {
+            // Check if the item is dragged over currentPage + 2 page
+            mTempTouchCoordinates[0] = Math.max(centerX, d.x);
+            mTempTouchCoordinates[1] = d.y;
+            layout = verifyInsidePage(nextPage + (mIsRtl ? -2 : 2), mTempTouchCoordinates);
+        }
+
         // Always pick the current page.
         if (layout == null && nextPage >= 0 && nextPage < getPageCount()) {
             layout = (CellLayout) getChildAt(nextPage);
@@ -2575,7 +2627,11 @@
 
     }
 
-    public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
+    private Drawable createWidgetDrawable(ItemInfo widgetInfo, View layout) {
+        if (layout instanceof LauncherAppWidgetHostView) {
+            return new AppWidgetHostViewDrawable((LauncherAppWidgetHostView) layout);
+        }
+
         int[] unScaledSize = estimateItemSize(widgetInfo);
         int visibility = layout.getVisibility();
         layout.setVisibility(VISIBLE);
@@ -2587,7 +2643,7 @@
         Bitmap b = BitmapRenderer.createHardwareBitmap(
                 unScaledSize[0], unScaledSize[1], layout::draw);
         layout.setVisibility(visibility);
-        return b;
+        return new FastBitmapDrawable(b);
     }
 
     private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
@@ -2657,8 +2713,8 @@
         boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET ||
                 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
         if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
-            Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
-            dragView.setCrossFadeBitmap(crossFadeBitmap);
+            Drawable crossFadeDrawable = createWidgetDrawable(info, finalView);
+            dragView.setCrossFadeDrawable(crossFadeDrawable);
             dragView.crossFade((int) (duration * 0.8f));
         } else if (isWidget && external) {
             scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0],  scaleXY[1]);
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 660eeab..412754e 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -47,7 +47,6 @@
 
 import com.android.launcher3.LauncherState.PageAlphaProvider;
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
-import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.anim.SpringAnimationBuilder;
@@ -55,6 +54,7 @@
 import com.android.launcher3.graphics.WorkspaceDragScrim;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.DynamicResource;
+import com.android.launcher3.util.MultiValueAlpha;
 import com.android.systemui.plugins.ResourceProvider;
 
 /**
@@ -110,9 +110,6 @@
                 pageAlphaProvider.interpolator);
         boolean playAtomicComponent = config.playAtomicOverviewScaleComponent();
         Hotseat hotseat = mWorkspace.getHotseat();
-        // Since we set the pivot relative to mWorkspace, we need to scale a sibling of Workspace.
-        AllAppsContainerView qsbScaleView = mLauncher.getAppsView();
-        View qsbView = qsbScaleView.getSearchView();
         if (playAtomicComponent) {
             Interpolator scaleInterpolator = config.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
             LauncherState fromState = mLauncher.getStateManager().getState();
@@ -126,25 +123,20 @@
             }
 
             setPivotToScaleWithWorkspace(hotseat);
-            setPivotToScaleWithWorkspace(qsbScaleView);
             float hotseatScale = hotseatScaleAndTranslation.scale;
             if (shouldSpring) {
                 PendingAnimation pa = (PendingAnimation) propertySetter;
                 pa.add(getSpringScaleAnimator(mLauncher, hotseat, hotseatScale));
-                pa.add(getSpringScaleAnimator(mLauncher, qsbScaleView,
-                        qsbScaleAndTranslation.scale));
             } else {
                 Interpolator hotseatScaleInterpolator = config.getInterpolator(ANIM_HOTSEAT_SCALE,
                         scaleInterpolator);
                 propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale,
                         hotseatScaleInterpolator);
-                propertySetter.setFloat(qsbScaleView, SCALE_PROPERTY, qsbScaleAndTranslation.scale,
-                        hotseatScaleInterpolator);
             }
 
             float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
-            propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha,
-                    config.getInterpolator(ANIM_HOTSEAT_FADE, fadeInterpolator));
+            propertySetter.setFloat(hotseat.getStateAlpha(), MultiValueAlpha.VALUE,
+                    hotseatIconsAlpha, config.getInterpolator(ANIM_HOTSEAT_FADE, fadeInterpolator));
             float workspacePageIndicatorAlpha = (elements & WORKSPACE_PAGE_INDICATOR) != 0 ? 1 : 0;
             propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
                     workspacePageIndicatorAlpha, fadeInterpolator);
@@ -169,8 +161,6 @@
                 hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
         propertySetter.setFloat(mWorkspace.getPageIndicator(), VIEW_TRANSLATE_Y,
                 hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
-        propertySetter.setFloat(qsbView, VIEW_TRANSLATE_Y,
-                qsbScaleAndTranslation.translationY, hotseatTranslationInterpolator);
 
         setScrim(propertySetter, state);
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index edd9a9f..78c404f 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -201,9 +201,9 @@
         }
         if (!mAH[AdapterHolder.MAIN].appsList.hasFilter()) {
             rebindAdapters(hasWorkApps);
-        }
-        if (hasWorkApps) {
-            resetWorkProfile();
+            if (hasWorkApps) {
+                resetWorkProfile();
+            }
         }
     }
 
@@ -246,11 +246,7 @@
             hideInput();
             return false;
         }
-        boolean shouldScroll = rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
-        if (shouldScroll) {
-            hideInput();
-        }
-        return shouldScroll;
+        return rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
     }
 
     @Override
@@ -347,7 +343,7 @@
 
         mSearchContainer = findViewById(R.id.search_container_all_apps);
         mSearchUiManager = (SearchUiManager) mSearchContainer;
-        mSearchUiManager.initialize(this);
+        mSearchUiManager.initializeSearch(this);
     }
 
     public SearchUiManager getSearchUiManager() {
@@ -395,7 +391,8 @@
     @Override
     public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
         if (Utilities.ATLEAST_Q) {
-            mNavBarScrimHeight = insets.getTappableElementInsets().bottom;
+            mNavBarScrimHeight = insets.getTappableElementInsets().bottom
+                    - mLauncher.getDeviceProfile().nonOverlappingTaskbarInset;
         } else {
             mNavBarScrimHeight = insets.getStableInsetBottom();
         }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 179cb77..f307a53 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -31,6 +31,7 @@
 import android.util.SparseIntArray;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.WindowInsets;
 
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -188,6 +189,7 @@
             case SCROLL_STATE_DRAGGING:
                 mgr.logger().sendToInteractionJankMonitor(
                         LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN, this);
+                getWindowInsetsController().hide(WindowInsets.Type.ime());
                 break;
             case SCROLL_STATE_IDLE:
                 mgr.logger().sendToInteractionJankMonitor(
diff --git a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
index f4d735e..269e390 100644
--- a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
+++ b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
@@ -56,11 +56,10 @@
                 SectionDecorationInfo sectionInfo = adapterItem.sectionDecorationInfo;
                 SectionDecorationHandler decorationHandler = sectionInfo.getDecorationHandler();
                 if (decorationHandler != null) {
-                    decorationHandler.extendBounds(view);
                     if (sectionInfo.isFocusedView()) {
                         decorationHandler.onFocusDraw(c, view);
                     } else {
-                        decorationHandler.onGroupDraw(c);
+                        decorationHandler.onGroupDraw(c, view);
                     }
                 }
             }
@@ -131,26 +130,13 @@
         }
 
         /**
-         * Extends current bounds to include the view.
-         */
-        public void extendBounds(View view) {
-            if (mBounds.isEmpty()) {
-                mBounds.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
-            } else {
-                mBounds.set(
-                        Math.min(mBounds.left, view.getLeft()),
-                        Math.min(mBounds.top, view.getTop()),
-                        Math.max(mBounds.right, view.getRight()),
-                        Math.max(mBounds.bottom, view.getBottom())
-                );
-            }
-        }
-
-        /**
          * Draw bounds onto canvas.
          */
-        public void onGroupDraw(Canvas canvas) {
+        public void onGroupDraw(Canvas canvas, View view) {
+            if (view == null) return;
+
             mPaint.setColor(mFillcolor);
+            mBounds.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
             onDraw(canvas);
         }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index abf63dc..16ecd58 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -16,16 +16,11 @@
 package com.android.launcher3.allapps;
 
 import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
-import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
-import static com.android.launcher3.LauncherState.APPS_VIEW_ITEM_MASK;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_ALLAPPS;
@@ -194,25 +189,10 @@
      */
     public void setAlphas(LauncherState state, StateAnimationConfig config, PropertySetter setter) {
         int visibleElements = state.getVisibleElements(mLauncher);
-        boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
         boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0;
 
-        boolean hasAnyVisibleItem = (visibleElements & APPS_VIEW_ITEM_MASK) != 0;
-
         Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
-        Interpolator headerFade = config.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
-
-
-        setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
-        setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
-        mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra,
-                hasAllAppsContent, setter, headerFade, allAppsFade);
-
-        mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
-
-        // Set visibility of the container at the very beginning or end of the transition.
-        setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0,
-                hasAnyVisibleItem ? INSTANT : FINAL_FRAME);
+        setter.setViewAlpha(mAppsView, hasAllAppsContent ? 1 : 0, allAppsFade);
     }
 
     public AnimatorListenerAdapter getProgressAnimatorListener() {
@@ -246,6 +226,7 @@
      * TODO: This logic should go in {@link LauncherState}
      */
     private void onProgressAnimationEnd() {
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
         if (Float.compare(mProgress, 1f) == 0) {
             mAppsView.reset(false /* animate */);
         }
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderRow.java b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
index 31c6cc7..9bf6043 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderRow.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
@@ -17,10 +17,8 @@
 
 import android.graphics.Rect;
 import android.view.View;
-import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.anim.PropertySetter;
 
 /**
  * A abstract representation of a row in all-apps view
@@ -47,9 +45,6 @@
      */
     boolean hasVisibleContent();
 
-    void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
-            PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade);
-
     /**
      * Scrolls the content vertically.
      */
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 9056e8a..86f330c 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
-
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Point;
@@ -26,7 +24,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.animation.Interpolator;
 import android.widget.LinearLayout;
 
 import androidx.annotation.NonNull;
@@ -37,7 +34,6 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
-import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.systemui.plugins.AllAppsRow;
@@ -88,7 +84,6 @@
     private int mSnappedScrolledY;
     private int mTranslationY;
 
-    private boolean mAllowTouchForwarding;
     private boolean mForwardToRecyclerView;
 
     protected boolean mTabsHidden;
@@ -350,10 +345,6 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        if (!mAllowTouchForwarding) {
-            mForwardToRecyclerView = false;
-            return super.onInterceptTouchEvent(ev);
-        }
         calcOffset(mTempOffset);
         ev.offsetLocation(mTempOffset.x, mTempOffset.y);
         mForwardToRecyclerView = mCurrentRV.onInterceptTouchEvent(ev);
@@ -382,20 +373,6 @@
         p.y = getTop() - mCurrentRV.getTop() - mParent.getTop();
     }
 
-    public void setContentVisibility(boolean hasHeader, boolean hasAllAppsContent,
-            PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
-        for (FloatingHeaderRow row : mAllRows) {
-            row.setContentVisibility(hasHeader, hasAllAppsContent, setter, headerFade, allAppsFade);
-        }
-
-        allowTouchForwarding(hasAllAppsContent);
-        setter.setFloat(mTabLayout, VIEW_ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
-    }
-
-    protected void allowTouchForwarding(boolean allow) {
-        mAllowTouchForwarding = allow;
-    }
-
     public boolean hasVisibleContent() {
         for (FloatingHeaderRow row : mAllRows) {
             if (row.hasVisibleContent()) {
diff --git a/src/com/android/launcher3/allapps/PluginHeaderRow.java b/src/com/android/launcher3/allapps/PluginHeaderRow.java
index cf7142c..5b5fbb7 100644
--- a/src/com/android/launcher3/allapps/PluginHeaderRow.java
+++ b/src/com/android/launcher3/allapps/PluginHeaderRow.java
@@ -18,14 +18,10 @@
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
-
 import android.graphics.Rect;
 import android.view.View;
-import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.anim.PropertySetter;
 import com.android.systemui.plugins.AllAppsRow;
 
 /**
@@ -65,13 +61,6 @@
     }
 
     @Override
-    public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
-            PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
-        // Don't use setViewAlpha as we want to control the visibility ourselves.
-        setter.setFloat(mView, VIEW_ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
-    }
-
-    @Override
     public void setVerticalScroll(int scroll, boolean isScrolledOut) {
         mView.setVisibility(isScrolledOut ? INVISIBLE : VISIBLE);
         if (!isScrolledOut) {
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index 0d42950..0a2dea9 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -15,16 +15,12 @@
  */
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.LauncherState.ALL_APPS_HEADER;
-
 import android.graphics.Rect;
 import android.view.KeyEvent;
-import android.view.animation.Interpolator;
 
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.ExtendedEditText;
-import com.android.launcher3.anim.PropertySetter;
 
 /**
  * Interface for controlling the Apps search UI.
@@ -34,7 +30,7 @@
     /**
      * Initializes the search manager.
      */
-    void initialize(AllAppsContainerView containerView);
+    void initializeSearch(AllAppsContainerView containerView);
 
     /**
      * Notifies the search manager to close any active search session.
@@ -45,7 +41,7 @@
      * Called before dispatching a key event, in case the search manager wants to initialize
      * some UI beforehand.
      */
-    void preDispatchKeyEvent(KeyEvent keyEvent);
+    default void preDispatchKeyEvent(KeyEvent keyEvent) { };
 
     /**
      * Returns the vertical shift for the all-apps view, so that it aligns with the hotseat.
@@ -53,23 +49,9 @@
     float getScrollRangeDelta(Rect insets);
 
     /**
-     * Called as part of state transition to update the content UI
-     */
-    void setContentVisibility(int visibleElements, PropertySetter setter,
-            Interpolator interpolator);
-
-    /**
      * Called when activity is destroyed. Used to close search system services
      */
-    default void destroy() {
-    }
-
-    /**
-     * Returns true if the QSB should be visible for the given set of visible elements
-     */
-    default boolean isQsbVisible(int visibleElements) {
-        return (visibleElements & ALL_APPS_HEADER) != 0;
-    }
+    default void destroySearch() { }
 
     /**
      * @return the edit text object
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 3319018..d3c9993 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -144,7 +144,7 @@
 
     @Override
     public void onFocusChange(View view, boolean hasFocus) {
-        if (!hasFocus) {
+        if (!hasFocus && !FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
             mInput.hideKeyboard();
         }
     }
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 2261d51..bfcc1c7 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -31,7 +31,6 @@
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
-import android.view.animation.Interpolator;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
@@ -44,7 +43,6 @@
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AlphabeticalAppsList;
 import com.android.launcher3.allapps.SearchUiManager;
-import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.search.SearchCallback;
 
@@ -134,7 +132,7 @@
     }
 
     @Override
-    public void initialize(AllAppsContainerView appsView) {
+    public void initializeSearch(AllAppsContainerView appsView) {
         mApps = appsView.getApps();
         mAppsView = appsView;
         mSearchBarController.initialize(
@@ -223,12 +221,6 @@
     }
 
     @Override
-    public void setContentVisibility(int visibleElements, PropertySetter setter,
-            Interpolator interpolator) {
-        setter.setViewAlpha(this, isQsbVisible(visibleElements) ? 1 : 0, interpolator);
-    }
-
-    @Override
     public ExtendedEditText getEditText() {
         return this;
     }
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
index f9fb22e..34895ed 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
@@ -25,6 +25,7 @@
 import com.android.launcher3.model.BaseModelUpdateTask;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.search.StringMatcherUtility;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -67,10 +68,10 @@
         // apps that don't match all of the words in the query.
         final String queryTextLower = query.toLowerCase();
         final ArrayList<AppInfo> result = new ArrayList<>();
-        DefaultAppSearchAlgorithm.StringMatcher matcher =
-                DefaultAppSearchAlgorithm.StringMatcher.getInstance();
+        StringMatcherUtility.StringMatcher matcher =
+                StringMatcherUtility.StringMatcher.getInstance();
         for (AppInfo info : apps) {
-            if (DefaultAppSearchAlgorithm.matches(info, queryTextLower, matcher)) {
+            if (StringMatcherUtility.matches(queryTextLower, info.title.toString(), matcher)) {
                 result.add(info);
             }
         }
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index 4e213b0..a386ef8 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -20,12 +20,9 @@
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
-import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.search.SearchAlgorithm;
 import com.android.launcher3.search.SearchCallback;
 
-import java.text.Collator;
-
 /**
  * The default search implementation.
  */
@@ -54,132 +51,4 @@
                         () -> callback.onSearchResult(query, results)),
                 null);
     }
-
-    public static boolean matches(AppInfo info, String query, StringMatcher matcher) {
-        int queryLength = query.length();
-
-        String title = info.title.toString();
-        int titleLength = title.length();
-
-        if (titleLength < queryLength || queryLength <= 0) {
-            return false;
-        }
-
-        if (requestSimpleFuzzySearch(query)) {
-            return title.toLowerCase().contains(query);
-        }
-
-        int lastType;
-        int thisType = Character.UNASSIGNED;
-        int nextType = Character.getType(title.codePointAt(0));
-
-        int end = titleLength - queryLength;
-        for (int i = 0; i <= end; i++) {
-            lastType = thisType;
-            thisType = nextType;
-            nextType = i < (titleLength - 1) ?
-                    Character.getType(title.codePointAt(i + 1)) : Character.UNASSIGNED;
-            if (isBreak(thisType, lastType, nextType) &&
-                    matcher.matches(query, title.substring(i, i + queryLength))) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Returns true if the current point should be a break point. Following cases
-     * are considered as break points:
-     *      1) Any non space character after a space character
-     *      2) Any digit after a non-digit character
-     *      3) Any capital character after a digit or small character
-     *      4) Any capital character before a small character
-     */
-    private static boolean isBreak(int thisType, int prevType, int nextType) {
-        switch (prevType) {
-            case Character.UNASSIGNED:
-            case Character.SPACE_SEPARATOR:
-            case Character.LINE_SEPARATOR:
-            case Character.PARAGRAPH_SEPARATOR:
-                return true;
-        }
-        switch (thisType) {
-            case Character.UPPERCASE_LETTER:
-                if (nextType == Character.UPPERCASE_LETTER) {
-                    return true;
-                }
-                // Follow through
-            case Character.TITLECASE_LETTER:
-                // Break point if previous was not a upper case
-                return prevType != Character.UPPERCASE_LETTER;
-            case Character.LOWERCASE_LETTER:
-                // Break point if previous was not a letter.
-                return prevType > Character.OTHER_LETTER || prevType <= Character.UNASSIGNED;
-            case Character.DECIMAL_DIGIT_NUMBER:
-            case Character.LETTER_NUMBER:
-            case Character.OTHER_NUMBER:
-                // Break point if previous was not a number
-                return !(prevType == Character.DECIMAL_DIGIT_NUMBER
-                        || prevType == Character.LETTER_NUMBER
-                        || prevType == Character.OTHER_NUMBER);
-            case Character.MATH_SYMBOL:
-            case Character.CURRENCY_SYMBOL:
-            case Character.OTHER_PUNCTUATION:
-            case Character.DASH_PUNCTUATION:
-                // Always a break point for a symbol
-                return true;
-            default:
-                return  false;
-        }
-    }
-
-    public static class StringMatcher {
-
-        private static final char MAX_UNICODE = '\uFFFF';
-
-        private final Collator mCollator;
-
-        StringMatcher() {
-            // On android N and above, Collator uses ICU implementation which has a much better
-            // support for non-latin locales.
-            mCollator = Collator.getInstance();
-            mCollator.setStrength(Collator.PRIMARY);
-            mCollator.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
-        }
-
-        /**
-         * Returns true if {@param query} is a prefix of {@param target}
-         */
-        public boolean matches(String query, String target) {
-            switch (mCollator.compare(query, target)) {
-                case 0:
-                    return true;
-                case -1:
-                    // The target string can contain a modifier which would make it larger than
-                    // the query string (even though the length is same). If the query becomes
-                    // larger after appending a unicode character, it was originally a prefix of
-                    // the target string and hence should match.
-                    return mCollator.compare(query + MAX_UNICODE, target) > -1;
-                default:
-                    return false;
-            }
-        }
-
-        public static StringMatcher getInstance() {
-            return new StringMatcher();
-        }
-    }
-
-    private static boolean requestSimpleFuzzySearch(String s) {
-        for (int i = 0; i < s.length(); ) {
-            int codepoint = s.codePointAt(i);
-            i += Character.charCount(codepoint);
-            switch (Character.UnicodeScript.of(codepoint)) {
-                case HAN:
-                    //Character.UnicodeScript.HAN: use String.contains to match
-                    return true;
-            }
-        }
-        return false;
-    }
 }
diff --git a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
index 330ec68..e268f56 100644
--- a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.allapps.search;
 
+import android.net.Uri;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -37,6 +38,11 @@
     }
 
     @Override
+    public void onSliceStatusUpdate(Uri sliceUri) {
+
+    }
+
+    @Override
     public boolean isSearchView(int viewType) {
         return false;
     }
diff --git a/src/com/android/launcher3/allapps/search/LiveSearchManager.java b/src/com/android/launcher3/allapps/search/LiveSearchManager.java
deleted file mode 100644
index 76099a6..0000000
--- a/src/com/android/launcher3/allapps/search/LiveSearchManager.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2020 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.allapps.search;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget;
-
-import android.app.Activity;
-import android.app.Application.ActivityLifecycleCallbacks;
-import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetHostView;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-import androidx.lifecycle.Observer;
-import androidx.slice.Slice;
-import androidx.slice.SliceViewManager;
-import androidx.slice.SliceViewManager.SliceCallback;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.statemanager.StateManager.StateListener;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.widget.PendingAddWidgetInfo;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * Manages Lifecycle for Live search results
- */
-public class LiveSearchManager implements StateListener<LauncherState> {
-
-    private static final String TAG = "LiveSearchManager";
-
-    public static final int SEARCH_APPWIDGET_HOST_ID = 2048;
-
-    private final Launcher mLauncher;
-    private final HashMap<Uri, SliceLifeCycle> mUriSliceMap = new HashMap<>();
-
-    private final HashMap<ComponentKey, SearchWidgetInfoContainer> mWidgetPlaceholders =
-            new HashMap<>();
-    private SearchWidgetHost mSearchWidgetHost;
-
-    public LiveSearchManager(Launcher launcher) {
-        mLauncher = launcher;
-        mLauncher.getStateManager().addStateListener(this);
-    }
-
-    /**
-     * Creates new {@link AppWidgetHostView} from {@link AppWidgetProviderInfo}. Caches views for
-     * quicker result within the same search session
-     */
-    public SearchWidgetInfoContainer getPlaceHolderWidget(AppWidgetProviderInfo providerInfo) {
-        if (mSearchWidgetHost == null) {
-            mSearchWidgetHost = new SearchWidgetHost(mLauncher);
-            mSearchWidgetHost.startListening();
-        }
-
-        ComponentName provider = providerInfo.provider;
-        UserHandle userHandle = providerInfo.getProfile();
-
-        ComponentKey key = new ComponentKey(provider, userHandle);
-        if (mWidgetPlaceholders.containsKey(key)) {
-            return mWidgetPlaceholders.get(key);
-        }
-
-        LauncherAppWidgetProviderInfo pinfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
-                mLauncher, providerInfo);
-        PendingAddWidgetInfo pendingAddWidgetInfo = new PendingAddWidgetInfo(pinfo);
-
-        Bundle options = getDefaultOptionsForWidget(mLauncher, pendingAddWidgetInfo);
-        int appWidgetId = mSearchWidgetHost.allocateAppWidgetId();
-        boolean success = AppWidgetManager.getInstance(mLauncher)
-                .bindAppWidgetIdIfAllowed(appWidgetId, userHandle, provider, options);
-        if (!success) {
-            mSearchWidgetHost.deleteAppWidgetId(appWidgetId);
-            mWidgetPlaceholders.put(key, null);
-            return null;
-        }
-
-        SearchWidgetInfoContainer view = (SearchWidgetInfoContainer) mSearchWidgetHost.createView(
-                mLauncher, appWidgetId, providerInfo);
-        view.setTag(pendingAddWidgetInfo);
-        mWidgetPlaceholders.put(key, view);
-        return view;
-    }
-
-    /**
-     * Stop search session
-     */
-    public void stop() {
-        clearWidgetHost();
-    }
-
-    private void clearWidgetHost() {
-        if (mSearchWidgetHost != null) {
-            mSearchWidgetHost.stopListening();
-            mSearchWidgetHost.clearViews();
-            mSearchWidgetHost.deleteHost();
-            mWidgetPlaceholders.clear();
-            mSearchWidgetHost = null;
-        }
-    }
-
-    @Override
-    public void onStateTransitionComplete(LauncherState finalState) {
-        if (finalState != ALL_APPS) {
-            // Clear all search session related objects
-            mUriSliceMap.values().forEach(SliceLifeCycle::destroy);
-            mUriSliceMap.clear();
-
-            clearWidgetHost();
-        }
-    }
-
-    /**
-     * Adds a new observer for the provided uri and returns a callback to cancel this observer
-     */
-    public SafeCloseable addObserver(Uri uri, Observer<Slice> listener) {
-        SliceLifeCycle slc = mUriSliceMap.get(uri);
-        if (slc == null) {
-            slc = new SliceLifeCycle(uri, mLauncher);
-            mUriSliceMap.put(uri, slc);
-        }
-        slc.addListener(listener);
-
-        final SliceLifeCycle sliceLifeCycle = slc;
-        return () -> sliceLifeCycle.removeListener(listener);
-    }
-
-    static class SearchWidgetHost extends AppWidgetHost {
-        SearchWidgetHost(Context context) {
-            super(context, SEARCH_APPWIDGET_HOST_ID);
-        }
-
-        @Override
-        protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
-                AppWidgetProviderInfo appWidget) {
-            return new SearchWidgetInfoContainer(context);
-        }
-
-        @Override
-        public void clearViews() {
-            super.clearViews();
-        }
-    }
-
-    private static class SliceLifeCycle
-            implements ActivityLifecycleCallbacks, SliceCallback {
-
-        private final Uri mUri;
-        private final Launcher mLauncher;
-        private final SliceViewManager mSliceViewManager;
-        private final ArrayList<Observer<Slice>> mListeners = new ArrayList<>();
-
-        private boolean mDestroyed = false;
-        private boolean mWasListening = false;
-
-        SliceLifeCycle(Uri uri, Launcher launcher) {
-            mUri = uri;
-            mLauncher = launcher;
-            mSliceViewManager = SliceViewManager.getInstance(launcher);
-            launcher.registerActivityLifecycleCallbacks(this);
-
-            if (launcher.isDestroyed()) {
-                onActivityDestroyed(launcher);
-            } else if (launcher.isStarted()) {
-                onActivityStarted(launcher);
-            }
-        }
-
-        @Override
-        public void onActivityDestroyed(Activity activity) {
-            destroy();
-        }
-
-        @Override
-        public void onActivityStarted(Activity activity) {
-            updateListening();
-        }
-
-        @Override
-        public void onActivityStopped(Activity activity) {
-            updateListening();
-        }
-
-        private void updateListening() {
-            boolean isListening = mDestroyed
-                    ? false
-                    : (mLauncher.isStarted() && !mListeners.isEmpty());
-            UI_HELPER_EXECUTOR.execute(() -> uploadListeningBg(isListening));
-        }
-
-        @WorkerThread
-        private void uploadListeningBg(boolean isListening) {
-            if (mWasListening != isListening) {
-                mWasListening = isListening;
-                if (isListening) {
-                    mSliceViewManager.registerSliceCallback(mUri, MAIN_EXECUTOR, this);
-                    // Update slice one-time on the different thread so that we can display
-                    // multiple slices in parallel
-                    THREAD_POOL_EXECUTOR.execute(this::updateSlice);
-                } else {
-                    mSliceViewManager.unregisterSliceCallback(mUri, this);
-                }
-            }
-        }
-
-        @UiThread
-        private void addListener(Observer<Slice> listener) {
-            mListeners.add(listener);
-            updateListening();
-        }
-
-        @UiThread
-        private void removeListener(Observer<Slice> listener) {
-            mListeners.remove(listener);
-            updateListening();
-        }
-
-        @WorkerThread
-        private void updateSlice() {
-            try {
-                Slice s = mSliceViewManager.bindSlice(mUri);
-                MAIN_EXECUTOR.execute(() -> onSliceUpdated(s));
-            } catch (Exception e) {
-                Log.d(TAG, "Error fetching slice", e);
-            }
-        }
-
-        @UiThread
-        @Override
-        public void onSliceUpdated(@Nullable Slice s) {
-            mListeners.forEach(l -> l.onChanged(s));
-        }
-
-        private void destroy() {
-            if (mDestroyed) {
-                return;
-            }
-            mDestroyed = true;
-            mLauncher.unregisterActivityLifecycleCallbacks(this);
-            mListeners.clear();
-        }
-
-        @Override
-        public void onActivityCreated(Activity activity, Bundle bundle) { }
-
-        @Override
-        public void onActivityPaused(Activity activity) { }
-
-        @Override
-        public void onActivityResumed(Activity activity) { }
-
-        @Override
-        public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
-    }
-}
diff --git a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
index fdacd3d..0864090 100644
--- a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.allapps.search;
 
+import android.net.Uri;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -40,6 +41,11 @@
     public abstract void onBindView(AllAppsGridAdapter.ViewHolder holder, int position);
 
     /**
+     * Called from LiveSearchManager to notify slice status updates.
+     */
+    public abstract void onSliceStatusUpdate(Uri sliceUri);
+
+    /**
      * Returns whether or not viewType can be handled by searchProvider
      */
     public abstract boolean isSearchView(int viewType);
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index e406e9b..637bf60 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -85,7 +85,7 @@
             "ADAPTIVE_ICON_WINDOW_ANIM", true, "Use adaptive icons for window animations.");
 
     public static final BooleanFlag ENABLE_QUICKSTEP_LIVE_TILE = getDebugFlag(
-            "ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview");
+            "ENABLE_QUICKSTEP_LIVE_TILE", true, "Enable live tile in Quickstep overview");
 
     // Keep as DeviceFlag to allow remote disable in emergency.
     public static final BooleanFlag ENABLE_SUGGESTED_ACTIONS_OVERVIEW = new DeviceFlag(
@@ -140,6 +140,9 @@
     public static final BooleanFlag ENABLE_OVERVIEW_SELECTIONS = new DeviceFlag(
             "ENABLE_OVERVIEW_SELECTIONS", true, "Show Select Mode button in Overview Actions");
 
+    public static final BooleanFlag ENABLE_WIDGETS_PICKER_AIAI_SEARCH = new DeviceFlag(
+            "ENABLE_WIDGETS_PICKER_AIAI_SEARCH", false, "Enable AiAi search in the widgets picker");
+
     public static final BooleanFlag ENABLE_OVERVIEW_SHARE = getDebugFlag(
             "ENABLE_OVERVIEW_SHARE", false, "Show Share button in Overview Actions");
 
@@ -198,13 +201,20 @@
             "ENABLE_APP_PREDICTIONS_WHILE_VISIBLE", true, "Allows app "
             + "predictions to be updated while they are visible to the user.");
 
-    public static final BooleanFlag ENABLE_TASKBAR = new DeviceFlag(
+    public static final BooleanFlag ENABLE_TASKBAR = getDebugFlag(
             "ENABLE_TASKBAR", false, "Allows a system Taskbar to be shown on larger devices.");
 
-    public static final BooleanFlag ENABLE_OVERVIEW_GRID = new DeviceFlag(
+    public static final BooleanFlag ENABLE_OVERVIEW_GRID = getDebugFlag(
             "ENABLE_OVERVIEW_GRID", false, "Uses grid overview layout. "
             + "Only applicable on large screen devices.");
 
+    public static final BooleanFlag ENABLE_TWO_PANEL_HOME = getDebugFlag(
+            "ENABLE_TWO_PANEL_HOME", false,
+            "Uses two panel on home screen. Only applicable on large screen devices.");
+
+    public static final BooleanFlag ENABLE_SPLIT_SELECT = getDebugFlag(
+            "ENABLE_SPLIT_SELECT", false, "Uses new split screen selection overview UI");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index c972cbb..7bc9865 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -142,7 +142,7 @@
 
         // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
         // we abort the drag.
-        if (img.getBitmap() == null) {
+        if (img.getDrawable() == null) {
             return false;
         }
 
@@ -151,7 +151,7 @@
 
         // Start home and pass the draw request params
         PinItemDragListener listener = new PinItemDragListener(mRequest, bounds,
-                img.getBitmap().getWidth(), img.getWidth());
+                img.getDrawable().getIntrinsicWidth(), img.getWidth());
 
 
         // Start a system drag and drop. We use a transparent bitmap as preview for system drag
diff --git a/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java b/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java
new file mode 100644
index 0000000..92ae670
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 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.dragndrop;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+
+/** A drawable which renders {@link LauncherAppWidgetHostView} to a canvas. */
+public final class AppWidgetHostViewDrawable extends Drawable {
+
+    private final LauncherAppWidgetHostView mAppWidgetHostView;
+    private Paint mPaint = new Paint();
+
+    public AppWidgetHostViewDrawable(LauncherAppWidgetHostView appWidgetHostView) {
+        mAppWidgetHostView = appWidgetHostView;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        int saveCount = canvas.saveLayer(0, 0, getIntrinsicWidth(), getIntrinsicHeight(), mPaint);
+        mAppWidgetHostView.draw(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mAppWidgetHostView.getMeasuredWidth();
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mAppWidgetHostView.getMeasuredHeight();
+    }
+
+    @Override
+    public int getOpacity() {
+        // This is up to app widget provider. We don't know if the host view will cover anything
+        // behind the drawable.
+        return PixelFormat.UNKNOWN;
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mPaint.setAlpha(alpha);
+    }
+
+    @Override
+    public int getAlpha() {
+        return mPaint.getAlpha();
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mPaint.setColorFilter(colorFilter);
+    }
+
+    @Override
+    public ColorFilter getColorFilter() {
+        return mPaint.getColorFilter();
+    }
+
+    /** Returns the {@link LauncherAppWidgetHostView}. */
+    public LauncherAppWidgetHostView getAppWidgetHostView() {
+        return mAppWidgetHostView;
+    }
+}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 93df599..b7a70cb 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -25,9 +25,9 @@
 import android.animation.ValueAnimator;
 import android.content.ComponentName;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.view.DragEvent;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -129,7 +129,7 @@
      * drop, it is the responsibility of the {@link DropTarget} to exit out of the spring loaded
      * mode. If the drop was cancelled for some reason, the UI will automatically exit out of this mode.
      *
-     * @param b The bitmap to display as the drag image.  It will be re-scaled to the
+     * @param drawable The drawable to be displayed in the drag view.  It will be re-scaled to the
      *          enlarged size.
      * @param originalView The source view (ie. icon, widget etc.) that is being dragged
      *          and which the DragView represents
@@ -140,9 +140,18 @@
      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
      */
-    public DragView startDrag(Bitmap b, DraggableView originalView, int dragLayerX, int dragLayerY,
-            DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion,
-            float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options) {
+    public DragView startDrag(
+            Drawable drawable,
+            DraggableView originalView,
+            int dragLayerX,
+            int dragLayerY,
+            DragSource source,
+            ItemInfo dragInfo,
+            Point dragOffset,
+            Rect dragRegion,
+            float initialDragViewScale,
+            float dragViewScaleOnDrop,
+            DragOptions options) {
         if (PROFILE_DRAWING_DURING_DRAG) {
             android.os.Debug.startMethodTracing("Launcher");
         }
@@ -173,8 +182,14 @@
         final Resources res = mLauncher.getResources();
         final float scaleDps = mIsInPreDrag
                 ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
-        final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
-                registrationY, initialDragViewScale, dragViewScaleOnDrop, scaleDps);
+        final DragView dragView = mDragObject.dragView = new DragView(
+                mLauncher,
+                drawable,
+                registrationX,
+                registrationY,
+                initialDragViewScale,
+                dragViewScaleOnDrop,
+                scaleDps);
         dragView.setItemInfo(dragInfo);
         mDragObject.dragComplete = false;
 
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 86b93d0..e2816f4 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -67,9 +67,9 @@
     public static final int COLOR_CHANGE_DURATION = 120;
     public static final int VIEW_ZOOM_DURATION = 150;
 
-    private boolean mDrawBitmap = true;
-    private Bitmap mBitmap;
-    private Bitmap mCrossFadeBitmap;
+    private boolean mShouldDraw = true;
+    private Drawable mDrawable;
+    private Drawable mCrossFadeDrawable;
     @Thunk Paint mPaint;
     private final int mBlurSizeOutline;
     private final int mRegistrationX;
@@ -114,19 +114,21 @@
      * The registration point is the point inside our view that the touch events should
      * be centered upon.
      * @param launcher The Launcher instance
-     * @param bitmap The view that we're dragging around.  We scale it up when we draw it.
+     * @param drawable The view that we're dragging around.  We scale it up when we draw it.
      * @param registrationX The x coordinate of the registration point.
      * @param registrationY The y coordinate of the registration point.
      */
-    public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY,
-                    final float initialScale, final float scaleOnDrop, final float finalScaleDps) {
+    public DragView(Launcher launcher, Drawable drawable, int registrationX,
+            int registrationY, final float initialScale, final float scaleOnDrop,
+            final float finalScaleDps) {
         super(launcher);
         mLauncher = launcher;
         mDragLayer = launcher.getDragLayer();
         mDragController = launcher.getDragController();
         mFirstFrameAnimatorHelper = new FirstFrameAnimatorHelper(this);
 
-        final float scale = (bitmap.getWidth() + finalScaleDps) / bitmap.getWidth();
+        final float scale = (drawable.getIntrinsicWidth() + finalScaleDps)
+                / drawable.getIntrinsicWidth();
 
         // Set the initial scale to avoid any jumps
         setScaleX(initialScale);
@@ -144,8 +146,9 @@
             }
         });
 
-        mBitmap = bitmap;
-        setDragRegion(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()));
+        mDrawable = drawable;
+        setDragRegion(new Rect(0, 0, drawable.getIntrinsicWidth(),
+                drawable.getIntrinsicHeight()));
 
         // The point in our scaled bitmap that the touch events are located
         mRegistrationX = registrationX;
@@ -197,8 +200,8 @@
             @Override
             public void run() {
                 Object[] outObj = new Object[1];
-                int w = mBitmap.getWidth();
-                int h = mBitmap.getHeight();
+                int w = mDrawable.getIntrinsicWidth();
+                int h = mDrawable.getIntrinsicHeight();
                 Drawable dr = Utilities.getFullDrawable(mLauncher, info, w, h, outObj);
 
                 if (dr instanceof AdaptiveIconDrawable) {
@@ -214,11 +217,11 @@
                     mBadge.setBounds(badgeBounds);
 
                     // Do not draw the background in case of folder as its translucent
-                    mDrawBitmap = !(dr instanceof FolderAdaptiveIcon);
+                    mShouldDraw = !(dr instanceof FolderAdaptiveIcon);
 
                     try (LauncherIcons li = LauncherIcons.obtain(mLauncher)) {
                         Drawable nDr; // drawable to be normalized
-                        if (mDrawBitmap) {
+                        if (mShouldDraw) {
                             nDr = dr;
                         } else {
                             // Since we just want the scale, avoid heavy drawing operations
@@ -308,7 +311,7 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
+        setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
     }
 
     /** Sets the scale of the view over the normal workspace icon size. */
@@ -352,29 +355,37 @@
         return mDragRegion;
     }
 
-    public Bitmap getPreviewBitmap() {
-        return mBitmap;
-    }
-
     @Override
     protected void onDraw(Canvas canvas) {
         mHasDrawn = true;
 
-        if (mDrawBitmap) {
+        if (mShouldDraw) {
             // Always draw the bitmap to mask anti aliasing due to clipPath
-            boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null;
+            boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeDrawable != null;
             if (crossFade) {
                 int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255;
                 mPaint.setAlpha(alpha);
             }
-            canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
+            mDrawable.setColorFilter(mPaint.getColorFilter());
+            mDrawable.setAlpha(mPaint.getAlpha());
+            mDrawable.setBounds(
+                    new Rect(0, 0, mDrawable.getIntrinsicWidth(),
+                            mDrawable.getIntrinsicHeight()));
+            mDrawable.draw(canvas);
             if (crossFade) {
                 mPaint.setAlpha((int) (255 * mCrossFadeProgress));
                 final int saveCount = canvas.save();
-                float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
-                float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
+                float sX = ((float) mDrawable.getIntrinsicWidth())
+                        / mCrossFadeDrawable.getIntrinsicWidth();
+                float sY = ((float) mDrawable.getIntrinsicHeight())
+                        / mCrossFadeDrawable.getIntrinsicHeight();
                 canvas.scale(sX, sY);
-                canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint);
+                mCrossFadeDrawable.setColorFilter(mPaint.getColorFilter());
+                mCrossFadeDrawable.setAlpha(mPaint.getAlpha());
+                mDrawable.setBounds(
+                        new Rect(0, 0, mDrawable.getIntrinsicWidth(),
+                                mDrawable.getIntrinsicHeight()));
+                mCrossFadeDrawable.draw(canvas);
                 canvas.restoreToCount(saveCount);
             }
         }
@@ -390,8 +401,8 @@
         }
     }
 
-    public void setCrossFadeBitmap(Bitmap crossFadeBitmap) {
-        mCrossFadeBitmap = crossFadeBitmap;
+    public void setCrossFadeDrawable(Drawable crossFadeDrawable) {
+        mCrossFadeDrawable = crossFadeDrawable;
     }
 
     public void crossFade(int duration) {
@@ -469,8 +480,8 @@
 
         // Start the pick-up animation
         DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);
-        lp.width = mBitmap.getWidth();
-        lp.height = mBitmap.getHeight();
+        lp.width = mDrawable.getIntrinsicWidth();
+        lp.height = mDrawable.getIntrinsicHeight();
         lp.customPosition = true;
         setLayoutParams(lp);
         move(touchX, touchY);
@@ -550,6 +561,11 @@
         return mInitialScale;
     }
 
+    /** Returns the current {@link Drawable} that is rendered in this view. */
+    public Drawable getDrawable() {
+        return mDrawable;
+    }
+
     private static class SpringFloatValue {
 
         private static final FloatPropertyCompat<SpringFloatValue> VALUE =
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index 2290473..9f12e6e 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -96,7 +96,7 @@
 
         PendingItemDragHelper dragHelper = new PendingItemDragHelper(view);
         if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_APPWIDGET) {
-            dragHelper.setPreview(getPreview(mRequest));
+            dragHelper.setRemoteViewsPreview(getPreview(mRequest));
         }
         return dragHelper;
     }
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
index 37200a6..6325877 100644
--- a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
+++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
@@ -56,9 +56,8 @@
         if (mScreen != null) {
             // Snap to the screen that we are hovering over now
             Workspace w = mLauncher.getWorkspace();
-            int page = w.indexOfChild(mScreen);
-            if (page != w.getCurrentPage()) {
-                w.snapToPage(page);
+            if (!w.isVisible(mScreen)) {
+                w.snapToPage(w.indexOfChild(mScreen));
             }
         } else {
             mLauncher.getDragController().cancelDrag();
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index feb528c..ee6ea99 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -40,7 +40,6 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PropertyResetListener;
@@ -177,7 +176,7 @@
                 Math.round((totalOffsetX + initialSize)),
                 Math.round((paddingOffsetY + initialSize)));
         Rect endRect = new Rect(0, 0, lp.width, lp.height);
-        float finalRadius = ResourceUtils.pxFromDp(2, mContext.getResources().getDisplayMetrics());
+        float finalRadius = mFolderBackground.getCornerRadius();
 
         // Create the animators.
         AnimatorSet a = new AnimatorSet();
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 21822a3..9bc5444 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -30,9 +30,11 @@
 import android.view.View;
 
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.util.SafeCloseable;
@@ -88,10 +90,14 @@
     }
 
     /**
-     * Returns a new bitmap to show when the {@link #mView} is being dragged around.
-     * Responsibility for the bitmap is transferred to the caller.
+     * Returns a new drawable to show when the {@link #mView} is being dragged around.
+     * Responsibility for the drawable is transferred to the caller.
      */
-    public Bitmap createDragBitmap() {
+    public Drawable createDrawable() {
+        if (mView instanceof LauncherAppWidgetHostView) {
+            return new AppWidgetHostViewDrawable((LauncherAppWidgetHostView) mView);
+        }
+
         int width = 0;
         int height = 0;
         // Assume scaleX == scaleY, which is always the case for workspace items.
@@ -105,8 +111,9 @@
             height = mView.getHeight();
         }
 
-        return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
-                height + blurSizeOutline, (c) -> drawDragView(c, scale));
+        return new FastBitmapDrawable(
+                BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
+                        height + blurSizeOutline, (c) -> drawDragView(c, scale)));
     }
 
     public final void generateDragOutline(Bitmap preview) {
@@ -129,7 +136,7 @@
         return bounds;
     }
 
-    public float getScaleAndPosition(Bitmap preview, int[] outPos) {
+    public float getScaleAndPosition(Drawable preview, int[] outPos) {
         float scale = Launcher.getLauncher(mView.getContext())
                 .getDragLayer().getLocationInDragLayer(mView, outPos);
         if (mView instanceof LauncherAppWidgetHostView) {
@@ -139,8 +146,8 @@
         }
 
         outPos[0] = Math.round(outPos[0] -
-                (preview.getWidth() - scale * mView.getWidth() * mView.getScaleX()) / 2);
-        outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getHeight() / 2
+                (preview.getIntrinsicWidth() - scale * mView.getWidth() * mView.getScaleX()) / 2);
+        outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getIntrinsicHeight() / 2
                 - previewPadding / 2);
         return scale;
     }
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 61f2c2a..988794c 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -131,6 +131,7 @@
 
     /**
      * Fetches high-res icon for the provided ItemInfo and updates the caller when done.
+     *
      * @return a request ID that can be used to cancel the request.
      */
     public HandlerRunnable updateIconInBackground(final ItemInfoUpdateReceiver caller,
@@ -139,7 +140,7 @@
         if (mPendingIconRequestCount <= 0) {
             MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
         }
-        mPendingIconRequestCount ++;
+        mPendingIconRequestCount++;
 
         HandlerRunnable<ItemInfoWithIcon> request = new HandlerRunnable<>(mWorkerHandler,
                 () -> {
@@ -158,7 +159,7 @@
     }
 
     private void onIconRequestEnd() {
-        mPendingIconRequestCount --;
+        mPendingIconRequestCount--;
         if (mPendingIconRequestCount <= 0) {
             MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
         }
@@ -289,7 +290,8 @@
             @NonNull Supplier<LauncherActivityInfo> activityInfoProvider,
             boolean usePkgIcon, boolean useLowResIcon) {
         CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user,
-                activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon, useLowResIcon);
+                activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon,
+                useLowResIcon);
         applyCacheEntry(entry, infoInOut);
     }
 
@@ -315,7 +317,8 @@
     }
 
     public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) {
-        cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(), info.getAppLabel());
+        cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(),
+                info.getAppLabel());
     }
 
     @Override
diff --git a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
index 6189dc9..a7cd10d 100644
--- a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
@@ -269,7 +269,7 @@
             lp.leftMargin = lp.rightMargin = 0;
             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
             lp.bottomMargin = grid.isTaskbarPresent
-                    ? grid.workspacePadding.bottom + insets.bottom
+                    ? grid.workspacePadding.bottom + grid.taskbarSize
                     : grid.hotseatBarSizePx + insets.bottom;
         }
         setLayoutParams(lp);
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 5a34d2a..15915e5 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -164,7 +164,9 @@
             reverseOrder(viewsToFlip);
         }
         onInflationComplete(reverseOrder);
-        addArrow();
+        if (shouldAddArrow()) {
+            addArrow();
+        }
         animateOpen();
     }
 
@@ -174,7 +176,9 @@
     protected void show() {
         setupForDisplay();
         onInflationComplete(false);
-        addArrow();
+        if (shouldAddArrow()) {
+            addArrow();
+        }
         animateOpen();
     }
 
@@ -233,6 +237,13 @@
     }
 
     /**
+     * Returns whether or not we should add the arrow.
+     */
+    protected boolean shouldAddArrow() {
+        return true;
+    }
+
+    /**
      * Provide the location of the target object relative to the dragLayer.
      */
     protected abstract void getTargetObjectLocation(Rect outPos);
@@ -392,13 +403,18 @@
         return getChildCount() > 0 ? getChildAt(0) : this;
     }
 
+    private int getArrowDuration() {
+        return shouldAddArrow()
+                ? getResources().getInteger(R.integer.config_popupArrowOpenCloseDuration)
+                : 0;
+    }
     private void animateOpen() {
         setVisibility(View.VISIBLE);
 
         final AnimatorSet openAnim = new AnimatorSet();
         final Resources res = getResources();
         final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
-        final long arrowDuration = res.getInteger(R.integer.config_popupArrowOpenCloseDuration);
+        final long arrowDuration = getArrowDuration();
         final TimeInterpolator revealInterpolator = ACCEL_DEACCEL;
 
         // Rectangular reveal.
@@ -460,7 +476,7 @@
         final Resources res = getResources();
         final TimeInterpolator revealInterpolator = ACCEL_DEACCEL;
         final long revealDuration = res.getInteger(R.integer.config_popupOpenCloseDuration);
-        final long arrowDuration = res.getInteger(R.integer.config_popupArrowOpenCloseDuration);
+        final long arrowDuration = getArrowDuration();
 
         // Hide the arrow
         Animator scaleArrow = ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 0)
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 577fe4a..e5424cf 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -45,6 +45,11 @@
     protected final T mTarget;
     protected final ItemInfo mItemInfo;
 
+    /**
+     * Indicates if it's invokable or not through some disabled UI
+     */
+    private boolean isEnabled = true;
+
     public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo) {
         mIconResId = iconResId;
         mLabelResId = labelResId;
@@ -83,6 +88,14 @@
                 mAccessibilityActionId, context.getText(mLabelResId));
     }
 
+    public void setEnabled(boolean enabled) {
+        isEnabled = enabled;
+    }
+
+    public boolean isEnabled() {
+        return isEnabled;
+    }
+
     public boolean hasHandlerForAction(int action) {
         return mAccessibilityActionId == action;
     }
diff --git a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
index 4653774..5b8d5bc 100644
--- a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
+++ b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
@@ -33,7 +33,7 @@
     V newViewHolder(ViewGroup parent);
 
     /** Populate UI references in {@link ViewHolder} with data. */
-    void bindViewHolder(V viewHolder, T data);
+    void bindViewHolder(V viewHolder, T data, int position);
 
     /**
      * Called when the view is recycled. Views are recycled in batches once they are sufficiently
diff --git a/src/com/android/launcher3/search/SearchAlgorithm.java b/src/com/android/launcher3/search/SearchAlgorithm.java
index 1665354..a1720c7 100644
--- a/src/com/android/launcher3/search/SearchAlgorithm.java
+++ b/src/com/android/launcher3/search/SearchAlgorithm.java
@@ -31,4 +31,9 @@
      * Cancels any active request.
      */
     void cancel(boolean interruptActiveRequests);
+
+    /**
+     * Cleans up after search is no longer needed.
+     */
+    default void destroy() {};
 }
diff --git a/src/com/android/launcher3/search/StringMatcherUtility.java b/src/com/android/launcher3/search/StringMatcherUtility.java
new file mode 100644
index 0000000..acab52b
--- /dev/null
+++ b/src/com/android/launcher3/search/StringMatcherUtility.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2021 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.search;
+
+import java.text.Collator;
+
+/**
+ * Utilities for matching query string to target string.
+ */
+public class StringMatcherUtility {
+
+    /**
+     * Returns {@code true} is {@code query} is a prefix substring of a complete word/phrase in
+     * {@code target}.
+     */
+    public static boolean matches(String query, String target, StringMatcher matcher) {
+        int queryLength = query.length();
+
+        int targetLength = target.length();
+
+        if (targetLength < queryLength || queryLength <= 0) {
+            return false;
+        }
+
+        if (requestSimpleFuzzySearch(query)) {
+            return target.toLowerCase().contains(query);
+        }
+
+        int lastType;
+        int thisType = Character.UNASSIGNED;
+        int nextType = Character.getType(target.codePointAt(0));
+
+        int end = targetLength - queryLength;
+        for (int i = 0; i <= end; i++) {
+            lastType = thisType;
+            thisType = nextType;
+            nextType = i < (targetLength - 1)
+                    ? Character.getType(target.codePointAt(i + 1)) : Character.UNASSIGNED;
+            if (isBreak(thisType, lastType, nextType)
+                    && matcher.matches(query, target.substring(i, i + queryLength))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the current point should be a break point. Following cases
+     * are considered as break points:
+     *      1) Any non space character after a space character
+     *      2) Any digit after a non-digit character
+     *      3) Any capital character after a digit or small character
+     *      4) Any capital character before a small character
+     */
+    private static boolean isBreak(int thisType, int prevType, int nextType) {
+        switch (prevType) {
+            case Character.UNASSIGNED:
+            case Character.SPACE_SEPARATOR:
+            case Character.LINE_SEPARATOR:
+            case Character.PARAGRAPH_SEPARATOR:
+                return true;
+        }
+        switch (thisType) {
+            case Character.UPPERCASE_LETTER:
+                if (nextType == Character.UPPERCASE_LETTER) {
+                    return true;
+                }
+                // Follow through
+            case Character.TITLECASE_LETTER:
+                // Break point if previous was not a upper case
+                return prevType != Character.UPPERCASE_LETTER;
+            case Character.LOWERCASE_LETTER:
+                // Break point if previous was not a letter.
+                return prevType > Character.OTHER_LETTER || prevType <= Character.UNASSIGNED;
+            case Character.DECIMAL_DIGIT_NUMBER:
+            case Character.LETTER_NUMBER:
+            case Character.OTHER_NUMBER:
+                // Break point if previous was not a number
+                return !(prevType == Character.DECIMAL_DIGIT_NUMBER
+                        || prevType == Character.LETTER_NUMBER
+                        || prevType == Character.OTHER_NUMBER);
+            case Character.MATH_SYMBOL:
+            case Character.CURRENCY_SYMBOL:
+            case Character.OTHER_PUNCTUATION:
+            case Character.DASH_PUNCTUATION:
+                // Always a break point for a symbol
+                return true;
+            default:
+                return  false;
+        }
+    }
+
+    /**
+     * Performs locale sensitive string comparison using {@link Collator}.
+     */
+    public static class StringMatcher {
+
+        private static final char MAX_UNICODE = '\uFFFF';
+
+        private final Collator mCollator;
+
+        StringMatcher() {
+            // On android N and above, Collator uses ICU implementation which has a much better
+            // support for non-latin locales.
+            mCollator = Collator.getInstance();
+            mCollator.setStrength(Collator.PRIMARY);
+            mCollator.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
+        }
+
+        /**
+         * Returns true if {@param query} is a prefix of {@param target}
+         */
+        public boolean matches(String query, String target) {
+            switch (mCollator.compare(query, target)) {
+                case 0:
+                    return true;
+                case -1:
+                    // The target string can contain a modifier which would make it larger than
+                    // the query string (even though the length is same). If the query becomes
+                    // larger after appending a unicode character, it was originally a prefix of
+                    // the target string and hence should match.
+                    return mCollator.compare(query + MAX_UNICODE, target) > -1;
+                default:
+                    return false;
+            }
+        }
+
+        public static StringMatcher getInstance() {
+            return new StringMatcher();
+        }
+    }
+
+    /**
+     * Matching optimization to search in Chinese.
+     */
+    private static boolean requestSimpleFuzzySearch(String s) {
+        for (int i = 0; i < s.length(); ) {
+            int codepoint = s.codePointAt(i);
+            i += Character.charCount(codepoint);
+            switch (Character.UnicodeScript.of(codepoint)) {
+                case HAN:
+                    //Character.UnicodeScript.HAN: use String.contains to match
+                    return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 0e8d4ae..f5e74b7 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -17,7 +17,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.app.ActivityOptions;
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
@@ -168,11 +167,6 @@
     }
 
     @Override
-    public ActivityOptions getActivityLaunchOptions(View v) {
-        return null;
-    }
-
-    @Override
     protected void reapplyUi() { }
 
     @Override
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index c9fb956..8d676c9 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -88,7 +88,6 @@
     private PreferenceCategory mPluginsCategory;
     private FlagTogglerPrefUi mFlagTogglerPrefUi;
 
-
     @Override
     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
index 3e59b61..530aaed 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
@@ -23,6 +23,7 @@
 import android.graphics.drawable.Drawable;
 import android.view.View;
 
+import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
@@ -42,15 +43,16 @@
     }
 
     @Override
-    public Bitmap createDragBitmap() {
+    public Drawable createDrawable() {
         if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
             int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx;
-            return BitmapRenderer.createHardwareBitmap(
-                    size + blurSizeOutline,
-                    size + blurSizeOutline,
-                    (c) -> drawDragViewOnBackground(c, size));
+            return new FastBitmapDrawable(
+                    BitmapRenderer.createHardwareBitmap(
+                            size + blurSizeOutline,
+                            size + blurSizeOutline,
+                            (c) -> drawDragViewOnBackground(c, size)));
         } else {
-            return createDragBitmapLegacy();
+            return new FastBitmapDrawable(createDragBitmapLegacy());
         }
     }
 
@@ -81,7 +83,7 @@
     }
 
     @Override
-    public float getScaleAndPosition(Bitmap preview, int[] outPos) {
+    public float getScaleAndPosition(Drawable preview, int[] outPos) {
         Launcher launcher = Launcher.getLauncher(mView.getContext());
         int iconSize = getDrawableBounds(mView.getBackground()).width();
         float scale = launcher.getDragLayer().getLocationInDragLayer(mView, outPos);
@@ -91,9 +93,10 @@
             iconLeft = mView.getWidth() - iconSize - iconLeft;
         }
 
-        outPos[0] += Math.round(scale * iconLeft + (scale * iconSize - preview.getWidth()) / 2 +
-                mPositionShift.x);
-        outPos[1] += Math.round((scale * mView.getHeight() - preview.getHeight()) / 2
+        outPos[0] += Math.round(
+                scale * iconLeft + (scale * iconSize - preview.getIntrinsicWidth()) / 2
+                        + mPositionShift.x);
+        outPos[1] += Math.round((scale * mView.getHeight() - preview.getIntrinsicHeight()) / 2
                 + mPositionShift.y);
         float size = launcher.getDeviceProfile().iconSizePx;
         return scale * iconSize / size;
diff --git a/src/com/android/launcher3/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java
index daec1d8..122573c 100644
--- a/src/com/android/launcher3/statemanager/BaseState.java
+++ b/src/com/android/launcher3/statemanager/BaseState.java
@@ -17,6 +17,8 @@
 
 import android.content.Context;
 
+import com.android.launcher3.DeviceProfile;
+
 /**
  * Interface representing a state of a StatefulActivity
  */
@@ -52,4 +54,11 @@
      * Returns if the state has the provided flag
      */
     boolean hasFlag(int flagMask);
+
+    /**
+     * For this state, whether tasks should layout as a grid rather than a list.
+     */
+    default boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
+        return false;
+    }
 }
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 2b51e97..51767e7 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -77,6 +77,15 @@
         return mCurrentStableState;
     }
 
+    @Override
+    public String toString() {
+        return " StateManager(mLastStableState:" + mLastStableState
+                + ", mCurrentStableState:" + mCurrentStableState
+                + ", mState:" + mState
+                + ", mRestState:" + mRestState
+                + ", isInTransition:" + (mConfig.currentAnimation != null) + ")";
+    }
+
     public void dump(String prefix, PrintWriter writer) {
         writer.println(prefix + "StateManager:");
         writer.println(prefix + "\tmLastStableState:" + mLastStableState);
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 7cb6e34..f34bff6 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -32,6 +32,7 @@
     public static final int ALL_APPS_STATE_ORDINAL = 5;
     public static final int BACKGROUND_APP_STATE_ORDINAL = 6;
     public static final int HINT_STATE_ORDINAL = 7;
+    public static final int OVERVIEW_SPLIT_SELECT_ORDINAL = 8;
     public static final String TAPL_EVENTS_TAG = "TaplEvents";
     public static final String SEQUENCE_MAIN = "Main";
     public static final String SEQUENCE_TIS = "TIS";
@@ -55,6 +56,8 @@
                 return "Background";
             case HINT_STATE_ORDINAL:
                 return "Hint";
+            case OVERVIEW_SPLIT_SELECT_ORDINAL:
+                return "OverviewSplitSelect";
             default:
                 return "Unknown";
         }
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 61bd30a..2e54904 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -180,7 +180,7 @@
                 LauncherApps launcherApps = launcher.getSystemService(LauncherApps.class);
                 try {
                     launcherApps.startPackageInstallerSessionDetailsActivity(sessionInfo, null,
-                            launcher.getActivityLaunchOptionsAsBundle(v));
+                            launcher.getActivityLaunchOptions(v).toBundle());
                     return;
                 } catch (Exception e) {
                     Log.e(TAG, "Unable to launch market intent for package=" + packageName, e);
@@ -304,7 +304,7 @@
                 intent.setPackage(null);
             }
         }
-        if (v != null && launcher.getAppTransitionManager().supportsAdaptiveIconAnimation(v)) {
+        if (v != null && launcher.supportsAdaptiveIconAnimation(v)) {
             // Preload the icon to reduce latency b/w swapping the floating view with the original.
             FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */);
         }
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 8a64f3d..c1cf0c8 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -21,6 +21,10 @@
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_SIDE;
 
 import android.content.res.Resources;
 import android.graphics.PointF;
@@ -36,8 +40,13 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+
+import java.util.ArrayList;
+import java.util.List;
 
 public class LandscapePagedViewHandler implements PagedOrientationHandler {
 
@@ -212,6 +221,20 @@
     }
 
     @Override
+    public int getSplitTranslationDirectionFactor(int stagePosition) {
+        if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+            return -1;
+        } else {
+            return 1;
+        }
+    }
+
+    @Override
+    public int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp) {
+        return translationOffset;
+    }
+
+    @Override
     public float getTaskMenuX(float x, View thumbnailView) {
         return thumbnailView.getMeasuredWidth() + x;
     }
@@ -282,4 +305,23 @@
     public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
         return rect.left;
     }
+
+    @Override
+    public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
+        List<SplitPositionOption> options = new ArrayList<>(2);
+        // Add left/right options where left => position top, right => position bottom
+        options.add(new SplitPositionOption(
+                R.drawable.ic_split_screen, R.string.split_screen_position_left,
+                STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+        options.add(new SplitPositionOption(
+                R.drawable.ic_split_screen, R.string.split_screen_position_right,
+                STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_SIDE));
+        return options;
+    }
+
+    @Override
+    public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
+            DeviceProfile deviceProfile) {
+        return primary;
+    }
 }
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index e1cec87..fcfa205 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -32,6 +32,10 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+
+import java.util.List;
 
 /**
  * Abstraction layer to separate horizontal and vertical specific implementations
@@ -75,6 +79,8 @@
     int getScrollOffsetEnd(View view, Rect insets);
     int getPrimaryTranslationDirectionFactor();
     int getSecondaryTranslationDirectionFactor();
+    int getSplitTranslationDirectionFactor(@StagePosition int stagePosition);
+    int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp);
     ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild);
     void setMaxScroll(AccessibilityEvent event, int maxScroll);
     boolean getRecentsRtlSetting(Resources resources);
@@ -95,6 +101,9 @@
     int getTaskMenuLayoutOrientation(boolean canRecentsActivityRotate, LinearLayout taskMenuLayout);
     void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp);
     int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
+    List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp);
+    FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
+            DeviceProfile deviceProfile);
 
     // The following are only used by TaskViewTouchHandler.
     /** @return Either VERTICAL or HORIZONTAL. */
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index bcaf5f4..2bc2dc7 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -19,6 +19,10 @@
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_SIDE;
 
 import android.content.res.Resources;
 import android.graphics.PointF;
@@ -34,8 +38,13 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+
+import java.util.ArrayList;
+import java.util.List;
 
 public class PortraitPagedViewHandler implements PagedOrientationHandler {
 
@@ -208,6 +217,23 @@
     }
 
     @Override
+    public int getSplitTranslationDirectionFactor(int stagePosition) {
+        if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+            return -1;
+        } else {
+            return 1;
+        }
+    }
+
+    @Override
+    public int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp) {
+        if (dp.isLandscape) {
+            return translationOffset;
+        }
+        return 0;
+    }
+
+    @Override
     public float getTaskMenuX(float x, View thumbnailView) {
         return x;
     }
@@ -277,4 +303,35 @@
     public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
         return dp.heightPx - rect.bottom;
     }
+
+    @Override
+    public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
+        List<SplitPositionOption> options = new ArrayList<>(2);
+        // TODO: Add in correct icons
+        if (dp.isLandscape) { // or seascape
+            // Add left/right options
+            options.add(new SplitPositionOption(
+                    R.drawable.ic_split_screen, R.string.split_screen_position_left,
+                    STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+            options.add(new SplitPositionOption(
+                    R.drawable.ic_split_screen, R.string.split_screen_position_right,
+                    STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_SIDE));
+        } else {
+            // Only add top option
+            options.add(new SplitPositionOption(
+                    R.drawable.ic_split_screen, R.string.split_screen_position_top,
+                    STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+        }
+        return options;
+    }
+
+    @Override
+    public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
+            DeviceProfile dp) {
+        if (dp.isLandscape) { // or seascape
+            return primary;
+        } else {
+            return secondary;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 54af029..b5252f7 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -17,6 +17,10 @@
 package com.android.launcher3.touch;
 
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_SIDE;
 
 import android.content.res.Resources;
 import android.graphics.PointF;
@@ -25,7 +29,12 @@
 import android.view.View;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+
+import java.util.ArrayList;
+import java.util.List;
 
 public class SeascapePagedViewHandler extends LandscapePagedViewHandler {
 
@@ -35,6 +44,20 @@
     }
 
     @Override
+    public int getSplitTranslationDirectionFactor(int stagePosition) {
+        if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+            return -1;
+        } else {
+            return 1;
+        }
+    }
+
+    @Override
+    public int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp) {
+        return translationOffset;
+    }
+
+    @Override
     public boolean getRecentsRtlSetting(Resources resources) {
         return Utilities.isRtl(resources);
     }
@@ -71,6 +94,19 @@
         return dp.widthPx - rect.right;
     }
 
+    @Override
+    public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
+        List<SplitPositionOption> options = new ArrayList<>(2);
+        // Add left/right options where left => position bottom, right => position top
+        options.add(new SplitPositionOption(
+                R.drawable.ic_split_screen, R.string.split_screen_position_left,
+                STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_SIDE));
+        options.add(new SplitPositionOption(
+                R.drawable.ic_split_screen, R.string.split_screen_position_right,
+                STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
+        return options;
+    }
+
     /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
 
     @Override
diff --git a/src/com/android/launcher3/util/ActivityOptionsWrapper.java b/src/com/android/launcher3/util/ActivityOptionsWrapper.java
new file mode 100644
index 0000000..99cc1f7
--- /dev/null
+++ b/src/com/android/launcher3/util/ActivityOptionsWrapper.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.util;
+
+
+import android.app.ActivityOptions;
+import android.os.Bundle;
+
+/**
+ * A wrapper around {@link ActivityOptions} to allow custom functionality in launcher
+ */
+public class ActivityOptionsWrapper {
+
+    public final ActivityOptions options;
+    public final RunnableList onEndCallback;
+
+    public ActivityOptionsWrapper(ActivityOptions options, RunnableList onEndCallback) {
+        this.options = options;
+        this.onEndCallback = onEndCallback;
+    }
+
+    /**
+     * @see {@link ActivityOptions#toBundle()}
+     */
+    public Bundle toBundle() {
+        return options.toBundle();
+    }
+}
diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java
index 5be9529..0ea0290 100644
--- a/src/com/android/launcher3/util/MultiValueAlpha.java
+++ b/src/com/android/launcher3/util/MultiValueAlpha.java
@@ -42,16 +42,43 @@
                 }
             };
 
+    /**
+     * Determines how each alpha should factor into the final alpha.
+     */
+    public enum Mode {
+        BLEND() {
+            @Override
+            public float calculateNewAlpha(float currentAlpha, float otherAlpha) {
+                return currentAlpha * otherAlpha;
+            }
+        },
+
+        MAX() {
+            @Override
+            public float calculateNewAlpha(float currentAlpha, float otherAlpha) {
+                return Math.max(currentAlpha, otherAlpha);
+            }
+        };
+
+        protected abstract float calculateNewAlpha(float currentAlpha, float otherAlpha);
+    }
+
     private final View mView;
     private final AlphaProperty[] mMyProperties;
+    private final Mode mMode;
 
     private int mValidMask;
     // Whether we should change from INVISIBLE to VISIBLE and vice versa at low alpha values.
     private boolean mUpdateVisibility;
 
     public MultiValueAlpha(View view, int size) {
+        this(view, size, Mode.BLEND);
+    }
+
+    public MultiValueAlpha(View view, int size, Mode mode) {
         mView = view;
         mMyProperties = new AlphaProperty[size];
+        mMode = mode;
 
         mValidMask = 0;
         for (int i = 0; i < size; i++) {
@@ -97,7 +124,7 @@
                 mOthers = 1;
                 for (AlphaProperty prop : mMyProperties) {
                     if (prop != this) {
-                        mOthers *= prop.mValue;
+                        mOthers = mMode.calculateNewAlpha(mOthers, prop.mValue);
                     }
                 }
             }
@@ -107,7 +134,7 @@
             mValidMask = mMyMask;
             mValue = value;
 
-            mView.setAlpha(mOthers * mValue);
+            mView.setAlpha(mMode.calculateNewAlpha(mOthers, mValue));
             if (mUpdateVisibility) {
                 AlphaUpdateListener.updateVisibility(mView);
             }
diff --git a/src/com/android/launcher3/util/RunnableList.java b/src/com/android/launcher3/util/RunnableList.java
new file mode 100644
index 0000000..55add14
--- /dev/null
+++ b/src/com/android/launcher3/util/RunnableList.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 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.util;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to hold a list of runnable
+ */
+public class RunnableList {
+
+    private ArrayList<Runnable> mList = null;
+    private boolean mDestroyed = false;
+
+    /**
+     * Ads a runnable to this list
+     */
+    public void add(Runnable runnable) {
+        if (mDestroyed) {
+            runnable.run();
+            return;
+        }
+        if (mList == null) {
+            mList = new ArrayList<>();
+        }
+        mList.add(runnable);
+    }
+
+    /**
+     * Destroys the list, executing any pending callbacks. All new callbacks are
+     * immediately executed
+     */
+    public void executeAllAndDestroy() {
+        mDestroyed = true;
+        executeAllAndClear();
+    }
+
+    /**
+     * Executes all previously added runnable and clears the list
+     */
+    public void executeAllAndClear() {
+        if (mList != null) {
+            ArrayList<Runnable> list = mList;
+            mList = null;
+            int count = list.size();
+            for (int i = 0; i < count; i++) {
+                list.get(i).run();
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
new file mode 100644
index 0000000..573c8bd
--- /dev/null
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2021 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.util;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+
+public final class SplitConfigurationOptions {
+
+    ///////////////////////////////////
+    // Taken from
+    // frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+    /**
+     * Stage position isn't specified normally meaning to use what ever it is currently set to.
+     */
+    public static final int STAGE_POSITION_UNDEFINED = -1;
+    /**
+     * Specifies that a stage is positioned at the top half of the screen if
+     * in portrait mode or at the left half of the screen if in landscape mode.
+     */
+    public static final int STAGE_POSITION_TOP_OR_LEFT = 0;
+
+    /**
+     * Specifies that a stage is positioned at the bottom half of the screen if
+     * in portrait mode or at the right half of the screen if in landscape mode.
+     */
+    public static final int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
+
+    @Retention(SOURCE)
+    @IntDef({STAGE_POSITION_UNDEFINED, STAGE_POSITION_TOP_OR_LEFT, STAGE_POSITION_BOTTOM_OR_RIGHT})
+    public @interface StagePosition {}
+
+    /**
+     * Stage type isn't specified normally meaning to use what ever the default is.
+     * E.g. exit split-screen and launch the app in fullscreen.
+     */
+    public static final int STAGE_TYPE_UNDEFINED = -1;
+    /**
+     * The main stage type.
+     */
+    public static final int STAGE_TYPE_MAIN = 0;
+
+    /**
+     * The side stage type.
+     */
+    public static final int STAGE_TYPE_SIDE = 1;
+
+    @IntDef({STAGE_TYPE_UNDEFINED, STAGE_TYPE_MAIN, STAGE_TYPE_SIDE})
+    public @interface StageType {}
+    ///////////////////////////////////
+
+    public static class SplitPositionOption {
+        public final int mIconResId;
+        public final int mTextResId;
+        @StagePosition
+        public final int mStagePosition;
+
+        @StageType
+        public final int mStageType;
+
+        public SplitPositionOption(int iconResId, int textResId, int stagePosition, int stageType) {
+            mIconResId = iconResId;
+            mTextResId = textResId;
+            mStagePosition = stagePosition;
+            mStageType = stageType;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java
index 5b33f18..e413d7f 100644
--- a/src/com/android/launcher3/util/ViewPool.java
+++ b/src/com/android/launcher3/util/ViewPool.java
@@ -58,7 +58,7 @@
         Preconditions.assertUIThread();
         Handler handler = new Handler();
 
-        // LayoutInflater is not thread save as it maintains a global variable 'mConstructorArgs'.
+        // LayoutInflater is not thread safe as it maintains a global variable 'mConstructorArgs'.
         // Create a different copy to use on the background thread.
         LayoutInflater inflater = mInflater.cloneInContext(mInflater.getContext());
 
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index 2ad80cf..b8554e4 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -145,14 +145,17 @@
         msg.sendToTarget();
     }
 
-    private void updateOffset() {
-        int numPagesForWallpaperParallax;
+    /** Returns the number of pages used for the wallpaper parallax. */
+    public int getNumPagesForWallpaperParallax() {
         if (mWallpaperIsLiveWallpaper) {
-            numPagesForWallpaperParallax = mNumScreens;
+            return mNumScreens;
         } else {
-            numPagesForWallpaperParallax = Math.max(MIN_PARALLAX_PAGE_SPAN, mNumScreens);
+            return Math.max(MIN_PARALLAX_PAGE_SPAN, mNumScreens);
         }
-        Message.obtain(mHandler, MSG_SET_NUM_PARALLAX, numPagesForWallpaperParallax, 0,
+    }
+
+    private void updateOffset() {
+        Message.obtain(mHandler, MSG_SET_NUM_PARALLAX, getNumPagesForWallpaperParallax(), 0,
                 mWindowToken).sendToTarget();
     }
 
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 899dcf7..1a114f3 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -114,6 +114,11 @@
     }
 
     @Override
+    protected boolean shouldAddArrow() {
+        return false;
+    }
+
+    @Override
     protected void getTargetObjectLocation(Rect outPos) {
         mTargetRect.roundOut(outPos);
     }
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index c9bd284..7f0765b 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -32,7 +32,6 @@
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 import com.android.launcher3.uioverrides.WallpaperColorInfo.OnChangeListener;
 import com.android.launcher3.util.Themes;
@@ -42,7 +41,6 @@
  */
 public class ScrimView<T extends Launcher> extends View implements Insettable, OnChangeListener {
 
-    private static final float SCRIM_ALPHA = .95f;
     protected final T mLauncher;
     private final WallpaperColorInfo mWallpaperColorInfo;
     protected final int mEndScrim;
@@ -61,12 +59,7 @@
         super(context, attrs);
         mLauncher = Launcher.cast(Launcher.getLauncher(context));
         mWallpaperColorInfo = WallpaperColorInfo.INSTANCE.get(context);
-        int endScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
-        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
-            endScrim = Themes.getColorBackgroundFloating(context);
-            endScrim = ColorUtils.setAlphaComponent(endScrim, (int) (255  * SCRIM_ALPHA));
-        }
-        mEndScrim = endScrim;
+        mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
         mIsScrimDark = ColorUtils.calculateLuminance(mEndScrim) < 0.5f;
 
         mMaxScrimAlpha = 0.7f;
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 03c58bb..fc63af0 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -74,7 +74,18 @@
 
     @Override
     public final void onClick(View v) {
-        mWidgetInstructionToast = showWidgetToast(getContext(), mWidgetInstructionToast);
+        Object tag = null;
+        if (v instanceof WidgetCell) {
+            tag = v.getTag();
+        } else if (v.getParent() instanceof WidgetCell) {
+            tag = ((WidgetCell) v.getParent()).getTag();
+        }
+        if (tag instanceof PendingAddShortcutInfo) {
+            mWidgetInstructionToast = showShortcutToast(getContext(), mWidgetInstructionToast);
+        } else {
+            mWidgetInstructionToast = showWidgetToast(getContext(), mWidgetInstructionToast);
+        }
+
     }
 
     @Override
@@ -97,17 +108,18 @@
 
         // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
         // we abort the drag.
-        if (image.getBitmap() == null) {
+        if (image.getDrawable() == null) {
             return false;
         }
 
         PendingItemDragHelper dragHelper = new PendingItemDragHelper(v);
-        dragHelper.setPreview(v.getPreview());
+        dragHelper.setRemoteViewsPreview(v.getPreview());
+        dragHelper.setAppWidgetHostViewPreview(v.getAppWidgetHostViewPreview());
 
         int[] loc = new int[2];
         getPopupContainer().getLocationInDragLayer(image, loc);
 
-        dragHelper.startDrag(image.getBitmapBounds(), image.getBitmap().getWidth(),
+        dragHelper.startDrag(image.getBitmapBounds(), image.getDrawable().getIntrinsicWidth(),
                 image.getWidth(), new Point(loc[0], loc[1]), this, new DragOptions());
         close(true);
         return true;
@@ -158,4 +170,21 @@
         toast.show();
         return toast;
     }
+
+    /**
+     * Show shortcut tap toast prompting user to drag instead.
+     */
+    private static Toast showShortcutToast(Context context, Toast toast) {
+        // Let the user know that they have to long press to add a widget
+        if (toast != null) {
+            toast.cancel();
+        }
+
+        CharSequence msg = Utilities.wrapForTts(
+                context.getText(R.string.long_press_shortcut_to_add),
+                context.getString(R.string.long_accessible_way_to_add_shortcut));
+        toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT);
+        toast.show();
+        return toast;
+    }
 }
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 3285c18..8df70fb 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -19,9 +19,13 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Handler;
 import android.os.SystemClock;
+import android.util.Log;
 import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -32,22 +36,31 @@
 import android.widget.Advanceable;
 import android.widget.RemoteViews;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.CheckLongPressHelper;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
+import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
+
+import java.util.List;
 
 /**
  * {@inheritDoc}
  */
 public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
-        implements TouchCompleteListener, View.OnLongClickListener {
+        implements TouchCompleteListener, View.OnLongClickListener,
+        LocalColorExtractor.Listener {
+
+    private static final String LOG_TAG = "LauncherAppWidgetHostView";
 
     // Related to the auto-advancing of widgets
     private static final long ADVANCE_INTERVAL = 20000;
@@ -60,18 +73,32 @@
 
     private final CheckLongPressHelper mLongPressHelper;
     protected final Launcher mLauncher;
+    private final Workspace mWorkspace;
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mReinflateOnConfigChange;
 
+    // Maintain the color manager.
+    private final LocalColorExtractor mColorExtractor;
+
     private boolean mIsScrollable;
     private boolean mIsAttachedToWindow;
     private boolean mIsAutoAdvanceRegistered;
+    private boolean mIsInDragMode = false;
     private Runnable mAutoAdvanceRunnable;
+    private RectF mLastLocationRegistered = null;
+    @Nullable private AppWidgetHostViewDragListener mDragListener;
+
+    // Used to store the widget size during onLayout.
+    private final Rect mCurrentWidgetSize = new Rect();
+    private final Rect mWidgetSizeAtDrag = new Rect();
+    private final RectF mTempRectF = new RectF();
+    private final boolean mIsRtl;
 
     public LauncherAppWidgetHostView(Context context) {
         super(context);
         mLauncher = Launcher.getLauncher(context);
+        mWorkspace = mLauncher.getWorkspace();
         mLongPressHelper = new CheckLongPressHelper(this, this);
         mInflater = LayoutInflater.from(context);
         setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
@@ -81,12 +108,28 @@
         if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) {
             setOnLightBackground(true);
         }
+        mIsRtl = Utilities.isRtl(context.getResources());
+        mColorExtractor = LocalColorExtractor.newInstance(getContext());
+        mColorExtractor.setListener(this);
+    }
+
+    @Override
+    public void setColorResources(@Nullable SparseIntArray colors) {
+        if (colors == null) {
+            resetColorResources();
+        } else {
+            super.setColorResources(colors);
+        }
+
+        if (mDragListener != null) {
+            mDragListener.onDragContentChanged();
+        }
     }
 
     @Override
     public boolean onLongClick(View view) {
         if (mIsScrollable) {
-            DragLayer dragLayer = Launcher.getLauncher(getContext()).getDragLayer();
+            DragLayer dragLayer = mLauncher.getDragLayer();
             dragLayer.requestDisallowInterceptTouchEvent(false);
         }
         view.performLongClick();
@@ -135,7 +178,7 @@
 
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            DragLayer dragLayer = Launcher.getLauncher(getContext()).getDragLayer();
+            DragLayer dragLayer = mLauncher.getDragLayer();
             if (mIsScrollable) {
                 dragLayer.requestDisallowInterceptTouchEvent(true);
             }
@@ -167,6 +210,7 @@
         // state is updated. So isAttachedToWindow() will return true until next frame.
         mIsAttachedToWindow = false;
         checkIfAutoAdvance();
+        mColorExtractor.removeLocations();
     }
 
     @Override
@@ -213,6 +257,97 @@
         }
 
         mIsScrollable = checkScrollableRecursively(this);
+
+        if (!mIsInDragMode && getTag() instanceof LauncherAppWidgetInfo) {
+            mCurrentWidgetSize.left = left;
+            mCurrentWidgetSize.top = top;
+            mCurrentWidgetSize.right = right;
+            mCurrentWidgetSize.bottom = bottom;
+            LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag();
+            int pageId = mWorkspace.getPageIndexForScreenId(info.screenId);
+            updateColorExtraction(mCurrentWidgetSize, pageId);
+        }
+    }
+
+    /** Starts the drag mode. */
+    public void startDrag(AppWidgetHostViewDragListener dragListener) {
+        mIsInDragMode = true;
+        mDragListener = dragListener;
+    }
+
+    /** Handles a drag event occurred on a workspace page, {@code pageId}. */
+    public void handleDrag(Rect rect, int pageId) {
+        mWidgetSizeAtDrag.set(rect);
+        updateColorExtraction(mWidgetSizeAtDrag, pageId);
+    }
+
+    /** Ends the drag mode. */
+    public void endDrag() {
+        mIsInDragMode = false;
+        mDragListener = null;
+        mWidgetSizeAtDrag.setEmpty();
+        requestLayout();
+    }
+
+    private void updateColorExtraction(Rect widgetLocation, int pageId) {
+        // If the widget hasn't been measured and laid out, we cannot do this.
+        if (widgetLocation.isEmpty()) {
+            return;
+        }
+        int screenWidth = mLauncher.getDeviceProfile().widthPx;
+        int screenHeight = mLauncher.getDeviceProfile().heightPx;
+        int numScreens = mWorkspace.getNumPagesForWallpaperParallax();
+        pageId = mIsRtl ? numScreens - pageId - 1 : pageId;
+        float relativeScreenWidth = 1f / numScreens;
+        float absoluteTop = widgetLocation.top;
+        float absoluteBottom = widgetLocation.bottom;
+        for (View v = (View) getParent();
+                v != null && v.getId() != R.id.launcher;
+                v = (View) v.getParent()) {
+            absoluteBottom += v.getTop();
+            absoluteTop += v.getTop();
+        }
+        float xOffset = 0;
+        View parentView = (View) getParent();
+        // The layout depends on the orientation.
+        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            int parentViewWidth = parentView == null ? 0 : parentView.getWidth();
+            xOffset = screenHeight - mWorkspace.getPaddingRight() - parentViewWidth;
+        } else {
+            int parentViewPaddingLeft = parentView == null ? 0 : parentView.getPaddingLeft();
+            xOffset = mWorkspace.getPaddingLeft() + parentViewPaddingLeft;
+        }
+        // This is the position of the widget relative to the wallpaper, as expected by the
+        // local color extraction of the WallpaperManager.
+        // The coordinate system is such that, on the horizontal axis, each screen has a
+        // distinct range on the [0,1] segment. So if there are 3 screens, they will have the
+        // ranges [0, 1/3], [1/3, 2/3] and [2/3, 1]. The position on the subrange should be
+        // the position of the widget relative to the screen. For the vertical axis, this is
+        // simply the location of the widget relative to the screen.
+        mTempRectF.left = ((widgetLocation.left + xOffset) / screenWidth + pageId)
+                * relativeScreenWidth;
+        mTempRectF.right = ((widgetLocation.right + xOffset) / screenWidth + pageId)
+                * relativeScreenWidth;
+        mTempRectF.top = absoluteTop / screenHeight;
+        mTempRectF.bottom = absoluteBottom / screenHeight;
+        if (mTempRectF.left < 0 || mTempRectF.right > 1 || mTempRectF.top < 0
+                || mTempRectF.bottom > 1) {
+            Log.e(LOG_TAG, "   Error, invalid relative position");
+            return;
+        }
+        if (!mTempRectF.equals(mLastLocationRegistered)) {
+            if (mLastLocationRegistered != null) {
+                mColorExtractor.removeLocations();
+            }
+            mLastLocationRegistered = new RectF(mTempRectF);
+            mColorExtractor.addLocation(List.of(mLastLocationRegistered));
+        }
+    }
+
+    @Override
+    public void onColorsChanged(RectF rectF, SparseIntArray colors) {
+        // setColorResources will reapply the view, which must happen in the UI thread.
+        post(() -> setColorResources(colors));
     }
 
     @Override
@@ -225,6 +360,14 @@
     protected void onWindowVisibilityChanged(int visibility) {
         super.onWindowVisibilityChanged(visibility);
         maybeRegisterAutoAdvance();
+
+        if (visibility == View.VISIBLE) {
+            if (mLastLocationRegistered != null) {
+                mColorExtractor.addLocation(List.of(mLastLocationRegistered));
+            }
+        } else {
+            mColorExtractor.removeLocations();
+        }
     }
 
     private void checkIfAutoAdvance() {
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
index ce97d2e..8689fbf 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.widget;
 
+import static com.android.launcher3.Utilities.ATLEAST_S;
+
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
@@ -33,6 +35,8 @@
     public int spanY;
     public int minSpanX;
     public int minSpanY;
+    public int maxSpanX;
+    public int maxSpanY;
 
     public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,
             AppWidgetProviderInfo info) {
@@ -78,15 +82,40 @@
                 || !idp.portraitProfile.shouldInsetWidgets()) {
             AppWidgetHostView.getDefaultPaddingForWidget(context, provider, widgetPadding);
         }
-        spanX = Math.max(1, (int) Math.ceil(
-                        (minWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
-        spanY = Math.max(1, (int) Math.ceil(
-                (minHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
 
-        minSpanX = Math.max(1, (int) Math.ceil(
-                (minResizeWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
-        minSpanY = Math.max(1, (int) Math.ceil(
-                (minResizeHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
+        minSpanX = getSpanX(widgetPadding, minResizeWidth, smallestCellWidth);
+        minSpanY = getSpanY(widgetPadding, minResizeHeight, smallestCellHeight);
+
+        // Use maxResizeWidth/Height if they are defined and we're on S or above.
+        maxSpanX =
+                (ATLEAST_S && maxResizeWidth > 0)
+                        ? getSpanX(widgetPadding, maxResizeWidth, smallestCellWidth)
+                        : idp.numColumns;
+        maxSpanY =
+                (ATLEAST_S && maxResizeHeight > 0)
+                        ? getSpanY(widgetPadding, maxResizeHeight, smallestCellHeight)
+                        : idp.numRows;
+
+        // Use targetCellWidth/Height if it is within the min/max ranges and we're on S or above.
+        // Otherwise, fall back to minWidth/Height.
+        if (ATLEAST_S && targetCellWidth >= minSpanX && targetCellWidth <= maxSpanX
+                && targetCellHeight >= minSpanY && targetCellHeight <= maxSpanY) {
+            spanX = targetCellWidth;
+            spanY = targetCellHeight;
+        } else {
+            spanX = getSpanX(widgetPadding, minWidth, smallestCellWidth);
+            spanY = getSpanY(widgetPadding, minHeight, smallestCellHeight);
+        }
+    }
+
+    private int getSpanX(Rect widgetPadding, int widgetWidth, float cellWidth) {
+        return Math.max(1, (int) Math.ceil(
+                (widgetWidth + widgetPadding.left + widgetPadding.right) / cellWidth));
+    }
+
+    private int getSpanY(Rect widgetPadding, int widgetHeight, float cellHeight) {
+        return Math.max(1, (int) Math.ceil(
+                (widgetHeight + widgetPadding.top + widgetPadding.bottom) / cellHeight));
     }
 
     public String getLabel(PackageManager packageManager) {
@@ -124,4 +153,4 @@
     public Drawable getFullResIcon(IconCache cache) {
         return cache.getFullResIcon(provider.getPackageName(), icon);
     }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/LocalColorExtractor.java b/src/com/android/launcher3/widget/LocalColorExtractor.java
new file mode 100644
index 0000000..097158b
--- /dev/null
+++ b/src/com/android/launcher3/widget/LocalColorExtractor.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2009 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.widget;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.Context;
+import android.graphics.RectF;
+import android.util.SparseIntArray;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+import java.util.List;
+
+/** Extracts the colors we need from the wallpaper at given locations. */
+public class LocalColorExtractor implements ResourceBasedOverride {
+
+    /** Listener for color changes on a screen location. */
+    public interface Listener {
+        /**
+         * Method called when the colors on a registered location has changed.
+         *
+         * {@code extractedColors} maps the color resources {@code android.R.colors.system_*} to
+         * their value, in a format that can be passed directly to
+         * {@link AppWidgetHostView#setColorResources(SparseIntArray)}.
+         */
+        void onColorsChanged(RectF rect, SparseIntArray extractedColors);
+    }
+
+    static LocalColorExtractor newInstance(Context context) {
+        return Overrides.getObject(LocalColorExtractor.class, context.getApplicationContext(),
+                R.string.local_colors_extraction_class);
+    }
+
+    /** Sets the object that will receive the color changes. */
+    public void setListener(@Nullable Listener listener) {
+        // no-op
+    }
+
+    /** Adds a list of locations to track with this listener. */
+    public void addLocation(List<RectF> locations) {
+        // no-op
+    }
+
+    /** Stops tracking any locations. */
+    public void removeLocations() {
+        // no-op
+    }
+}
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 6e83836..247a748 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -29,14 +29,17 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DragSource;
+import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
 
 /**
  * Extension of {@link DragPreviewProvider} with logic specific to pending widgets/shortcuts
@@ -49,15 +52,26 @@
     private final PendingAddItemInfo mAddInfo;
     private int[] mEstimatedCellSize;
 
-    @Nullable private RemoteViews mPreview;
+    @Nullable private RemoteViews mRemoteViewsPreview;
+    @Nullable private LauncherAppWidgetHostView mAppWidgetHostViewPreview;
 
     public PendingItemDragHelper(View view) {
         super(view);
         mAddInfo = (PendingAddItemInfo) view.getTag();
     }
 
-    public void setPreview(@Nullable RemoteViews preview) {
-        mPreview = preview;
+    /**
+     * Sets a {@link RemoteViews} which shows an app widget preview provided by app developers in
+     * the pin widget flow.
+     */
+    public void setRemoteViewsPreview(@Nullable RemoteViews remoteViewsPreview) {
+        mRemoteViewsPreview = remoteViewsPreview;
+    }
+
+    /** Sets a {@link LauncherAppWidgetHostView} which shows a preview layout of an app widget. */
+    public void setAppWidgetHostViewPreview(
+            @Nullable LauncherAppWidgetHostView appWidgetHostViewPreview) {
+        mAppWidgetHostViewPreview = appWidgetHostViewPreview;
     }
 
     /**
@@ -74,7 +88,7 @@
         final Launcher launcher = Launcher.getLauncher(mView.getContext());
         LauncherAppState app = LauncherAppState.getInstance(launcher);
 
-        Bitmap preview = null;
+        Drawable preview = null;
         final float scale;
         final Point dragOffset;
         final Rect dragRegion;
@@ -90,13 +104,21 @@
 
             int[] previewSizeBeforeScale = new int[1];
 
-            if (mPreview != null) {
-                preview = WidgetCell.generateFromRemoteViews(launcher, mPreview,
-                        createWidgetInfo.info, maxWidth, previewSizeBeforeScale);
+            if (mRemoteViewsPreview != null) {
+                preview = new FastBitmapDrawable(
+                        WidgetCell.generateFromRemoteViews(launcher, mRemoteViewsPreview,
+                                createWidgetInfo.info, maxWidth, previewSizeBeforeScale));
+            }
+            if (mAppWidgetHostViewPreview != null) {
+                preview = new AppWidgetHostViewDrawable(mAppWidgetHostViewPreview);
+                launcher.getDragController()
+                        .addDragListener(new AppWidgetHostViewDragListener(launcher));
             }
             if (preview == null) {
-                preview = app.getWidgetCache().generateWidgetPreview(launcher,
-                        createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale).first;
+                preview = new FastBitmapDrawable(
+                        app.getWidgetCache().generateWidgetPreview(launcher,
+                                createWidgetInfo.info, maxWidth, null,
+                                previewSizeBeforeScale).first);
             }
 
             if (previewSizeBeforeScale[0] < previewBitmapWidth) {
@@ -109,7 +131,7 @@
                 previewBounds.left += padding;
                 previewBounds.right -= padding;
             }
-            scale = previewBounds.width() / (float) preview.getWidth();
+            scale = previewBounds.width() / (float) preview.getIntrinsicWidth();
             launcher.getDragController().addDragListener(new WidgetHostViewLoader(launcher, mView));
 
             dragOffset = null;
@@ -119,9 +141,10 @@
             PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo;
             Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache());
             LauncherIcons li = LauncherIcons.obtain(launcher);
-            preview = li.createScaledBitmapWithoutShadow(icon, 0);
+            preview = new FastBitmapDrawable(
+                    li.createScaledBitmapWithoutShadow(icon, 0));
             li.recycle();
-            scale = ((float) launcher.getDeviceProfile().iconSizePx) / preview.getWidth();
+            scale = ((float) launcher.getDeviceProfile().iconSizePx) / preview.getIntrinsicWidth();
 
             dragOffset = new Point(previewPadding / 2, previewPadding / 2);
 
@@ -149,9 +172,9 @@
         launcher.getWorkspace().prepareDragWithProvider(this);
 
         int dragLayerX = screenPos.x + previewBounds.left
-                + (int) ((scale * preview.getWidth() - preview.getWidth()) / 2);
+                + (int) ((scale * preview.getIntrinsicWidth() - preview.getIntrinsicWidth()) / 2);
         int dragLayerY = screenPos.y + previewBounds.top
-                + (int) ((scale * preview.getHeight() - preview.getHeight()) / 2);
+                + (int) ((scale * preview.getIntrinsicHeight() - preview.getIntrinsicHeight()) / 2);
 
         // Start the drag
         launcher.getDragController().startDrag(preview, draggableView, dragLayerX, dragLayerY,
diff --git a/src/com/android/launcher3/widget/WidgetAddFlowHandler.java b/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
index 1ac5a33..9313266 100644
--- a/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
+++ b/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.widget;
 
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE;
+
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.os.Parcel;
@@ -78,8 +81,22 @@
         return true;
     }
 
+    /**
+     * Checks whether the widget needs configuration.
+     *
+     * A widget needs configuration if (1) it has a configuration activity and (2)
+     * it's configuration is not optional.
+     *
+     * @return true if the widget needs configuration, false otherwise.
+     */
     public boolean needsConfigure() {
-        return mProviderInfo.configure != null;
+        int featureFlags = mProviderInfo.widgetFeatures;
+        // A widget's configuration is optional only if it's configuration is marked as optional AND
+        // it can be reconfigured later.
+        boolean configurationOptional = (featureFlags & WIDGET_FEATURE_CONFIGURATION_OPTIONAL) != 0
+                && (featureFlags & WIDGET_FEATURE_RECONFIGURABLE) != 0;
+
+        return mProviderInfo.configure != null && !configurationOptional;
     }
 
     public LauncherAppWidgetProviderInfo getProviderInfo(Context context) {
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 229df50..5328041 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -18,10 +18,10 @@
 
 import static com.android.launcher3.Utilities.ATLEAST_S;
 
-import android.appwidget.AppWidgetHostView;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
 import android.os.CancellationSignal;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -35,11 +35,15 @@
 import android.widget.RemoteViews;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.CheckLongPressHelper;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.R;
 import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.model.WidgetItem;
@@ -84,14 +88,14 @@
     private boolean mAnimatePreview = true;
 
     private boolean mApplyBitmapDeferred = false;
-    private Bitmap mDeferredBitmap;
+    private Drawable mDeferredDrawable;
 
     protected final BaseActivity mActivity;
     protected final DeviceProfile mDeviceProfile;
     private final CheckLongPressHelper mLongPressHelper;
 
     private RemoteViews mPreview;
-    private AppWidgetHostView mPreviewAppWidgetHostView;
+    private LauncherAppWidgetHostView mAppWidgetHostViewPreview;
 
     public WidgetCell(Context context) {
         this(context, null);
@@ -147,7 +151,7 @@
             Log.d(TAG, "reset called on:" + mWidgetName.getText());
         }
         mWidgetImage.animate().cancel();
-        mWidgetImage.setBitmap(null, null);
+        mWidgetImage.setDrawable(null, null);
         mWidgetName.setText(null);
         mWidgetDims.setText(null);
         mWidgetDescription.setText(null);
@@ -159,7 +163,7 @@
             mActiveRequest = null;
         }
         mPreview = null;
-        mPreviewAppWidgetHostView = null;
+        mAppWidgetHostViewPreview = null;
     }
 
     public void applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader) {
@@ -194,7 +198,7 @@
                 && mPreview == null
                 && item.widgetInfo != null
                 && item.widgetInfo.previewLayout != Resources.ID_NULL) {
-            mPreviewAppWidgetHostView = new AppWidgetHostView(getContext());
+            mAppWidgetHostViewPreview = new LauncherAppWidgetHostView(getContext());
             LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
                     LauncherAppWidgetProviderInfo.fromProviderInfo(getContext(),
                             item.widgetInfo.clone());
@@ -202,11 +206,11 @@
             // rendering a preview layout for work profile apps yet. For non-work profile layout, a
             // proper solution is to use RemoteViews(PackageName, LayoutId).
             launcherAppWidgetProviderInfo.initialLayout = item.widgetInfo.previewLayout;
-            mPreviewAppWidgetHostView.setAppWidget(/* appWidgetId= */ -1,
+            mAppWidgetHostViewPreview.setAppWidget(/* appWidgetId= */ -1,
                     launcherAppWidgetProviderInfo);
-            mPreviewAppWidgetHostView.setPadding(/* left= */ 0, /* top= */0, /* right= */
+            mAppWidgetHostViewPreview.setPadding(/* left= */ 0, /* top= */0, /* right= */
                     0, /* bottom= */ 0);
-            mPreviewAppWidgetHostView.updateAppWidget(/* remoteViews= */ null);
+            mAppWidgetHostViewPreview.updateAppWidget(/* remoteViews= */ null);
         }
     }
 
@@ -214,6 +218,11 @@
         return mWidgetImage;
     }
 
+    @Nullable
+    public LauncherAppWidgetHostView getAppWidgetHostViewPreview() {
+        return mAppWidgetHostViewPreview;
+    }
+
     /**
      * Sets if applying bitmap preview should be deferred. The UI will still load the bitmap, but
      * will not cause invalidate, so that when deferring is disabled later, all the bitmaps are
@@ -223,9 +232,9 @@
     public void setApplyBitmapDeferred(boolean isDeferred) {
         if (mApplyBitmapDeferred != isDeferred) {
             mApplyBitmapDeferred = isDeferred;
-            if (!mApplyBitmapDeferred && mDeferredBitmap != null) {
-                applyPreview(mDeferredBitmap);
-                mDeferredBitmap = null;
+            if (!mApplyBitmapDeferred && mDeferredDrawable != null) {
+                applyPreview(mDeferredDrawable);
+                mDeferredDrawable = null;
             }
         }
     }
@@ -235,17 +244,21 @@
     }
 
     public void applyPreview(Bitmap bitmap) {
+        applyPreview(new FastBitmapDrawable(bitmap));
+    }
+
+    private void applyPreview(Drawable drawable) {
         if (mApplyBitmapDeferred) {
-            mDeferredBitmap = bitmap;
+            mDeferredDrawable = drawable;
             return;
         }
-        if (bitmap != null) {
+        if (drawable != null) {
             LayoutParams layoutParams = (LayoutParams) mWidgetImage.getLayoutParams();
-            layoutParams.width = bitmap.getWidth();
-            layoutParams.height = bitmap.getHeight();
+            layoutParams.width = drawable.getIntrinsicWidth();
+            layoutParams.height = drawable.getIntrinsicHeight();
             mWidgetImage.setLayoutParams(layoutParams);
 
-            mWidgetImage.setBitmap(bitmap, mWidgetPreviewLoader.getBadgeForUser(mItem.user,
+            mWidgetImage.setDrawable(drawable, mWidgetPreviewLoader.getBadgeForUser(mItem.user,
                     BaseIconFactory.getBadgeSizeForIconSize(mDeviceProfile.allAppsIconSizePx)));
             if (mAnimatePreview) {
                 mWidgetImage.setAlpha(0f);
@@ -262,18 +275,26 @@
             Bitmap preview = generateFromRemoteViews(
                     mActivity, mPreview, mItem.widgetInfo, mPresetPreviewSize, new int[1]);
             if (preview != null) {
-                applyPreview(preview);
+                applyPreview(new FastBitmapDrawable(preview));
                 return;
             }
         }
 
-        if (mPreviewAppWidgetHostView != null) {
-            Bitmap preview = generateFromView(mActivity, mPreviewAppWidgetHostView,
-                    mItem.widgetInfo, mPreviewWidth, new int[1]);
-            if (preview != null) {
-                applyPreview(preview);
-                return;
-            }
+        if (mAppWidgetHostViewPreview != null) {
+            DeviceProfile dp = mActivity.getDeviceProfile();
+            int viewWidth = dp.cellWidthPx * mItem.spanX;
+            int viewHeight = dp.cellHeightPx * mItem.spanY;
+
+            mAppWidgetHostViewPreview.measure(
+                    MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));
+
+            viewWidth = mAppWidgetHostViewPreview.getMeasuredWidth();
+            viewHeight = mAppWidgetHostViewPreview.getMeasuredHeight();
+            mAppWidgetHostViewPreview.layout(0, 0, viewWidth, viewHeight);
+            Drawable drawable = new AppWidgetHostViewDrawable(mAppWidgetHostViewPreview);
+            applyPreview(drawable);
+            return;
         }
         if (mActiveRequest != null) {
             return;
diff --git a/src/com/android/launcher3/widget/WidgetImageView.java b/src/com/android/launcher3/widget/WidgetImageView.java
index df2bcff..39d701c 100644
--- a/src/com/android/launcher3/widget/WidgetImageView.java
+++ b/src/com/android/launcher3/widget/WidgetImageView.java
@@ -17,9 +17,7 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
@@ -35,11 +33,10 @@
  */
 public class WidgetImageView extends View {
 
-    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
     private final RectF mDstRectF = new RectF();
     private final int mBadgeMargin;
 
-    private Bitmap mBitmap;
+    private Drawable mDrawable;
     private Drawable mBadge;
 
     public WidgetImageView(Context context) {
@@ -57,21 +54,22 @@
                 .getDimensionPixelSize(R.dimen.profile_badge_margin);
     }
 
-    public void setBitmap(Bitmap bitmap, Drawable badge) {
-        mBitmap = bitmap;
+    public void setDrawable(Drawable drawable, Drawable badge) {
+        mDrawable = drawable;
         mBadge = badge;
         invalidate();
     }
 
-    public Bitmap getBitmap() {
-        return mBitmap;
+    public Drawable getDrawable() {
+        return mDrawable;
     }
 
     @Override
     protected void onDraw(Canvas canvas) {
-        if (mBitmap != null) {
+        if (mDrawable != null) {
             updateDstRectF();
-            canvas.drawBitmap(mBitmap, null, mDstRectF, mPaint);
+            mDrawable.setBounds(getBitmapBounds());
+            mDrawable.draw(canvas);
 
             // Only draw the badge if a preview was drawn.
             if (mBadge != null) {
@@ -91,11 +89,11 @@
     private void updateDstRectF() {
         float myWidth = getWidth();
         float myHeight = getHeight();
-        float bitmapWidth = mBitmap.getWidth();
+        float bitmapWidth = mDrawable.getIntrinsicWidth();
 
         final float scale = bitmapWidth > myWidth ? myWidth / bitmapWidth : 1;
         float scaledWidth = bitmapWidth * scale;
-        float scaledHeight = mBitmap.getHeight() * scale;
+        float scaledHeight = mDrawable.getIntrinsicHeight() * scale;
 
         mDstRectF.left = (myWidth - scaledWidth) / 2;
         mDstRectF.right = (myWidth + scaledWidth) / 2;
diff --git a/src/com/android/launcher3/widget/dragndrop/AppWidgetHostViewDragListener.java b/src/com/android/launcher3/widget/dragndrop/AppWidgetHostViewDragListener.java
new file mode 100644
index 0000000..c5e6fbd
--- /dev/null
+++ b/src/com/android/launcher3/widget/dragndrop/AppWidgetHostViewDragListener.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 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.widget.dragndrop;
+
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+
+/** A drag listener of {@link LauncherAppWidgetHostView}. */
+public final class AppWidgetHostViewDragListener implements DragController.DragListener {
+    private final Launcher mLauncher;
+    private DropTarget.DragObject mDragObject;
+    private AppWidgetHostViewDrawable mAppWidgetHostViewDrawable;
+    private LauncherAppWidgetHostView mAppWidgetHostView;
+
+    public AppWidgetHostViewDragListener(Launcher launcher) {
+        mLauncher = launcher;
+    }
+
+    @Override
+    public void onDragStart(DropTarget.DragObject dragObject, DragOptions unused) {
+        if (dragObject.dragView.getDrawable() instanceof AppWidgetHostViewDrawable) {
+            mDragObject = dragObject;
+            mAppWidgetHostViewDrawable =
+                    (AppWidgetHostViewDrawable) mDragObject.dragView.getDrawable();
+            mAppWidgetHostView = mAppWidgetHostViewDrawable.getAppWidgetHostView();
+            mAppWidgetHostView.startDrag(this);
+        } else {
+            mLauncher.getDragController().removeDragListener(this);
+        }
+    }
+
+    @Override
+    public void onDragEnd() {
+        mAppWidgetHostView.endDrag();
+        mLauncher.getDragController().removeDragListener(this);
+    }
+
+    /** Notifies when there is a content change in the drag view. */
+    public void onDragContentChanged() {
+        mDragObject.dragView.invalidate();
+    }
+}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
index 09517e1..73bae6f 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
@@ -20,10 +20,14 @@
 
 import androidx.annotation.IntDef;
 
+import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.widget.WidgetItemComparator;
 
 import java.lang.annotation.Retention;
+import java.util.List;
+import java.util.stream.Collectors;
 
 /** Holder class to store the package information of an entry shown in the widgets list. */
 public abstract class WidgetsListBaseEntry {
@@ -35,9 +39,14 @@
      */
     public final String mTitleSectionName;
 
-    public WidgetsListBaseEntry(PackageItemInfo pkgItem, String titleSectionName) {
+    public final List<WidgetItem> mWidgets;
+
+    public WidgetsListBaseEntry(PackageItemInfo pkgItem, String titleSectionName,
+            List<WidgetItem> items) {
         mPkgItem = pkgItem;
         mTitleSectionName = titleSectionName;
+        this.mWidgets =
+                items.stream().sorted(new WidgetItemComparator()).collect(Collectors.toList());
     }
 
     /**
@@ -51,10 +60,11 @@
     public abstract int getRank();
 
     @Retention(SOURCE)
-    @IntDef({RANK_WIDGETS_LIST_HEADER, RANK_WIDGETS_LIST_CONTENT})
+    @IntDef({RANK_WIDGETS_LIST_HEADER, RANK_WIDGETS_LIST_SEARCH_HEADER, RANK_WIDGETS_LIST_CONTENT})
     public @interface Rank {
     }
 
     public static final int RANK_WIDGETS_LIST_HEADER = 1;
-    public static final int RANK_WIDGETS_LIST_CONTENT = 2;
+    public static final int RANK_WIDGETS_LIST_SEARCH_HEADER = 2;
+    public static final int RANK_WIDGETS_LIST_CONTENT = 3;
 }
diff --git a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
index b0cb8c7..0328cf6 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
@@ -17,10 +17,8 @@
 
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.widget.WidgetItemComparator;
 
 import java.util.List;
-import java.util.stream.Collectors;
 
 /**
  * Holder class to store all the information related to a list of widgets from the same app which is
@@ -28,18 +26,14 @@
  */
 public final class WidgetsListContentEntry extends WidgetsListBaseEntry {
 
-    public final List<WidgetItem> mWidgets;
-
     public WidgetsListContentEntry(PackageItemInfo pkgItem, String titleSectionName,
             List<WidgetItem> items) {
-        super(pkgItem, titleSectionName);
-        this.mWidgets =
-                items.stream().sorted(new WidgetItemComparator()).collect(Collectors.toList());
+        super(pkgItem, titleSectionName, items);
     }
 
     @Override
     public String toString() {
-        return mPkgItem.packageName + ":" + mWidgets.size();
+        return "Content:" + mPkgItem.packageName + ":" + mWidgets.size();
     }
 
     @Override
@@ -47,4 +41,12 @@
     public int getRank() {
         return RANK_WIDGETS_LIST_CONTENT;
     }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof WidgetsListContentEntry)) return false;
+        WidgetsListContentEntry otherEntry = (WidgetsListContentEntry) obj;
+        return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem)
+                && mTitleSectionName.equals(otherEntry.mTitleSectionName);
+    }
 }
diff --git a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
index 6899647..1fdc399 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
@@ -18,7 +18,7 @@
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.PackageItemInfo;
 
-import java.util.Collection;
+import java.util.List;
 
 /** An information holder for an app which has widgets or/and shortcuts. */
 public final class WidgetsListHeaderEntry extends WidgetsListBaseEntry {
@@ -30,8 +30,8 @@
     private boolean mHasEntryUpdated = false;
 
     public WidgetsListHeaderEntry(PackageItemInfo pkgItem, String titleSectionName,
-            Collection<WidgetItem> items) {
-        super(pkgItem, titleSectionName);
+            List<WidgetItem> items) {
+        super(pkgItem, titleSectionName, items);
         widgetsCount = (int) items.stream().filter(item -> item.widgetInfo != null).count();
         shortcutsCount = Math.max(0, items.size() - widgetsCount);
     }
@@ -57,8 +57,21 @@
     }
 
     @Override
+    public String toString() {
+        return "Header:" + mPkgItem.packageName + ":" + mWidgets.size();
+    }
+
+    @Override
     @Rank
     public int getRank() {
         return RANK_WIDGETS_LIST_HEADER;
     }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof WidgetsListHeaderEntry)) return false;
+        WidgetsListHeaderEntry otherEntry = (WidgetsListHeaderEntry) obj;
+        return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem)
+                && mTitleSectionName.equals(otherEntry.mTitleSectionName);
+    }
 }
diff --git a/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java
new file mode 100644
index 0000000..2aec3f8
--- /dev/null
+++ b/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 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.widget.model;
+
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.data.PackageItemInfo;
+
+import java.util.List;
+
+/** An information holder for an app which has widgets or/and shortcuts, to be shown in search. */
+public final class WidgetsListSearchHeaderEntry extends WidgetsListBaseEntry {
+
+    private boolean mIsWidgetListShown = false;
+    private boolean mHasEntryUpdated = false;
+
+    public WidgetsListSearchHeaderEntry(PackageItemInfo pkgItem, String titleSectionName,
+            List<WidgetItem> items) {
+        super(pkgItem, titleSectionName, items);
+    }
+
+    /** Sets if the widgets list associated with this header is shown. */
+    public void setIsWidgetListShown(boolean isWidgetListShown) {
+        if (mIsWidgetListShown != isWidgetListShown) {
+            this.mIsWidgetListShown = isWidgetListShown;
+            mHasEntryUpdated = true;
+        } else {
+            mHasEntryUpdated = false;
+        }
+    }
+
+    /** Returns {@code true} if the widgets list associated with this header is shown. */
+    public boolean isWidgetListShown() {
+        return mIsWidgetListShown;
+    }
+
+    /** Returns {@code true} if this entry has been updated due to user interactions. */
+    public boolean hasEntryUpdated() {
+        return mHasEntryUpdated;
+    }
+
+    @Override
+    public String toString() {
+        return "SearchHeader:" + mPkgItem.packageName + ":" + mWidgets.size();
+    }
+
+    @Override
+    @Rank
+    public int getRank() {
+        return RANK_WIDGETS_LIST_SEARCH_HEADER;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof WidgetsListSearchHeaderEntry)) return false;
+        WidgetsListSearchHeaderEntry otherEntry = (WidgetsListSearchHeaderEntry) obj;
+        return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem)
+                && mTitleSectionName.equals(otherEntry.mTitleSectionName);
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/OnHeaderClickListener.java b/src/com/android/launcher3/widget/picker/OnHeaderClickListener.java
new file mode 100644
index 0000000..7372751
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/OnHeaderClickListener.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 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.widget.picker;
+
+import com.android.launcher3.util.PackageUserKey;
+
+/**
+ * A listener to be invoked when a header is clicked.
+ */
+public interface OnHeaderClickListener {
+    /**
+     * Calls when a header is clicked to show / hide widgets for a package.
+     */
+    void onHeaderClicked(boolean showWidgets, PackageUserKey packageUserKey);
+}
diff --git a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
index 95fa05f..7eb5b83 100644
--- a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
+++ b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
@@ -34,6 +34,7 @@
     private final boolean mHasWorkProfile;
     private final SearchAndRecommendationViewHolder mViewHolder;
     private final WidgetsRecyclerView mPrimaryRecyclerView;
+    private final WidgetsRecyclerView mSearchRecyclerView;
 
     // The following are only non null if mHasWorkProfile is true.
     @Nullable private final WidgetsRecyclerView mWorkRecyclerView;
@@ -48,12 +49,14 @@
             SearchAndRecommendationViewHolder viewHolder,
             WidgetsRecyclerView primaryRecyclerView,
             @Nullable WidgetsRecyclerView workRecyclerView,
+            WidgetsRecyclerView searchRecyclerView,
             @Nullable View personalWorkTabsView,
             @Nullable PersonalWorkPagedView primaryWorkViewPager) {
         mHasWorkProfile = hasWorkProfile;
         mViewHolder = viewHolder;
         mPrimaryRecyclerView = primaryRecyclerView;
         mWorkRecyclerView = workRecyclerView;
+        mSearchRecyclerView = searchRecyclerView;
         mPrimaryWorkTabsView = personalWorkTabsView;
         mPrimaryWorkViewPager = primaryWorkViewPager;
         mCurrentRecyclerView = mPrimaryRecyclerView;
@@ -149,6 +152,11 @@
                     mPrimaryRecyclerView.getPaddingRight(),
                     mPrimaryRecyclerView.getPaddingBottom());
         }
+        mSearchRecyclerView.setPadding(
+                mSearchRecyclerView.getPaddingLeft(),
+                topContainerHeight,
+                mSearchRecyclerView.getPaddingRight(),
+                mSearchRecyclerView.getPaddingBottom());
     }
 
     /**
diff --git a/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java
index dbd1bdf..2366609 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java
@@ -25,6 +25,7 @@
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
 import com.android.launcher3.widget.picker.WidgetsListAdapter.WidgetListBaseRowEntryComparator;
 
 import java.util.ArrayList;
@@ -113,7 +114,7 @@
                 // or did the header view changed due to user interactions?
                 // or did the widget size and desc, span, etc change?
                 if (!isSamePackageItemInfo(orgRowEntry.mPkgItem, newRowEntry.mPkgItem)
-                        || hasHeaderUpdated(newRowEntry)
+                        || hasHeaderUpdated(orgRowEntry, newRowEntry)
                         || hasWidgetsListChanged(orgRowEntry, newRowEntry)) {
                     index = currentEntries.indexOf(orgRowEntry);
                     currentEntries.set(index, newRowEntry);
@@ -174,12 +175,16 @@
      * Returns {@code true} if {@code newRow} is {@link WidgetsListHeaderEntry} and its content has
      * been changed due to user interactions.
      */
-    private boolean hasHeaderUpdated(WidgetsListBaseEntry newRow) {
-        if (!(newRow instanceof WidgetsListHeaderEntry)) {
-            return false;
+    private boolean hasHeaderUpdated(WidgetsListBaseEntry curRow, WidgetsListBaseEntry newRow) {
+        if (newRow instanceof WidgetsListHeaderEntry && curRow instanceof WidgetsListHeaderEntry) {
+            return ((WidgetsListHeaderEntry) newRow).hasEntryUpdated() || !curRow.equals(newRow);
         }
-        WidgetsListHeaderEntry newRowEntry = (WidgetsListHeaderEntry) newRow;
-        return newRowEntry.hasEntryUpdated();
+        if (newRow instanceof WidgetsListSearchHeaderEntry
+                && curRow instanceof WidgetsListSearchHeaderEntry) {
+            return ((WidgetsListSearchHeaderEntry) newRow).hasEntryUpdated()
+                    || !curRow.equals(newRow);
+        }
+        return false;
     }
 
     private boolean isSamePackageItemInfo(PackageItemInfo curInfo, PackageItemInfo newInfo) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index bf9b849..5747690 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -34,7 +34,6 @@
 import android.view.View;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
-import android.widget.EditText;
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
@@ -53,9 +52,12 @@
 import com.android.launcher3.widget.BaseWidgetSheet;
 import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.picker.search.SearchModeListener;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
 import com.android.launcher3.workprofile.PersonalWorkPagedView;
 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Predicate;
 
@@ -64,7 +66,7 @@
  */
 public class WidgetsFullSheet extends BaseWidgetSheet
         implements Insettable, ProviderChangedListener, OnActivePageChangedListener,
-        WidgetsRecyclerView.HeaderViewDimensionsProvider {
+        WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener {
 
     private static final long DEFAULT_OPEN_DURATION = 267;
     private static final long FADE_IN_DURATION = 150;
@@ -81,7 +83,9 @@
 
     @Nullable private PersonalWorkPagedView mViewPager;
     private int mInitialTabsHeight = 0;
+    private boolean mIsInSearchMode;
     private View mTabsView;
+    private TextView mNoWidgetsView;
     private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
     private SearchAndRecommendationsScrollController mSearchAndRecommendationsScrollController;
 
@@ -90,6 +94,7 @@
         mHasWorkProfile = context.getSystemService(LauncherApps.class).getProfiles().size() > 1;
         mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
         mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
+        mAdapters.put(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
     }
 
     public WidgetsFullSheet(Context context, AttributeSet attrs) {
@@ -137,33 +142,66 @@
                 mSearchAndRecommendationViewHolder,
                 findViewById(R.id.primary_widgets_list_view),
                 mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null,
+                findViewById(R.id.search_widgets_list_view),
                 mTabsView,
                 mViewPager);
         fastScroller.setOnFastScrollChangeListener(mSearchAndRecommendationsScrollController);
 
+        mNoWidgetsView = findViewById(R.id.no_widgets_text);
+
         onWidgetsBound();
+
+        mSearchAndRecommendationViewHolder.mSearchBar.initialize(
+                mLauncher.getPopupDataProvider().getAllWidgets(), /* searchModeListener= */ this);
     }
 
     @Override
     public void onActivePageChanged(int currentActivePage) {
+        AdapterHolder currentAdapterHolder = mAdapters.get(currentActivePage);
         WidgetsRecyclerView currentRecyclerView =
                 mAdapters.get(currentActivePage).mWidgetsRecyclerView;
-        currentRecyclerView.bindFastScrollbar();
-        mSearchAndRecommendationsScrollController.setCurrentRecyclerView(currentRecyclerView);
 
+        updateNoWidgetsView(currentAdapterHolder);
+        attachScrollbarToRecyclerView(currentRecyclerView);
+        resetExpandedHeaders();
+    }
+
+    private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) {
+        recyclerView.bindFastScrollbar();
+        mSearchAndRecommendationsScrollController.setCurrentRecyclerView(recyclerView);
         reset();
     }
 
+    private void updateNoWidgetsView(AdapterHolder adapterHolder) {
+        boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.getItemCount() > 0;
+        adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
+
+        // Always resets the text in case this is updated by search.
+        mNoWidgetsView.setText(R.string.no_widgets_available);
+        mNoWidgetsView.setVisibility(isWidgetAvailable ? GONE : VISIBLE);
+    }
+
+    private void updateNoSearchResultsView(boolean isVisible) {
+        mNoWidgetsView.setVisibility(isVisible ? VISIBLE : GONE);
+        if (isVisible) {
+            mNoWidgetsView.setText(R.string.no_search_results);
+        }
+    }
+
     private void reset() {
         mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop();
         if (mHasWorkProfile) {
             mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
         }
+        mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
         mSearchAndRecommendationsScrollController.reset();
     }
 
     @VisibleForTesting
     public WidgetsRecyclerView getRecyclerView() {
+        if (mIsInSearchMode) {
+            return mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView;
+        }
         if (!mHasWorkProfile || mViewPager.getCurrentPage() == AdapterHolder.PRIMARY) {
             return mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
         }
@@ -275,7 +313,11 @@
 
         AdapterHolder primaryUserAdapterHolder = mAdapters.get(AdapterHolder.PRIMARY);
         primaryUserAdapterHolder.setup(findViewById(R.id.primary_widgets_list_view));
+        AdapterHolder searchAdapterHolder = mAdapters.get(AdapterHolder.SEARCH);
+        searchAdapterHolder.setup(findViewById(R.id.search_widgets_list_view));
         primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
+        updateNoWidgetsView(primaryUserAdapterHolder);
+
         if (mHasWorkProfile) {
             AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
             workUserAdapterHolder.setup(findViewById(R.id.work_widgets_list_view));
@@ -284,6 +326,50 @@
         }
     }
 
+    @Override
+    public void enterSearchMode() {
+        if (mIsInSearchMode) return;
+        setViewVisibilityBasedOnSearch(/*isInSearchMode= */ true);
+        attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView);
+        resetExpandedHeaders();
+    }
+
+    @Override
+    public void exitSearchMode() {
+        onSearchResults(new ArrayList<>());
+        setViewVisibilityBasedOnSearch(/*isInSearchMode=*/ false);
+        if (mHasWorkProfile) {
+            mViewPager.snapToPage(AdapterHolder.PRIMARY);
+        }
+        attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView);
+    }
+
+    @Override
+    public void onSearchResults(List<WidgetsListBaseEntry> entries) {
+        mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setWidgetsOnSearch(entries);
+        updateNoSearchResultsView(
+                mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.getItemCount() == 0);
+    }
+
+    private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
+        mIsInSearchMode = isInSearchMode;
+        if (mHasWorkProfile) {
+            mViewPager.setVisibility(isInSearchMode ? GONE : VISIBLE);
+            mTabsView.setVisibility(isInSearchMode ? GONE : VISIBLE);
+        } else {
+            mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView
+                    .setVisibility(isInSearchMode ? GONE : VISIBLE);
+        }
+        mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView
+                .setVisibility(mIsInSearchMode ? VISIBLE : GONE);
+        mNoWidgetsView.setVisibility(GONE);
+    }
+
+    private void resetExpandedHeaders() {
+        mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.resetExpandedHeader();
+        mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.resetExpandedHeader();
+    }
+
     private void open(boolean animate) {
         if (animate) {
             if (getPopupContainer().getInsets().bottom > 0) {
@@ -369,14 +455,17 @@
 
     @Override
     public int getHeaderViewHeight() {
-        // No need to check work profile here because mInitialTabHeight is always 0 if there is no
-        // work profile.
-        return mInitialTabsHeight
-                + measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mContainer);
+        return measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mCollapseHandle)
+                + measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mHeaderTitle)
+                + measureHeightWithVerticalMargins(
+                (View) mSearchAndRecommendationViewHolder.mSearchBar);
     }
 
     /** private the height, in pixel, + the vertical margins of a given view. */
     private static int measureHeightWithVerticalMargins(View view) {
+        if (view.getVisibility() != VISIBLE) {
+            return 0;
+        }
         MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
         return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
                 + marginLayoutParams.topMargin;
@@ -386,6 +475,7 @@
     private final class AdapterHolder {
         static final int PRIMARY = 0;
         static final int WORK = 1;
+        static final int SEARCH = 2;
 
         private final int mAdapterType;
         private final WidgetsListAdapter mWidgetsListAdapter;
@@ -404,8 +494,16 @@
                     apps.getIconCache(),
                     /* iconClickListener= */ WidgetsFullSheet.this,
                     /* iconLongClickListener= */ WidgetsFullSheet.this);
-            mWidgetsListAdapter.setFilter(
-                    mAdapterType == PRIMARY ? mPrimaryWidgetsFilter : mWorkWidgetsFilter);
+            switch (mAdapterType) {
+                case PRIMARY:
+                    mWidgetsListAdapter.setFilter(mPrimaryWidgetsFilter);
+                    break;
+                case WORK:
+                    mWidgetsListAdapter.setFilter(mWorkWidgetsFilter);
+                    break;
+                default:
+                    break;
+            }
         }
 
         void setup(WidgetsRecyclerView recyclerView) {
@@ -421,7 +519,7 @@
     final class SearchAndRecommendationViewHolder {
         final View mContainer;
         final View mCollapseHandle;
-        final EditText mSearchBar;
+        final WidgetsSearchBar mSearchBar;
         final TextView mHeaderTitle;
 
         SearchAndRecommendationViewHolder(View searchAndRecommendationContainer) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index 8b49d1e..cab1e02 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.widget.picker;
 
 import android.content.Context;
+import android.os.Process;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
@@ -34,11 +35,12 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.recyclerview.ViewHolderBinder;
 import com.android.launcher3.util.LabelComparator;
+import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.widget.WidgetCell;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
-import com.android.launcher3.widget.picker.WidgetsListHeaderViewHolderBinder.OnHeaderClickListener;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
 
 import java.util.ArrayList;
 import java.util.Comparator;
@@ -62,8 +64,9 @@
     private static final boolean DEBUG = false;
 
     /** Uniquely identifies widgets list view type within the app. */
-    private static final int VIEW_TYPE_WIDGETS_LIST = R.layout.widgets_list_row_view;
-    private static final int VIEW_TYPE_WIDGETS_HEADER = R.layout.widgets_list_row_header;
+    private static final int VIEW_TYPE_WIDGETS_LIST = R.id.view_type_widgets_list;
+    private static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header;
+    private static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header;
 
     private final WidgetsDiffReporter mDiffReporter;
     private final SparseArray<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
@@ -73,11 +76,13 @@
 
     private List<WidgetsListBaseEntry> mAllEntries = new ArrayList<>();
     private ArrayList<WidgetsListBaseEntry> mVisibleEntries = new ArrayList<>();
-    @Nullable private String mWidgetsContentVisiblePackage = null;
+    @Nullable private PackageUserKey mWidgetsContentVisiblePackageUserKey = null;
 
     private Predicate<WidgetsListBaseEntry> mHeaderAndSelectedContentFilter = entry ->
             entry instanceof WidgetsListHeaderEntry
-                    || entry.mPkgItem.packageName.equals(mWidgetsContentVisiblePackage);
+                    || entry instanceof WidgetsListSearchHeaderEntry
+                    || new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)
+                            .equals(mWidgetsContentVisiblePackageUserKey);
     @Nullable private Predicate<WidgetsListBaseEntry> mFilter = null;
 
     public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
@@ -85,10 +90,17 @@
             OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
         mDiffReporter = new WidgetsDiffReporter(iconCache, this);
         mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(context,
-                layoutInflater, iconClickListener, iconLongClickListener, widgetPreviewLoader);
+                layoutInflater, iconClickListener, iconLongClickListener,
+                widgetPreviewLoader, /* listAdapter= */ this);
         mViewHolderBinders.put(VIEW_TYPE_WIDGETS_LIST, mWidgetsListTableViewHolderBinder);
-        mViewHolderBinders.put(VIEW_TYPE_WIDGETS_HEADER,
-                new WidgetsListHeaderViewHolderBinder(layoutInflater, this::onHeaderClicked));
+        mViewHolderBinders.put(
+                VIEW_TYPE_WIDGETS_HEADER,
+                new WidgetsListHeaderViewHolderBinder(
+                        layoutInflater, /* onHeaderClickListener= */this, /* listAdapter= */ this));
+        mViewHolderBinders.put(
+                VIEW_TYPE_WIDGETS_SEARCH_HEADER,
+                new WidgetsListSearchHeaderViewHolderBinder(
+                        layoutInflater, /*onHeaderClickListener=*/ this, /* listAdapter= */ this));
     }
 
     public void setFilter(Predicate<WidgetsListBaseEntry> filter) {
@@ -122,23 +134,40 @@
         return mVisibleEntries.size();
     }
 
+    /** Returns all items that will be drawn in a recycler view. */
+    public List<WidgetsListBaseEntry> getItems() {
+        return mVisibleEntries;
+    }
+
     /** Gets the section name for {@link com.android.launcher3.views.RecyclerViewFastScroller}. */
     public String getSectionName(int pos) {
         return mVisibleEntries.get(pos).mTitleSectionName;
     }
 
-    /** Updates the widget list. */
+    /** Updates the widget list based on {@code tempEntries}. */
     public void setWidgets(List<WidgetsListBaseEntry> tempEntries) {
         mAllEntries = tempEntries.stream().sorted(mRowComparator)
                 .collect(Collectors.toList());
         updateVisibleEntries();
     }
 
+    /** Updates the widget list based on {@code searchResults}. */
+    public void setWidgetsOnSearch(List<WidgetsListBaseEntry> searchResults) {
+        // Forget the expanded package every time widget list is refreshed in search mode.
+        mWidgetsContentVisiblePackageUserKey = null;
+        setWidgets(searchResults);
+    }
+
     private void updateVisibleEntries() {
         mAllEntries.forEach(entry -> {
             if (entry instanceof WidgetsListHeaderEntry) {
                 ((WidgetsListHeaderEntry) entry).setIsWidgetListShown(
-                        entry.mPkgItem.packageName.equals(mWidgetsContentVisiblePackage));
+                        new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)
+                                .equals(mWidgetsContentVisiblePackageUserKey));
+            } else if (entry instanceof WidgetsListSearchHeaderEntry) {
+                ((WidgetsListSearchHeaderEntry) entry).setIsWidgetListShown(
+                        new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)
+                                .equals(mWidgetsContentVisiblePackageUserKey));
             }
         });
         List<WidgetsListBaseEntry> newVisibleEntries = mAllEntries.stream()
@@ -148,10 +177,18 @@
         mDiffReporter.process(mVisibleEntries, newVisibleEntries, mRowComparator);
     }
 
+    /**
+     * Resets any expanded widget header.
+     */
+    public void resetExpandedHeader() {
+        mWidgetsContentVisiblePackageUserKey = null;
+        updateVisibleEntries();
+    }
+
     @Override
     public void onBindViewHolder(ViewHolder holder, int pos) {
         ViewHolderBinder viewHolderBinder = mViewHolderBinders.get(getItemViewType(pos));
-        viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos));
+        viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), pos);
     }
 
     @Override
@@ -189,17 +226,19 @@
             return VIEW_TYPE_WIDGETS_LIST;
         } else if (entry instanceof WidgetsListHeaderEntry) {
             return VIEW_TYPE_WIDGETS_HEADER;
+        } else if (entry instanceof WidgetsListSearchHeaderEntry) {
+            return VIEW_TYPE_WIDGETS_SEARCH_HEADER;
         }
         throw new UnsupportedOperationException("ViewHolderBinder not found for " + entry);
     }
 
     @Override
-    public void onHeaderClicked(boolean showWidgets, String expandedPackage) {
+    public void onHeaderClicked(boolean showWidgets, PackageUserKey packageUserKey) {
         if (showWidgets) {
-            mWidgetsContentVisiblePackage = expandedPackage;
+            mWidgetsContentVisiblePackageUserKey = packageUserKey;
             updateVisibleEntries();
-        } else if (expandedPackage.equals(mWidgetsContentVisiblePackage)) {
-            mWidgetsContentVisiblePackage = null;
+        } else if (packageUserKey.equals(mWidgetsContentVisiblePackageUserKey)) {
+            mWidgetsContentVisiblePackageUserKey = null;
             updateVisibleEntries();
         }
     }
@@ -229,7 +268,14 @@
 
         @Override
         public int compare(WidgetsListBaseEntry a, WidgetsListBaseEntry b) {
-            return mComparator.compare(a.mPkgItem.title.toString(), b.mPkgItem.title.toString());
+            int i = mComparator.compare(a.mPkgItem.title.toString(), b.mPkgItem.title.toString());
+            if (i != 0) {
+                return i;
+            }
+            // Prioritize entries from current user over other users if the entries are same.
+            if (a.mPkgItem.user.equals(b.mPkgItem.user)) return 0;
+            if (a.mPkgItem.user.equals(Process.myUserHandle())) return -1;
+            return 1;
         }
     }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index 070a9aa..75dd409 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -29,6 +29,7 @@
 
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
+import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.FastBitmapDrawable;
@@ -41,6 +42,9 @@
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+
+import java.util.stream.Collectors;
 
 /**
  * A UI represents a header of an app shown in the full widgets tray.
@@ -55,6 +59,7 @@
     @Nullable private HandlerRunnable mIconLoadRequest;
     @Nullable private Drawable mIconDrawable;
     private final int mIconSize;
+    private final int mBottomMarginSize;
 
     private ImageView mAppIcon;
     private TextView mTitle;
@@ -80,6 +85,8 @@
                 R.styleable.WidgetsListRowHeader, defStyleAttr, /* defStyleRes= */ 0);
         mIconSize = a.getDimensionPixelSize(R.styleable.WidgetsListRowHeader_appIconSize,
                 grid.iconSizePx);
+        mBottomMarginSize =
+                getResources().getDimensionPixelSize(R.dimen.widget_list_entry_bottom_margin);
     }
 
     @Override
@@ -110,6 +117,13 @@
     public void setExpanded(boolean isExpanded) {
         this.mIsExpanded = isExpanded;
         mExpandToggle.setChecked(isExpanded);
+        if (getLayoutParams() instanceof RecyclerView.LayoutParams) {
+            int bottomMargin = isExpanded ? 0 : mBottomMarginSize;
+            RecyclerView.LayoutParams layoutParams =
+                    ((RecyclerView.LayoutParams) getLayoutParams());
+            layoutParams.bottomMargin = bottomMargin;
+            setLayoutParams(layoutParams);
+        }
     }
 
     /** Apply app icon, labels and tag using a generic {@link WidgetsListHeaderEntry}. */
@@ -173,7 +187,7 @@
                     shortcutsCount);
         } else if (entry.widgetsCount > 0) {
             subtitle = resources.getQuantityString(R.plurals.widgets_count,
-                     entry.widgetsCount, entry.widgetsCount);
+                    entry.widgetsCount, entry.widgetsCount);
         } else {
             subtitle = resources.getQuantityString(R.plurals.shortcuts_count,
                     entry.shortcutsCount, entry.shortcutsCount);
@@ -182,6 +196,32 @@
         mSubtitle.setVisibility(VISIBLE);
     }
 
+    /** Apply app icon, labels and tag using a generic {@link WidgetsListSearchHeaderEntry}. */
+    @UiThread
+    public void applyFromItemInfoWithIcon(WidgetsListSearchHeaderEntry entry) {
+        applyIconAndLabel(entry);
+    }
+
+    @UiThread
+    private void applyIconAndLabel(WidgetsListSearchHeaderEntry entry) {
+        PackageItemInfo info = entry.mPkgItem;
+        setIcon(info);
+        setTitles(entry);
+        setExpanded(entry.isWidgetListShown());
+
+        super.setTag(info);
+
+        verifyHighRes();
+    }
+
+    private void setTitles(WidgetsListSearchHeaderEntry entry) {
+        mTitle.setText(entry.mPkgItem.title);
+
+        mSubtitle.setText(entry.mWidgets.stream()
+                .map(item -> item.label).sorted().collect(Collectors.joining(", ")));
+        mSubtitle.setVisibility(VISIBLE);
+    }
+
     @Override
     public void reapplyItemInfo(ItemInfoWithIcon info) {
         if (getTag() == info) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
index ed53e6f..f126321 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
@@ -20,6 +20,7 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.recyclerview.ViewHolderBinder;
+import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
 
 /**
@@ -29,11 +30,14 @@
         ViewHolderBinder<WidgetsListHeaderEntry, WidgetsListHeaderHolder> {
     private final LayoutInflater mLayoutInflater;
     private final OnHeaderClickListener mOnHeaderClickListener;
+    private final WidgetsListAdapter mWidgetsListAdapter;
 
     public WidgetsListHeaderViewHolderBinder(LayoutInflater layoutInflater,
-            OnHeaderClickListener onHeaderClickListener) {
+            OnHeaderClickListener onHeaderClickListener,
+            WidgetsListAdapter listAdapter) {
         mLayoutInflater = layoutInflater;
         mOnHeaderClickListener = onHeaderClickListener;
+        mWidgetsListAdapter = listAdapter;
     }
 
     @Override
@@ -45,17 +49,22 @@
     }
 
     @Override
-    public void bindViewHolder(WidgetsListHeaderHolder viewHolder, WidgetsListHeaderEntry data) {
+    public void bindViewHolder(WidgetsListHeaderHolder viewHolder, WidgetsListHeaderEntry data,
+            int position) {
         WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+        if (position == 0) {
+            widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_top_ripple);
+        } else if (position == mWidgetsListAdapter.getItemCount() - 1) {
+            widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_bottom_ripple);
+        } else {
+            widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_middle_ripple);
+        }
         widgetsListHeader.applyFromItemInfoWithIcon(data);
         widgetsListHeader.setExpanded(data.isWidgetListShown());
         widgetsListHeader.setOnExpandChangeListener(isExpanded ->
-                mOnHeaderClickListener.onHeaderClicked(isExpanded, data.mPkgItem.packageName));
-    }
-
-    /** A listener to be invoked when {@link WidgetsListHeader} is clicked. */
-    public interface OnHeaderClickListener {
-        /** Calls when {@link WidgetsListHeader} is clicked to show / hide widgets for a package. */
-        void onHeaderClicked(boolean showWidgets, String packageName);
+                mOnHeaderClickListener.onHeaderClicked(
+                        isExpanded,
+                        new PackageUserKey(data.mPkgItem.packageName, data.mPkgItem.user)
+                ));
     }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderHolder.java b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderHolder.java
new file mode 100644
index 0000000..9562af3
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderHolder.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 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.widget.picker;
+
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+/**
+ * A {@link ViewHolder} for {@link WidgetsListHeader} of an app, which renders the app icon, the app
+ * name, label and a button for showing / hiding widgets.
+ */
+public final class WidgetsListSearchHeaderHolder extends ViewHolder {
+    final WidgetsListHeader mWidgetsListHeader;
+
+    public WidgetsListSearchHeaderHolder(WidgetsListHeader view) {
+        super(view);
+
+        mWidgetsListHeader = view;
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
new file mode 100644
index 0000000..37713e1
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 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.widget.picker;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.android.launcher3.R;
+import com.android.launcher3.recyclerview.ViewHolderBinder;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+
+/**
+ * Binds data from {@link WidgetsListHeaderEntry} to UI elements in {@link WidgetsListHeaderHolder}.
+ */
+public final class WidgetsListSearchHeaderViewHolderBinder implements
+        ViewHolderBinder<WidgetsListSearchHeaderEntry, WidgetsListSearchHeaderHolder> {
+    private final LayoutInflater mLayoutInflater;
+    private final OnHeaderClickListener mOnHeaderClickListener;
+    private final WidgetsListAdapter mWidgetsListAdapter;
+
+    public WidgetsListSearchHeaderViewHolderBinder(LayoutInflater layoutInflater,
+            OnHeaderClickListener onHeaderClickListener,
+            WidgetsListAdapter listAdapter) {
+        mLayoutInflater = layoutInflater;
+        mOnHeaderClickListener = onHeaderClickListener;
+        mWidgetsListAdapter = listAdapter;
+    }
+
+    @Override
+    public WidgetsListSearchHeaderHolder newViewHolder(ViewGroup parent) {
+        WidgetsListHeader header = (WidgetsListHeader) mLayoutInflater.inflate(
+                R.layout.widgets_list_row_header, parent, false);
+
+        return new WidgetsListSearchHeaderHolder(header);
+    }
+
+    @Override
+    public void bindViewHolder(WidgetsListSearchHeaderHolder viewHolder,
+            WidgetsListSearchHeaderEntry data, int position) {
+        WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
+        if (position == 0) {
+            widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_top_ripple);
+        } else if (position == mWidgetsListAdapter.getItemCount() - 1) {
+            widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_bottom_ripple);
+        } else {
+            widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_middle_ripple);
+        }
+        widgetsListHeader.applyFromItemInfoWithIcon(data);
+        widgetsListHeader.setExpanded(data.isWidgetListShown());
+        widgetsListHeader.setOnExpandChangeListener(isExpanded ->
+                mOnHeaderClickListener.onHeaderClicked(isExpanded,
+                        new PackageUserKey(data.mPkgItem.packageName, data.mPkgItem.user)));
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index 47fa71a..d0be35d 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -52,6 +52,7 @@
     private final OnClickListener mIconClickListener;
     private final OnLongClickListener mIconLongClickListener;
     private final WidgetPreviewLoader mWidgetPreviewLoader;
+    private final WidgetsListAdapter mWidgetsListAdapter;
     private boolean mApplyBitmapDeferred = false;
 
     public WidgetsListTableViewHolderBinder(
@@ -59,12 +60,14 @@
             LayoutInflater layoutInflater,
             OnClickListener iconClickListener,
             OnLongClickListener iconLongClickListener,
-            WidgetPreviewLoader widgetPreviewLoader) {
+            WidgetPreviewLoader widgetPreviewLoader,
+            WidgetsListAdapter listAdapter) {
         mLayoutInflater = layoutInflater;
         mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent);
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
         mWidgetPreviewLoader = widgetPreviewLoader;
+        mWidgetsListAdapter = listAdapter;
     }
 
     /**
@@ -97,13 +100,22 @@
     }
 
     @Override
-    public void bindViewHolder(WidgetsRowViewHolder holder, WidgetsListContentEntry entry) {
+    public void bindViewHolder(WidgetsRowViewHolder holder, WidgetsListContentEntry entry,
+            int position) {
         TableLayout table = holder.mTableContainer;
         if (DEBUG) {
             Log.d(TAG, String.format("onBindViewHolder [widget#=%d, table.getChildCount=%d]",
                     entry.mWidgets.size(), table.getChildCount()));
         }
 
+        if (position == mWidgetsListAdapter.getItemCount() - 1) {
+            table.setBackgroundResource(R.drawable.widgets_list_bottom_ripple);
+        } else {
+            // WidgetsListContentEntry is never shown in position 0. There must be a header above
+            // it.
+            table.setBackgroundResource(R.drawable.widgets_list_middle_ripple);
+        }
+
         List<ArrayList<WidgetItem>> widgetItemsTable =
                 WidgetsTableUtils.groupWidgetItemsIntoTable(entry.mWidgets, mMaxSpansPerRow);
         recycleTableBeforeBinding(table, widgetItemsTable);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index d65a809..b016b4f 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -21,13 +21,20 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
+import android.widget.TableLayout;
 
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
 
 import com.android.launcher3.BaseRecyclerView;
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
 
 /**
  * The widgets recycler view.
@@ -39,8 +46,10 @@
     private final int mScrollbarTop;
 
     private final Point mFastScrollerOffset = new Point();
+    private final int mEstimatedWidgetListHeaderHeight;
     private boolean mTouchDownOnScroller;
     private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
+    private int mLastVisibleWidgetContentTableHeight = 0;
 
     public WidgetsRecyclerView(Context context) {
         this(context, null);
@@ -55,6 +64,12 @@
         super(context, attrs, defStyleAttr);
         mScrollbarTop = getResources().getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
         addOnItemTouchListener(this);
+
+        ActivityContext activity = ActivityContext.lookupContext(getContext());
+        DeviceProfile grid = activity.getDeviceProfile();
+        mEstimatedWidgetListHeaderHeight = grid.iconSizePx
+                + 2 * context.getResources().getDimensionPixelSize(
+                        R.dimen.widget_list_header_view_vertical_padding);
     }
 
     @Override
@@ -123,21 +138,32 @@
 
         View child = getChildAt(0);
         int rowIndex = getChildPosition(child);
-        int y = (child.getMeasuredHeight() * rowIndex);
+        for (int i = 0; i < getChildCount(); i++) {
+            View view = getChildAt(i);
+            if (view instanceof TableLayout) {
+                // This assumes there is ever only one content shown in this recycler view.
+                mLastVisibleWidgetContentTableHeight = view.getMeasuredHeight();
+            }
+        }
+
+        int scrollPosition = getItemsHeight(rowIndex);
         int offset = getLayoutManager().getDecoratedTop(child);
 
-        return getPaddingTop() + y - offset;
+        return getPaddingTop() + scrollPosition - offset;
     }
 
     /**
-     * Returns the available scroll height:
-     *   AvailableScrollHeight = Total height of the all items - last page height
+     * Returns the available scroll height, in pixel.
+     *
+     * <p>If the recycler view can't be scrolled, returns 0.
      */
     @Override
     protected int getAvailableScrollHeight() {
-        View child = getChildAt(0);
-        return child.getMeasuredHeight() * mAdapter.getItemCount() + getScrollBarTop()
-                + getPaddingBottom() - mScrollbar.getHeight();
+        // AvailableScrollHeight = Total height of the all items - first page height
+        int firstPageHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
+        int totalHeightOfAllItems = getItemsHeight(/* untilIndex= */ mAdapter.getItemCount());
+        int availableScrollHeight = totalHeightOfAllItems - firstPageHeight;
+        return Math.max(0, availableScrollHeight);
     }
 
     private boolean isModelNotReady() {
@@ -181,6 +207,32 @@
     }
 
     /**
+     * Returns the sum of the height, in pixels, of this list adapter's items from index 0 until
+     * {@code untilIndex}.
+     *
+     * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
+     * sum of all items' height.
+     */
+    private int getItemsHeight(int untilIndex) {
+        if (untilIndex > mAdapter.getItems().size()) {
+            untilIndex = mAdapter.getItems().size();
+        }
+        int totalItemsHeight = 0;
+        for (int i = 0; i < untilIndex; i++) {
+            WidgetsListBaseEntry entry = mAdapter.getItems().get(i);
+            if (entry instanceof WidgetsListHeaderEntry
+                    || entry instanceof WidgetsListSearchHeaderEntry) {
+                totalItemsHeight += mEstimatedWidgetListHeaderHeight;
+            } else if (entry instanceof WidgetsListContentEntry) {
+                totalItemsHeight += mLastVisibleWidgetContentTableHeight;
+            } else {
+                throw new UnsupportedOperationException("Can't estimate height for " + entry);
+            }
+        }
+        return totalItemsHeight;
+    }
+
+    /**
      * Provides dimensions of the header view that is shown at the top of a
      * {@link WidgetsRecyclerView}.
      */
diff --git a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
new file mode 100644
index 0000000..5520826
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 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.widget.picker.search;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.R;
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.List;
+
+/**
+ * View for a search bar with an edit text with a cancel button.
+ */
+public class LauncherWidgetsSearchBar extends LinearLayout implements WidgetsSearchBar {
+    private WidgetsSearchBarController mController;
+    private ExtendedEditText mEditText;
+    private ImageButton mCancelButton;
+
+    public LauncherWidgetsSearchBar(Context context) {
+        this(context, null, 0);
+    }
+
+    public LauncherWidgetsSearchBar(@NonNull Context context,
+            @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LauncherWidgetsSearchBar(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    public void initialize(List<WidgetsListBaseEntry> allWidgets,
+            SearchModeListener searchModeListener) {
+        SearchAlgorithm<WidgetsListBaseEntry> algo =
+                new SimpleWidgetsSearchAlgorithm(new SimpleWidgetsSearchPipeline(allWidgets));
+        mController = new WidgetsSearchBarController(
+                algo, mEditText, mCancelButton, searchModeListener);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mEditText = findViewById(R.id.widgets_search_bar_edit_text);
+        mCancelButton = findViewById(R.id.widgets_search_cancel_button);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mController.onDestroy();
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/search/SearchModeListener.java b/src/com/android/launcher3/widget/picker/search/SearchModeListener.java
new file mode 100644
index 0000000..cee7d67
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/SearchModeListener.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 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.widget.picker.search;
+
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.List;
+
+/**
+ * A listener to help with widgets picker search.
+ */
+public interface SearchModeListener {
+    /**
+     * Notifies the subscriber when user enters widget picker search mode.
+     */
+    void enterSearchMode();
+
+    /**
+     * Notifies the subscriber when user exits widget picker search mode.
+     */
+    void exitSearchMode();
+
+    /**
+     * Notifies the subscriber with search results.
+     */
+    void onSearchResults(List<WidgetsListBaseEntry> entries);
+}
diff --git a/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java
new file mode 100644
index 0000000..15d2454
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 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.widget.picker.search;
+
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.search.SearchCallback;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.ArrayList;
+
+/**
+ * Implementation of {@link SearchAlgorithm} that posts a task to query on the main thread.
+ */
+public final class SimpleWidgetsSearchAlgorithm implements SearchAlgorithm<WidgetsListBaseEntry> {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "SimpleWidgetsSearchAlgo";
+    private static final String DELIM = "\t";
+
+    private final Handler mResultHandler;
+    private final WidgetsPickerSearchPipeline mSearchPipeline;
+
+    public SimpleWidgetsSearchAlgorithm(WidgetsPickerSearchPipeline searchPipeline) {
+        mResultHandler = new Handler();
+        mSearchPipeline = searchPipeline;
+    }
+
+    @Override
+    public void doSearch(String query, SearchCallback<WidgetsListBaseEntry> callback) {
+        long startTime = System.currentTimeMillis();
+        String queryToken = query + DELIM + startTime;
+        if (DEBUG) {
+            Log.d(TAG, "doSearch queryToken:" + queryToken);
+        }
+        mSearchPipeline.query(query,
+                results -> mResultHandler.post(
+                        () -> callback.onSearchResult(queryToken, new ArrayList(results))));
+    }
+
+    @Override
+    public void cancel(boolean interruptActiveRequests) {
+        if (interruptActiveRequests) {
+            mResultHandler.removeCallbacksAndMessages(/*token= */null);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipeline.java b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipeline.java
new file mode 100644
index 0000000..5222e8e
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipeline.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 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.widget.picker.search;
+
+import static com.android.launcher3.search.StringMatcherUtility.matches;
+
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.search.StringMatcherUtility.StringMatcher;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+/**
+ * Implementation of {@link WidgetsPickerSearchPipeline} that performs search by prefix matching on
+ * app names and widget labels.
+ */
+public final class SimpleWidgetsSearchPipeline implements WidgetsPickerSearchPipeline {
+
+    private final List<WidgetsListBaseEntry> mAllEntries;
+
+    public SimpleWidgetsSearchPipeline(List<WidgetsListBaseEntry> allEntries) {
+        mAllEntries = allEntries;
+    }
+
+    @Override
+    public void query(String input, Consumer<List<WidgetsListBaseEntry>> callback) {
+        ArrayList<WidgetsListBaseEntry> results = new ArrayList<>();
+        mAllEntries.stream().filter(entry -> entry instanceof WidgetsListHeaderEntry)
+                .forEach(headerEntry -> {
+                    List<WidgetItem> matchedWidgetItems = filterWidgetItems(
+                            input, headerEntry.mPkgItem.title.toString(), headerEntry.mWidgets);
+                    if (matchedWidgetItems.size() > 0) {
+                        results.add(new WidgetsListSearchHeaderEntry(headerEntry.mPkgItem,
+                                headerEntry.mTitleSectionName, matchedWidgetItems));
+                        results.add(new WidgetsListContentEntry(headerEntry.mPkgItem,
+                                headerEntry.mTitleSectionName, matchedWidgetItems));
+                    }
+                });
+        callback.accept(results);
+    }
+
+    private List<WidgetItem> filterWidgetItems(String query, String packageTitle,
+            List<WidgetItem> items) {
+        StringMatcher matcher = StringMatcher.getInstance();
+        if (matches(query, packageTitle, matcher)) {
+            return items;
+        }
+        return items.stream()
+                .filter(item -> matches(query, item.label, matcher))
+                .collect(Collectors.toList());
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
new file mode 100644
index 0000000..af6dc48
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 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.widget.picker.search;
+
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.List;
+
+/**
+ * Interface for a widgets picker search bar.
+ */
+public interface WidgetsSearchBar {
+    /**
+     * Attaches a controller to the search bar which interacts with {@code searchModeListener}.
+     */
+    void initialize(List<WidgetsListBaseEntry> allWidgets, SearchModeListener searchModeListener);
+
+    /**
+     * Sets the vertical location, in pixels, of this search bar relative to its top position.
+     */
+    void setTranslationY(float translationY);
+}
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
new file mode 100644
index 0000000..6011097
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2021 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.widget.picker.search;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.ImageButton;
+
+import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.search.SearchCallback;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.ArrayList;
+
+/**
+ * Controller for a search bar with an edit text and a cancel button.
+ */
+public class WidgetsSearchBarController implements TextWatcher,
+        SearchCallback<WidgetsListBaseEntry>,  ExtendedEditText.OnBackKeyListener,
+        View.OnKeyListener {
+    private static final String TAG = "WidgetsSearchBarController";
+    private static final boolean DEBUG = false;
+
+    protected SearchAlgorithm<WidgetsListBaseEntry> mSearchAlgorithm;
+    protected ExtendedEditText mInput;
+    protected ImageButton mCancelButton;
+    protected SearchModeListener mSearchModeListener;
+    protected String mQuery;
+
+    public WidgetsSearchBarController(
+            SearchAlgorithm<WidgetsListBaseEntry> algo, ExtendedEditText editText,
+            ImageButton cancelButton, SearchModeListener searchModeListener) {
+        mSearchAlgorithm = algo;
+        mInput = editText;
+        mInput.addTextChangedListener(this);
+        mInput.setOnBackKeyListener(this);
+        mInput.setOnKeyListener(this);
+        mCancelButton = cancelButton;
+        mCancelButton.setOnClickListener(v -> clearSearchResult());
+        mSearchModeListener = searchModeListener;
+    }
+
+    @Override
+    public void afterTextChanged(final Editable s) {
+        mQuery = s.toString();
+        if (mQuery.isEmpty()) {
+            mSearchAlgorithm.cancel(/* interruptActiveRequests= */ true);
+            mSearchModeListener.exitSearchMode();
+            mCancelButton.setVisibility(GONE);
+        } else {
+            mSearchAlgorithm.cancel(/* interruptActiveRequests= */ false);
+            mSearchModeListener.enterSearchMode();
+            mSearchAlgorithm.doSearch(mQuery, this);
+            mCancelButton.setVisibility(VISIBLE);
+        }
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+        // Do nothing.
+    }
+
+    @Override
+    public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+        // Do nothing.
+    }
+
+    @Override
+    public void onSearchResult(String query, ArrayList<WidgetsListBaseEntry> items) {
+        if (DEBUG) {
+            Log.d(TAG, "onSearchResult query: " + query + " items: " + items);
+        }
+        mSearchModeListener.onSearchResults(items);
+    }
+
+    @Override
+    public void onAppendSearchResult(String query, ArrayList<WidgetsListBaseEntry> items) {
+        // Not needed.
+    }
+
+    @Override
+    public void clearSearchResult() {
+        mSearchAlgorithm.cancel(/* interruptActiveRequests= */ true);
+        mInput.getText().clear();
+        mInput.clearFocus();
+        mInput.hideKeyboard();
+        mSearchModeListener.exitSearchMode();
+    }
+
+    /**
+     * Cleans up after search is no longer needed.
+     */
+    public void onDestroy() {
+        mSearchAlgorithm.destroy();
+    }
+
+    @Override
+    public boolean onBackKey() {
+        mInput.clearFocus();
+        mInput.hideKeyboard();
+        return true;
+    }
+
+    @Override
+    public boolean onKey(View view, int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) {
+            mInput.clearFocus();
+            mInput.hideKeyboard();
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/src_plugins/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/src_plugins/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
deleted file mode 100644
index f8a9a04..0000000
--- a/src_plugins/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2021 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.systemui.plugins;
-
-import android.os.Parcelable;
-
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-
-import java.util.List;
-
-/**
- * Interface to provide SmartspaceTargets to BcSmartspace.
- */
-@ProvidesInterface(action = BcSmartspaceDataPlugin.ACTION, version = BcSmartspaceDataPlugin.VERSION)
-public interface BcSmartspaceDataPlugin extends Plugin {
-    String ACTION = "com.android.systemui.action.PLUGIN_BC_SMARTSPACE_DATA";
-    int VERSION = 1;
-
-    /** Register a listener to get Smartspace data. */
-    void registerListener(SmartspaceTargetListener listener);
-
-    /** Unregister a listener. */
-    void unregisterListener(SmartspaceTargetListener listener);
-
-    /** Provides Smartspace data to registered listeners. */
-    interface SmartspaceTargetListener {
-        /** Each Parcelable is a SmartspaceTarget that represents a card. */
-        void onSmartspaceTargetsUpdated(List<? extends Parcelable> targets);
-    }
-}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
index a4e53a1..ff28148 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -56,7 +56,7 @@
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        return ALL_APPS_HEADER | ALL_APPS_CONTENT;
+        return ALL_APPS_CONTENT;
     }
 
     @Override
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
index da5a94f..e85e505 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -49,4 +49,11 @@
     public static OverviewState newModalTaskState(int id) {
         return new OverviewState(id);
     }
+
+    /**
+     *  New Overview substate that represents the overview in modal mode (one task shown on its own)
+     */
+    public static OverviewState newSplitSelectState(int id) {
+        return new OverviewState(id);
+    }
 }
diff --git a/tests/Android.bp b/tests/Android.bp
new file mode 100644
index 0000000..8a73483
--- /dev/null
+++ b/tests/Android.bp
@@ -0,0 +1,17 @@
+// Copyright (C) 2021 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.
+filegroup {
+    name: "launcher3-test-src-common",
+    srcs: ["src_common/**/*.java"],
+}
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 744dee0..7f6c8f8 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -60,6 +60,17 @@
                        android:resource="@xml/appwidget_with_config"/>
         </receiver>
 
+        <receiver
+            android:name="com.android.launcher3.testcomponent.AppWidgetDynamicColors"
+            android:exported="true"
+            android:label="Dynamic Colors">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
+            </intent-filter>
+            <meta-data android:name="android.appwidget.provider"
+                android:resource="@xml/appwidget_dynamic_colors"/>
+        </receiver>
+
         <activity
             android:name="com.android.launcher3.testcomponent.WidgetConfigActivity"
             android:exported="true">
diff --git a/tests/Launcher3Tests.xml b/tests/Launcher3Tests.xml
new file mode 100644
index 0000000..3fff622
--- /dev/null
+++ b/tests/Launcher3Tests.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<!-- This test config file is auto-generated. -->
+<configuration description="Runs Launcher3 tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="set-test-harness" value="true" />
+        <option name="run-command" value="am force-stop com.android.launcher3" />
+        <option name="run-command" value="pm uninstall com.google.android.apps.nexuslauncher" />
+        <option name="run-command" value="pm uninstall com.google.android.apps.nexuslauncher.out_of_proc_tests" />
+        <option name="run-command" value="pm uninstall com.google.android.apps.nexuslauncher.tests" />
+        <option name="run-command" value="pm disable com.google.android.googlequicksearchbox" />
+
+        <option name="run-command" value="input keyevent 82" />
+        <option name="run-command" value="settings delete secure assistant" />
+        <option name="run-command" value="settings put global airplane_mode_on 1" />
+        <option name="run-command" value="am broadcast -a android.intent.action.AIRPLANE_MODE" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="Launcher3Tests.apk" />
+        <option name="test-file-name" value="Launcher3.apk" />
+    </target_preparer>
+
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/data/user/0/com.android.launcher3/files" />
+        <option name="collect-on-run-ended-only" value="true" />
+    </metrics_collector>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.launcher3.tests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/tests/res/layout/test_layout_appwidget_dynamic_colors.xml b/tests/res/layout/test_layout_appwidget_dynamic_colors.xml
new file mode 100644
index 0000000..c5ab030
--- /dev/null
+++ b/tests/res/layout/test_layout_appwidget_dynamic_colors.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:background="?android:attr/colorBackground"
+    android:padding="8dp"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout
+        android:orientation = "horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="prim"/>
+        <ImageView
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:background="@android:color/system_primary_500"/>
+    </LinearLayout>
+    <LinearLayout
+        android:orientation = "horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="second"/>
+        <ImageView
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:background="@android:color/system_secondary_500"/>
+    </LinearLayout>
+    <LinearLayout
+        android:orientation = "horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="neutral"/>
+        <ImageView
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:background="@android:color/system_neutral_500"/>
+    </LinearLayout>
+
+    </LinearLayout>
\ No newline at end of file
diff --git a/tests/res/xml/appwidget_dynamic_colors.xml b/tests/res/xml/appwidget_dynamic_colors.xml
new file mode 100644
index 0000000..f6b9a04
--- /dev/null
+++ b/tests/res/xml/appwidget_dynamic_colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="1dp"
+    android:minHeight="1dp"
+    android:updatePeriodMillis="0"
+    android:initialLayout="@layout/test_layout_appwidget_dynamic_colors"
+    android:resizeMode="horizontal|vertical"
+    android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java b/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java
deleted file mode 100644
index 39709a9..0000000
--- a/tests/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithmTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2016 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.allapps.search;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.content.ComponentName;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.launcher3.model.data.AppInfo;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Unit tests for {@link DefaultAppSearchAlgorithm}
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class DefaultAppSearchAlgorithmTest {
-    private static final DefaultAppSearchAlgorithm.StringMatcher MATCHER =
-            DefaultAppSearchAlgorithm.StringMatcher.getInstance();
-
-    @Test
-    public void testMatches() {
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("white cow"), "cow", MATCHER));
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("whiteCow"), "cow", MATCHER));
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("whiteCOW"), "cow", MATCHER));
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("whitecowCOW"), "cow", MATCHER));
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("white2cow"), "cow", MATCHER));
-
-        assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("whitecow"), "cow", MATCHER));
-        assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("whitEcow"), "cow", MATCHER));
-
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("whitecowCow"), "cow", MATCHER));
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("whitecow cow"), "cow", MATCHER));
-        assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("whitecowcow"), "cow", MATCHER));
-        assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("whit ecowcow"), "cow", MATCHER));
-
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("cats&dogs"), "dog", MATCHER));
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("cats&Dogs"), "dog", MATCHER));
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("cats&Dogs"), "&", MATCHER));
-
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("2+43"), "43", MATCHER));
-        assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("2+43"), "3", MATCHER));
-
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("Q"), "q", MATCHER));
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("  Q"), "q", MATCHER));
-
-        // match lower case words
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("elephant"), "e", MATCHER));
-
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("电子邮件"), "电", MATCHER));
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("电子邮件"), "电子", MATCHER));
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("电子邮件"), "子", MATCHER));
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("电子邮件"), "邮件", MATCHER));
-
-        assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("Bot"), "ba", MATCHER));
-        assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("bot"), "ba", MATCHER));
-    }
-
-    @Test
-    public void testMatchesVN() {
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("다운로드"), "다", MATCHER));
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("드라이브"), "드", MATCHER));
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("다운로드 드라이브"), "ㄷ", MATCHER));
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("운로 드라이브"), "ㄷ", MATCHER));
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("abc"), "åbç", MATCHER));
-        assertTrue(DefaultAppSearchAlgorithm.matches(getInfo("Alpha"), "ål", MATCHER));
-
-        assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("다운로드 드라이브"), "ㄷㄷ", MATCHER));
-        assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("로드라이브"), "ㄷ", MATCHER));
-        assertFalse(DefaultAppSearchAlgorithm.matches(getInfo("abc"), "åç", MATCHER));
-    }
-
-    private AppInfo getInfo(String title) {
-        AppInfo info = new AppInfo();
-        info.title = title;
-        info.componentName = new ComponentName("Test", title);
-        return info;
-    }
-}
diff --git a/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java b/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
new file mode 100644
index 0000000..413f404
--- /dev/null
+++ b/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 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.search;
+
+import static com.android.launcher3.search.StringMatcherUtility.matches;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.search.StringMatcherUtility.StringMatcher;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link StringMatcherUtility}
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StringMatcherUtilityTest {
+    private static final StringMatcher MATCHER =
+            StringMatcher.getInstance();
+
+    @Test
+    public void testMatches() {
+        assertTrue(matches("white ", "white cow", MATCHER));
+        assertTrue(matches("white c", "white cow", MATCHER));
+        assertTrue(matches("cow", "white cow", MATCHER));
+        assertTrue(matches("cow", "whiteCow", MATCHER));
+        assertTrue(matches("cow", "whiteCOW", MATCHER));
+        assertTrue(matches("cow", "whitecowCOW", MATCHER));
+        assertTrue(matches("cow", "white2cow", MATCHER));
+
+        assertFalse(matches("cow", "whitecow", MATCHER));
+        assertFalse(matches("cow", "whitEcow", MATCHER));
+
+        assertTrue(matches("cow", "whitecowCow", MATCHER));
+        assertTrue(matches("cow", "whitecow cow", MATCHER));
+        assertFalse(matches("cow", "whitecowcow", MATCHER));
+        assertFalse(matches("cow", "whit ecowcow", MATCHER));
+
+        assertTrue(matches("dog", "cats&dogs", MATCHER));
+        assertTrue(matches("dog", "cats&Dogs", MATCHER));
+        assertTrue(matches("&", "cats&Dogs", MATCHER));
+
+        assertTrue(matches("43", "2+43", MATCHER));
+        assertFalse(matches("3", "2+43", MATCHER));
+
+        assertTrue(matches("q", "Q", MATCHER));
+        assertTrue(matches("q", "  Q", MATCHER));
+
+        // match lower case words
+        assertTrue(matches("e", "elephant", MATCHER));
+        assertTrue(matches("eL", "Elephant", MATCHER));
+
+        assertTrue(matches("电", "电子邮件", MATCHER));
+        assertTrue(matches("电子", "电子邮件", MATCHER));
+        assertTrue(matches("子", "电子邮件", MATCHER));
+        assertTrue(matches("邮件", "电子邮件", MATCHER));
+
+        assertFalse(matches("ba", "Bot", MATCHER));
+        assertFalse(matches("ba", "bot", MATCHER));
+        assertFalse(matches("phant", "elephant", MATCHER));
+        assertFalse(matches("elephants", "elephant", MATCHER));
+    }
+
+    @Test
+    public void testMatchesVN() {
+        assertTrue(matches("다", "다운로드", MATCHER));
+        assertTrue(matches("드", "드라이브", MATCHER));
+        assertTrue(matches("ㄷ", "다운로드 드라이브", MATCHER));
+        assertTrue(matches("ㄷ", "운로 드라이브", MATCHER));
+        assertTrue(matches("åbç", "abc", MATCHER));
+        assertTrue(matches("ål", "Alpha", MATCHER));
+
+        assertFalse(matches("ㄷㄷ", "다운로드 드라이브", MATCHER));
+        assertFalse(matches("ㄷ", "로드라이브", MATCHER));
+        assertFalse(matches("åç", "abc", MATCHER));
+    }
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java b/tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java
new file mode 100644
index 0000000..5fb3454
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013 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.testcomponent;
+
+import android.appwidget.AppWidgetProvider;
+
+/**
+ * A simple app widget showing a primary, secondary and neutral color.
+ */
+public class AppWidgetDynamicColors extends AppWidgetProvider {
+}
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index a6c7c03..8f4381b 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -18,7 +18,6 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
-import static com.android.launcher3.tapl.LauncherInstrumentation.LONG_WAIT_TIME_MS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -37,6 +36,7 @@
 import com.android.launcher3.allapps.AllAppsPagedView;
 import com.android.launcher3.allapps.WorkModeSwitch;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.WorkEduView;
 
@@ -147,7 +147,7 @@
                 return true;
             }
             return false;
-        }, LONG_WAIT_TIME_MS);
+        }, LauncherInstrumentation.WAIT_TIME_MS);
 
         executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
         WorkEduView workEduView = getEduView();
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index b6c17df..e32250e 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -27,7 +27,6 @@
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
-import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.testing.TestProtocol;
 
 import java.util.stream.Collectors;
@@ -108,26 +107,24 @@
                     "apps_list_view");
             final UiObject2 searchBox = getSearchBox(allAppsContainer);
 
-            int bottomGestureMargin = ResourceUtils.getNavbarSize(
-                    ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1;
-            int deviceHeight = mLauncher.getDevice().getDisplayHeight();
-            int displayBottom = deviceHeight - bottomGestureMargin;
+            int deviceHeight = mLauncher.getRealDisplaySize().y;
+            int bottomGestureStartOnScreen = mLauncher.getBottomGestureStartOnScreen();
             final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
             if (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
-                    displayBottom)) {
+                    bottomGestureStartOnScreen)) {
                 scrollBackToBeginning();
                 int attempts = 0;
                 int scroll = getAllAppsScroll();
                 try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled")) {
                     while (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
-                            displayBottom)) {
+                            bottomGestureStartOnScreen)) {
                         mLauncher.scrollToLastVisibleRow(
                                 allAppsContainer,
                                 mLauncher.getObjectsInContainer(allAppsContainer, "icon")
                                         .stream()
                                         .filter(icon ->
-                                                        mLauncher.getVisibleBounds(icon).bottom
-                                                        <= displayBottom)
+                                                mLauncher.getVisibleBounds(icon).top
+                                                        < bottomGestureStartOnScreen)
                                         .collect(Collectors.toList()),
                                 mLauncher.getVisibleBounds(searchBox).bottom
                                         - mLauncher.getVisibleBounds(allAppsContainer).top);
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index f279a82..a7b92b7 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -155,8 +155,7 @@
     private static final String OVERVIEW_RES_ID = "overview_panel";
     private static final String WIDGETS_RES_ID = "primary_widgets_list_view";
     private static final String CONTEXT_MENU_RES_ID = "deep_shortcuts_container";
-    public static final int WAIT_TIME_MS = 10000;
-    public static final int LONG_WAIT_TIME_MS = 60000;
+    public static final int WAIT_TIME_MS = 60000;
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
     private static final String ANDROID_PACKAGE = "android";
 
@@ -586,11 +585,7 @@
                 "but the current state is not " + containerType.name())) {
             switch (containerType) {
                 case WORKSPACE: {
-                    if (mDevice.isNaturalOrientation()) {
-                        waitForLauncherObject(APPS_RES_ID);
-                    } else {
-                        waitUntilLauncherObjectGone(APPS_RES_ID);
-                    }
+                    waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(OVERVIEW_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
                     return waitForLauncherObject(WORKSPACE_RES_ID);
@@ -656,7 +651,7 @@
         try {
             final AccessibilityEvent event =
                     mInstrumentation.getUiAutomation().executeAndWaitForEvent(
-                            command, eventFilter, LONG_WAIT_TIME_MS);
+                            command, eventFilter, WAIT_TIME_MS);
             assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event);
             final Parcelable parcelableData = event.getParcelableData();
             event.recycle();
@@ -1028,16 +1023,20 @@
                 expectedState);
     }
 
-    int getBottomGestureSize() {
+    private int getBottomGestureSize() {
         return ResourceUtils.getNavbarSize(
                 ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources()) + 1;
     }
 
     int getBottomGestureMarginInContainer(UiObject2 container) {
-        final int bottomGestureStartOnScreen = getRealDisplaySize().y - getBottomGestureSize();
+        final int bottomGestureStartOnScreen = getBottomGestureStartOnScreen();
         return getVisibleBounds(container).bottom - bottomGestureStartOnScreen;
     }
 
+    int getBottomGestureStartOnScreen() {
+        return getRealDisplaySize().y - getBottomGestureSize();
+    }
+
     void clickLauncherObject(UiObject2 object) {
         expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_TOUCH_DOWN);
         expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_TOUCH_UP);