Merge "Updated TaskMenuView." into sc-dev
diff --git a/Android.mk b/Android.mk
index 19ad328..127df79 100644
--- a/Android.mk
+++ b/Android.mk
@@ -145,7 +145,9 @@
     $(call all-java-files-under, quickstep/src) \
     $(call all-java-files-under, src_shortcuts_overrides)
 
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
+LOCAL_RESOURCE_DIR := \
+    $(LOCAL_PATH)/quickstep/res \
+    $(LOCAL_PATH)/quickstep/overview_ui_overrides/res
 LOCAL_PROGUARD_ENABLED := disabled
 
 
@@ -174,7 +176,9 @@
 LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3
 LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
 
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
+LOCAL_RESOURCE_DIR := \
+    $(LOCAL_PATH)/quickstep/res \
+    $(LOCAL_PATH)/quickstep/overview_ui_overrides/res
 
 LOCAL_FULL_LIBS_MANIFEST_FILES := \
     $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \
@@ -213,7 +217,8 @@
 LOCAL_RESOURCE_DIR := \
     $(LOCAL_PATH)/quickstep/res \
     $(LOCAL_PATH)/go/res \
-    $(LOCAL_PATH)/go/quickstep/res
+    $(LOCAL_PATH)/go/quickstep/res \
+    $(LOCAL_PATH)/go/quickstep/overview_ui_overrides/res
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 LOCAL_PROGUARD_ENABLED := full
diff --git a/go/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml b/go/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
new file mode 100644
index 0000000..b438da3
--- /dev/null
+++ b/go/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
@@ -0,0 +1,98 @@
+<?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.
+-->
+<com.android.quickstep.views.GoOverviewActionsView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/overview_actions_height"
+    android:layout_gravity="center_horizontal|bottom">
+
+    <LinearLayout
+        android:id="@+id/action_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:orientation="horizontal">
+
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1" />
+
+        <Button
+            android:id="@+id/action_listen"
+            style="@style/OverviewActionButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableTop="@drawable/ic_listen"
+            android:drawablePadding="1dp"
+            android:text="@string/action_listen"
+            android:theme="@style/ThemeControlHighlightWorkspaceColor" />
+
+        <Button
+            android:id="@+id/action_translate"
+            style="@style/OverviewActionButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableTop="@drawable/ic_translate"
+            android:drawablePadding="1dp"
+            android:text="@string/action_translate"
+            android:theme="@style/ThemeControlHighlightWorkspaceColor" />
+
+        <Button
+            android:id="@+id/action_search"
+            style="@style/OverviewActionButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableTop="@drawable/ic_search"
+            android:drawablePadding="1dp"
+            android:text="@string/action_search"
+            android:theme="@style/ThemeControlHighlightWorkspaceColor" />
+
+        <Button
+            android:id="@+id/action_screenshot"
+            style="@style/OverviewActionButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableTop="@drawable/ic_screenshot"
+            android:drawablePadding="1dp"
+            android:text="@string/action_screenshot"
+            android:theme="@style/ThemeControlHighlightWorkspaceColor" />
+
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1" />
+
+        <Button
+            android:id="@+id/action_share"
+            style="@style/OverviewActionButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableStart="@drawable/ic_share"
+            android:text="@string/action_share"
+            android:theme="@style/ThemeControlHighlightWorkspaceColor"
+            android:visibility="gone" />
+
+        <Space
+            android:id="@+id/oav_three_button_space"
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1"
+            android:visibility="gone" />
+    </LinearLayout>
+
+</com.android.quickstep.views.GoOverviewActionsView>
\ No newline at end of file
diff --git a/go/quickstep/overview_ui_overrides/res/values/config.xml b/go/quickstep/overview_ui_overrides/res/values/config.xml
new file mode 100644
index 0000000..ec21a01
--- /dev/null
+++ b/go/quickstep/overview_ui_overrides/res/values/config.xml
@@ -0,0 +1,22 @@
+<?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.
+*/
+-->
+<resources>
+    <string name="task_overlay_factory_class" translatable="false">
+        com.android.quickstep.TaskOverlayFactoryGo</string>
+</resources>
\ No newline at end of file
diff --git a/go/quickstep/res/drawable/ic_listen.xml b/go/quickstep/res/drawable/ic_listen.xml
new file mode 100644
index 0000000..a8e6c93
--- /dev/null
+++ b/go/quickstep/res/drawable/ic_listen.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="28dp"
+    android:height="28dp"
+    android:viewportWidth="28"
+    android:viewportHeight="28">
+  <path
+      android:pathData="M10.5,15.17c2.58,0 4.67,-2.09 4.67,-4.67s-2.09,-4.67 -4.67,-4.67c-2.58,0 -4.67,2.09 -4.67,4.67S7.92,15.17 10.5,15.17zM10.5,8.17c1.28,0 2.33,1.05 2.33,2.33s-1.05,2.33 -2.33,2.33c-1.28,0 -2.33,-1.05 -2.33,-2.33S9.22,8.17 10.5,8.17z"
+      android:fillColor="#4285F4"/>
+  <path
+      android:pathData="M10.5,17.5c-3.11,0 -9.33,1.56 -9.33,4.67v2.33h18.67v-2.33C19.83,19.06 13.62,17.5 10.5,17.5zM3.5,22.17c0.26,-0.84 3.86,-2.33 7,-2.33c3.15,0 6.77,1.5 7,2.33H3.5z"
+      android:fillColor="#4285F4"/>
+  <path
+      android:pathData="M25.67,10.5c0,0.36 -0.02,0.71 -0.05,1.05c-0.01,0.15 -0.03,0.29 -0.05,0.43c-0.02,0.18 -0.05,0.36 -0.08,0.54c-0.04,0.2 -0.07,0.39 -0.12,0.58c-0.01,0.06 -0.03,0.11 -0.04,0.17c-0.59,2.34 -1.81,4.01 -2.52,4.82c-0.09,0.1 -0.18,0.2 -0.28,0.3c-0.17,0.18 -0.27,0.27 -0.27,0.27l-1.65,-1.63c1.34,-1.33 2.27,-3.07 2.6,-5.01c0.01,-0.08 0.02,-0.16 0.04,-0.24c0.06,-0.42 0.1,-0.85 0.1,-1.29c0,-0.44 -0.04,-0.88 -0.1,-1.3c-0.01,-0.06 -0.02,-0.13 -0.03,-0.19c-0.32,-1.95 -1.25,-3.7 -2.6,-5.04l1.65,-1.63c0,0 0.11,0.1 0.27,0.27c0.09,0.1 0.19,0.2 0.28,0.3c0.71,0.82 1.93,2.48 2.52,4.82c0.01,0.06 0.03,0.11 0.04,0.17c0.04,0.19 0.08,0.38 0.12,0.58c0.03,0.18 0.06,0.36 0.08,0.54c0.02,0.14 0.04,0.28 0.05,0.43C25.65,9.79 25.67,10.14 25.67,10.5z"
+      android:fillColor="#EA4335"/>
+  <path
+      android:pathData="M20.61,8.4C20.85,9.06 21,9.76 21,10.5s-0.15,1.44 -0.39,2.1c-0.28,0.77 -0.71,1.46 -1.25,2.05l-1.66,-1.64c0.56,-0.63 0.91,-1.44 0.95,-2.34c0,-0.06 0.02,-0.11 0.02,-0.17s-0.01,-0.11 -0.02,-0.17c-0.04,-0.9 -0.39,-1.71 -0.95,-2.34l1.66,-1.64C19.9,6.94 20.32,7.63 20.61,8.4z"
+      android:fillColor="#FBBC04"/>
+</vector>
diff --git a/go/quickstep/res/drawable/ic_search.xml b/go/quickstep/res/drawable/ic_search.xml
new file mode 100644
index 0000000..4307330
--- /dev/null
+++ b/go/quickstep/res/drawable/ic_search.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="28dp"
+    android:height="28dp"
+    android:viewportWidth="28"
+    android:viewportHeight="28">
+  <path
+      android:pathData="M24.5,22.75l-6.84,-6.84c1,-1.35 1.59,-3.02 1.59,-4.83h-2.33c0,3.22 -2.62,5.83 -5.83,5.83v2.33c1.81,0 3.47,-0.6 4.83,-1.59l6.84,6.84L24.5,22.75z"
+      android:fillColor="#4285F4"/>
+  <path
+      android:pathData="M11.08,2.92v2.33c3.22,0 5.83,2.62 5.83,5.83h2.33C19.25,6.57 15.59,2.92 11.08,2.92z"
+      android:fillColor="#34A853"/>
+  <path
+      android:pathData="M5.25,11.08H2.92c0,4.51 3.66,8.17 8.17,8.17v-2.33C7.87,16.92 5.25,14.3 5.25,11.08z"
+      android:fillColor="#EA4335"/>
+  <path
+      android:pathData="M2.92,11.08h2.33c0,-3.22 2.62,-5.83 5.83,-5.83V2.92C6.57,2.92 2.92,6.57 2.92,11.08z"
+      android:fillColor="#FBBC04"/>
+</vector>
diff --git a/go/quickstep/res/drawable/ic_translate.xml b/go/quickstep/res/drawable/ic_translate.xml
new file mode 100644
index 0000000..1247807
--- /dev/null
+++ b/go/quickstep/res/drawable/ic_translate.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="28dp"
+    android:height="28dp"
+    android:viewportWidth="28"
+    android:viewportHeight="28">
+  <path
+      android:pathData="M12.28,15.19l-0.07,-0.05c-0.61,-0.49 -1.15,-1.05 -1.65,-1.63c-1.05,-1.22 -1.88,-2.63 -2.39,-4.17H5.83c0.54,2.17 1.58,4.16 3.01,5.85l-5.93,5.23l1.75,1.75l5.91,-5.26c0.05,0.04 0.1,0.09 0.15,0.13l3.42,2.79l1.02,-2.33L12.28,15.19z"
+      android:fillColor="#FBBC04"/>
+  <path
+      android:pathData="M21.58,11.67h-2.33l-5.25,14h2.33l1.31,-3.5h5.54l1.32,3.5h2.33L21.58,11.67zM18.53,19.83l1.89,-5.05l1.89,5.05H18.53z"
+      android:fillColor="#4285F4"/>
+  <path
+      android:pathData="M11.67,2.33l-2.34,0l0,2.34l-8.16,0l0,2.33l10.5,0l0,-2.33z"
+      android:fillColor="#EA4335"/>
+  <path
+      android:pathData="M11.67,4.67V7H14c-0.61,2.42 -1.79,4.65 -3.44,6.5c0.5,0.59 1.04,1.15 1.65,1.63l0.07,0.05c2.03,-2.32 3.44,-5.14 4.05,-8.19h3.5V4.67H11.67z"
+      android:fillColor="#34A853"/>
+</vector>
diff --git a/go/quickstep/res/values/config.xml b/go/quickstep/res/values/config.xml
index f376774..9dca137 100644
--- a/go/quickstep/res/values/config.xml
+++ b/go/quickstep/res/values/config.xml
@@ -14,5 +14,11 @@
      limitations under the License.
 -->
 <resources>
+    <!-- The component to receive app sharing Intents -->
     <string name="app_sharing_component" translatable="false"/>
+    <!-- The package to receive Listen, Translate, and Search Intents -->
+    <string name="niu_actions_package" translatable="false"/>
+
+    <!-- Feature Flags -->
+    <bool name="enable_niu_actions">false</bool>
 </resources>
\ No newline at end of file
diff --git a/go/quickstep/res/values/strings.xml b/go/quickstep/res/values/strings.xml
index fdd8397..71e2f3a 100644
--- a/go/quickstep/res/values/strings.xml
+++ b/go/quickstep/res/values/strings.xml
@@ -3,4 +3,12 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Label for app share drop target. [CHAR_LIMIT=20] -->
     <string name="app_share_drop_target_label">Share App</string>
+
+    <!-- ******* Overview ******* -->
+    <!-- Label for a button that lets the user listen to the content of the current app. [CHAR_LIMIT=40] -->
+    <string name="action_listen">Listen</string>
+    <!-- Label for a button that translates a screenshot of the current app. [CHAR_LIMIT=40] -->
+    <string name="action_translate">Translate</string>
+    <!-- Label for a button that triggers Search on a screenshot of the current app. [CHAR_LIMIT=40] -->
+    <string name="action_search">Search</string>
 </resources>
diff --git a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
new file mode 100644
index 0000000..b102a39
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
@@ -0,0 +1,147 @@
+/*
+ * 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.quickstep;
+
+import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL;
+import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Matrix;
+import android.os.SystemClock;
+import android.text.TextUtils;
+
+import com.android.launcher3.R;
+import com.android.quickstep.views.OverviewActionsView;
+import com.android.quickstep.views.TaskThumbnailView;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+/**
+ * Go-specific extension of the factory class that adds an overlay to TaskView
+ */
+public final class TaskOverlayFactoryGo extends TaskOverlayFactory {
+    public static final String ACTION_LISTEN = "com.android.quickstep.ACTION_LISTEN";
+    public static final String ACTION_TRANSLATE = "com.android.quickstep.ACTION_TRANSLATE";
+    public static final String ACTION_SEARCH = "com.android.quickstep.ACTION_SEARCH";
+    public static final String ELAPSED_NANOS = "niu_actions_elapsed_realtime_nanos";
+
+    // Empty constructor required for ResourceBasedOverride
+    public TaskOverlayFactoryGo(Context context) {}
+
+    /**
+     * Create a new overlay instance for the given View
+     */
+    public TaskOverlayGo createOverlay(TaskThumbnailView thumbnailView) {
+        return new TaskOverlayGo(thumbnailView);
+    }
+
+    /**
+     * Overlay on each task handling Overview Action Buttons.
+     * @param <T> The type of View in which the overlay will be placed
+     */
+    public static final class TaskOverlayGo<T extends OverviewActionsView> extends TaskOverlay {
+
+        private String mPackageName;
+
+        private TaskOverlayGo(TaskThumbnailView taskThumbnailView) {
+            super(taskThumbnailView);
+        }
+
+        /**
+         * Called when the current task is interactive for the user
+         */
+        @Override
+        public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix,
+                boolean rotated) {
+            getActionsView().updateDisabledFlags(DISABLED_NO_THUMBNAIL, thumbnail == null);
+            mPackageName =
+                    mApplicationContext.getResources().getString(R.string.niu_actions_package);
+
+            if (thumbnail == null || TextUtils.isEmpty(mPackageName)) {
+                return;
+            }
+
+            getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
+            boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
+            getActionsView().setCallbacks(new OverlayUICallbacksGoImpl(isAllowedByPolicy, task));
+        }
+
+        private void sendNIUIntent(String actionType) {
+            Intent intent = createNIUIntent(actionType);
+            mImageApi.shareAsDataWithExplicitIntent(/* crop */ null, intent);
+        }
+
+        private Intent createNIUIntent(String actionType) {
+            return new Intent(actionType)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
+                    .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
+                    .setPackage(mPackageName)
+                    .putExtra(ELAPSED_NANOS, SystemClock.elapsedRealtimeNanos());
+        }
+
+        protected class OverlayUICallbacksGoImpl extends OverlayUICallbacksImpl
+                implements OverlayUICallbacksGo {
+            public OverlayUICallbacksGoImpl(boolean isAllowedByPolicy, Task task) {
+                super(isAllowedByPolicy, task);
+            }
+
+            @SuppressLint("NewApi")
+            public void onListen() {
+                if (mIsAllowedByPolicy) {
+                    sendNIUIntent(ACTION_LISTEN);
+                } else {
+                    showBlockedByPolicyMessage();
+                }
+            }
+
+            @SuppressLint("NewApi")
+            public void onTranslate() {
+                if (mIsAllowedByPolicy) {
+                    sendNIUIntent(ACTION_TRANSLATE);
+                } else {
+                    showBlockedByPolicyMessage();
+                }
+            }
+
+            @SuppressLint("NewApi")
+            public void onSearch() {
+                if (mIsAllowedByPolicy) {
+                    sendNIUIntent(ACTION_SEARCH);
+                } else {
+                    showBlockedByPolicyMessage();
+                }
+            }
+        }
+    }
+
+    /**
+     * Callbacks the Ui can generate. This is the only way for a Ui to call methods on the
+     * controller.
+     */
+    public interface OverlayUICallbacksGo extends OverlayUICallbacks {
+        /** User has requested to listen to the current content read aloud */
+        void onListen();
+
+        /** User has requested a translation of the current content */
+        void onTranslate();
+
+        /** User has requested a visual search of the current content */
+        void onSearch();
+    }
+}
diff --git a/go/quickstep/src/com/android/quickstep/views/GoOverviewActionsView.java b/go/quickstep/src/com/android/quickstep/views/GoOverviewActionsView.java
new file mode 100644
index 0000000..9997d16
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/views/GoOverviewActionsView.java
@@ -0,0 +1,75 @@
+/*
+ * 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.quickstep.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.quickstep.TaskOverlayFactoryGo.OverlayUICallbacksGo;
+
+/**
+ * View for showing Go-specific action buttons in Overview
+ */
+public final class GoOverviewActionsView extends OverviewActionsView<OverlayUICallbacksGo> {
+    public GoOverviewActionsView(Context context) {
+        this(context, null);
+    }
+
+    public GoOverviewActionsView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public GoOverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        if (getResources().getBoolean(R.bool.enable_niu_actions)) {
+            findViewById(R.id.action_listen).setOnClickListener(this);
+            findViewById(R.id.action_translate).setOnClickListener(this);
+            findViewById(R.id.action_search).setOnClickListener(this);
+        } else {
+            findViewById(R.id.action_listen).setVisibility(View.GONE);
+            findViewById(R.id.action_translate).setVisibility(View.GONE);
+            findViewById(R.id.action_search).setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        super.onClick(view);
+
+        if (mCallbacks == null) {
+            return;
+        }
+        int id = view.getId();
+        if (id == R.id.action_listen) {
+            mCallbacks.onListen();
+        } else if (id == R.id.action_translate) {
+            mCallbacks.onTranslate();
+        } else if (id == R.id.action_search) {
+            mCallbacks.onSearch();
+        }
+    }
+}
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
index 89b3831..a202095 100644
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -25,7 +25,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.icons.ComponentWithLabelAndIcon;
 import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -43,17 +43,17 @@
     public static final boolean GO_DISABLE_WIDGETS = true;
     public static final boolean GO_DISABLE_NOTIFICATION_DOTS = true;
 
-    private static final ArrayList<WidgetListRowEntry> EMPTY_WIDGET_LIST = new ArrayList<>();
+    private static final ArrayList<WidgetsListBaseEntry> EMPTY_WIDGET_LIST = new ArrayList<>();
 
     /**
-     * Returns a list of {@link WidgetListRowEntry}. All {@link WidgetItem} in a single row
-     * are sorted (based on label and user), but the overall list of {@link WidgetListRowEntry}s
-     * is not sorted. This list is sorted at the UI when using
-     * {@link com.android.launcher3.widget.WidgetsDiffReporter}
+     * Returns a list of {@link WidgetsListBaseEntry}. All {@link WidgetItem} in a single row are
+     * sorted (based on label and user), but the overall list of {@link WidgetsListBaseEntry}s is
+     * not sorted. This list is sorted at the UI when using
+     * {@link com.android.launcher3.widget.picker.WidgetsDiffReporter}
      *
-     * @see com.android.launcher3.widget.WidgetsListAdapter#setWidgets(ArrayList)
+     * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
      */
-    public synchronized ArrayList<WidgetListRowEntry> getWidgetsList(Context context) {
+    public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsList(Context context) {
         return EMPTY_WIDGET_LIST;
     }
 
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 7431551..97f4a21 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -39,6 +39,7 @@
     <uses-permission android:name="android.permission.SET_ORIENTATION"/>
     <uses-permission android:name="android.permission.READ_FRAME_BUFFER"/>
     <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY"/>
+    <uses-permission android:name="android.permission.MONITOR_INPUT"/>
 
     <uses-permission android:name="${packageName}.permission.HOTSEAT_EDU" />
     <uses-permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY" />
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
similarity index 100%
rename from quickstep/res/layout/overview_actions_container.xml
rename to quickstep/overview_ui_overrides/res/layout/overview_actions_container.xml
diff --git a/quickstep/overview_ui_overrides/res/values/config.xml b/quickstep/overview_ui_overrides/res/values/config.xml
new file mode 100644
index 0000000..0f09439
--- /dev/null
+++ b/quickstep/overview_ui_overrides/res/values/config.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<resources>
+    <string name="task_overlay_factory_class" translatable="false"/>
+</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 9ec303a..be66104 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -14,8 +14,6 @@
      limitations under the License.
 -->
 <resources>
-    <string name="task_overlay_factory_class" translatable="false"/>
-
     <string name="overscroll_plugin_factory_class" translatable="false" />
 
     <!-- Activities which block home gesture -->
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 2e018f3..8b4f4f8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -21,12 +21,11 @@
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
-import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS;
-import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
 import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
@@ -105,12 +104,11 @@
 
     @Override
     protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
-        // If the app launch is from DeviceSearchResultContainer then add the InstanceId from
-        // LiveSearchManager to recreate the AllApps search session on the server side.
+        // If the app launch is from any of the surfaces in AllApps then add the InstanceId from
+        // LiveSearchManager to recreate the AllApps session on the server side.
         Optional<InstanceId> logInstanceId = this.getLiveSearchManager().getLogInstanceId();
-        if (info.getContainerInfo().getContainerCase() == EXTENDED_CONTAINERS
-                && info.getContainerInfo().getExtendedContainers().getContainerCase()
-                == DEVICE_SEARCH_RESULT_CONTAINER && logInstanceId.isPresent()) {
+        if (logInstanceId.isPresent() && ALL_APPS.equals(
+                getStateManager().getCurrentStableState())) {
             instanceId = logInstanceId.get();
         }
 
diff --git a/quickstep/src/com/android/quickstep/ImageActionsApi.java b/quickstep/src/com/android/quickstep/ImageActionsApi.java
index cb4d53a..8cb64c2 100644
--- a/quickstep/src/com/android/quickstep/ImageActionsApi.java
+++ b/quickstep/src/com/android/quickstep/ImageActionsApi.java
@@ -64,6 +64,20 @@
      */
     @UiThread
     public void shareWithExplicitIntent(@Nullable Rect crop, Intent intent) {
+        addImageAndSendIntent(crop, intent, false);
+    }
+
+    /**
+     * Share the image this api was constructed with using the provided intent. The implementation
+     * should set the intent's data field to the URI pointing to the image.
+     */
+    @UiThread
+    public void shareAsDataWithExplicitIntent(@Nullable Rect crop, Intent intent) {
+        addImageAndSendIntent(crop, intent, true);
+    }
+
+    @UiThread
+    private void addImageAndSendIntent(@Nullable Rect crop, Intent intent, boolean setData) {
         if (mBitmapSupplier.get() == null) {
             Log.e(TAG, "No snapshot available, not starting share.");
             return;
@@ -71,12 +85,14 @@
 
         UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(mContext,
                 mBitmapSupplier.get(), crop, intent, (uri, intentForUri) -> {
-                    intentForUri
-                            .addFlags(FLAG_GRANT_READ_URI_PERMISSION)
-                            .putExtra(EXTRA_STREAM, uri);
+                    intentForUri.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
+                    if (setData) {
+                        intentForUri.setData(uri);
+                    } else {
+                        intentForUri.putExtra(EXTRA_STREAM, uri);
+                    }
                     return new Intent[]{intentForUri};
                 }, TAG));
-
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 844d6f5..0d2c42e 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -122,12 +122,11 @@
      */
     public static class TaskOverlay<T extends OverviewActionsView> {
 
-        private final Context mApplicationContext;
+        protected final Context mApplicationContext;
         protected final TaskThumbnailView mThumbnailView;
 
         private T mActionsView;
-        private ImageActionsApi mImageApi;
-        private boolean mIsAllowedByPolicy;
+        protected ImageActionsApi mImageApi;
 
         protected TaskOverlay(TaskThumbnailView taskThumbnailView) {
             mApplicationContext = taskThumbnailView.getContext().getApplicationContext();
@@ -153,24 +152,8 @@
 
             if (thumbnail != null) {
                 getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
-                final boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
-
-                getActionsView().setCallbacks(new OverlayUICallbacks() {
-                    @Override
-                    public void onShare() {
-                        if (isAllowedByPolicy) {
-                            endLiveTileMode(() -> mImageApi.startShareActivity(null));
-                        } else {
-                            showBlockedByPolicyMessage();
-                        }
-                    }
-
-                    @SuppressLint("NewApi")
-                    @Override
-                    public void onScreenshot() {
-                        endLiveTileMode(() -> saveScreenshot(task));
-                    }
-                });
+                boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
+                getActionsView().setCallbacks(new OverlayUICallbacksImpl(isAllowedByPolicy, task));
             }
         }
 
@@ -193,7 +176,7 @@
          * Called to save screenshot of the task thumbnail.
          */
         @SuppressLint("NewApi")
-        private void saveScreenshot(Task task) {
+        protected void saveScreenshot(Task task) {
             if (mThumbnailView.isRealSnapshot()) {
                 mImageApi.saveScreenshot(mThumbnailView.getThumbnail(),
                         getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key);
@@ -257,7 +240,7 @@
             return mThumbnailView.getScaledInsets();
         }
 
-        private void showBlockedByPolicyMessage() {
+        protected void showBlockedByPolicyMessage() {
             Toast.makeText(
                     mThumbnailView.getContext(),
                     R.string.blocked_by_policy,
@@ -279,6 +262,29 @@
                 dismissTaskMenuView(mActivity);
             }
         }
+
+        protected class OverlayUICallbacksImpl implements OverlayUICallbacks {
+            protected final boolean mIsAllowedByPolicy;
+            protected final Task mTask;
+
+            public OverlayUICallbacksImpl(boolean isAllowedByPolicy, Task task) {
+                mIsAllowedByPolicy = isAllowedByPolicy;
+                mTask = task;
+            }
+
+            public void onShare() {
+                if (mIsAllowedByPolicy) {
+                    endLiveTileMode(() -> mImageApi.startShareActivity(null));
+                } else {
+                    showBlockedByPolicyMessage();
+                }
+            }
+
+            @SuppressLint("NewApi")
+            public void onScreenshot() {
+                endLiveTileMode(() -> saveScreenshot(mTask));
+            }
+        }
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 8ebea33..e243715 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -24,7 +24,6 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
 
@@ -283,9 +282,7 @@
             return;
         }
 
-        Bundle bundle = SystemUiProxy.INSTANCE.get(this).monitorGestureInput("swipe-up",
-                mDeviceState.getDisplayId());
-        mInputMonitorCompat = InputMonitorCompat.fromBundle(bundle, KEY_EXTRA_INPUT_MONITOR);
+        mInputMonitorCompat = new InputMonitorCompat("swipe-up", mDeviceState.getDisplayId());
         mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
                 mMainChoreographer, this::onInputEvent);
 
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 5bae3c7..932ff27 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -139,7 +139,7 @@
 
         addDepthAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
 
-        mAnimators.play(launcher.getDragLayer().getScrim().createSysuiMultiplierAnim(0f, 1f)
+        mAnimators.play(launcher.getDragLayer().getSysUiScrim().createSysuiMultiplierAnim(0f, 1f)
                 .setDuration(ALPHA_DURATION_MS));
         mAnimators.addListener(new AnimatorListenerAdapter() {
             @Override
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index f507a88..6e7cf0f 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.launcher3.widget.WidgetsFullSheet
+<com.android.launcher3.widget.picker.WidgetsFullSheet
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
@@ -27,7 +27,7 @@
         android:background="?android:attr/colorPrimary"
         android:elevation="4dp">
 
-        <com.android.launcher3.widget.WidgetsRecyclerView
+        <com.android.launcher3.widget.picker.WidgetsRecyclerView
             android:id="@+id/widgets_list_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
@@ -49,4 +49,4 @@
             android:layout_alignParentTop="true"
             android:layout_marginEnd="@dimen/fastscroll_end_margin" />
     </com.android.launcher3.views.TopRoundedCornerView>
-</com.android.launcher3.widget.WidgetsFullSheet>
\ No newline at end of file
+</com.android.launcher3.widget.picker.WidgetsFullSheet>
\ No newline at end of file
diff --git a/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java b/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java
new file mode 100644
index 0000000..dbf4b3e
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/testing/TestActivity.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.testing;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BaseDragLayer;
+
+/** An empty activity for {@link android.app.Fragment}s, {@link android.view.View}s testing. */
+public class TestActivity extends BaseActivity implements ActivityContext {
+
+    private DeviceProfile mDeviceProfile;
+
+    @Override
+    public BaseDragLayer getDragLayer() {
+        return null;
+    }
+
+    @Override
+    public DeviceProfile getDeviceProfile() {
+        return mDeviceProfile;
+    }
+
+    public void setDeviceProfile(DeviceProfile deviceProfile) {
+        mDeviceProfile = deviceProfile;
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
index 957ae77..34cb2ad 100644
--- a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
+++ b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
@@ -38,7 +38,7 @@
 import com.android.launcher3.util.LauncherLayoutBuilder;
 import com.android.launcher3.util.LauncherLayoutBuilder.FolderBuilder;
 import com.android.launcher3.util.LauncherModelHelper;
-import com.android.launcher3.widget.WidgetsFullSheet;
+import com.android.launcher3.widget.picker.WidgetsFullSheet;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
deleted file mode 100644
index 5ab3106..0000000
--- a/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2017 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 static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.times;
-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 androidx.recyclerview.widget.RecyclerView;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.model.data.PackageItemInfo;
-
-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.Collections;
-
-@RunWith(RobolectricTestRunner.class)
-public class WidgetsListAdapterTest {
-
-    @Mock private LayoutInflater mMockLayoutInflater;
-    @Mock private WidgetPreviewLoader mMockWidgetCache;
-    @Mock private RecyclerView.AdapterDataObserver mListener;
-    @Mock private IconCache mIconCache;
-
-    private WidgetsListAdapter mAdapter;
-    private InvariantDeviceProfile mTestProfile;
-    private Context mContext;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        mContext = RuntimeEnvironment.application;
-        mTestProfile = new InvariantDeviceProfile();
-        mTestProfile.numRows = 5;
-        mTestProfile.numColumns = 5;
-        mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
-                mIconCache, null, null);
-        mAdapter.registerAdapterDataObserver(mListener);
-    }
-
-    @Test
-    public void test_notifyDataSetChanged() throws Exception {
-        mAdapter.setWidgets(generateSampleMap(1));
-        verify(mListener, times(1)).onChanged();
-    }
-
-    @Test
-    public void test_notifyItemInserted() throws Exception {
-        mAdapter.setWidgets(generateSampleMap(1));
-        mAdapter.setWidgets(generateSampleMap(2));
-        verify(mListener, times(1)).onChanged();
-        verify(mListener, times(1)).onItemRangeInserted(eq(1), eq(1));
-    }
-
-    @Test
-    public void test_notifyItemRemoved() throws Exception {
-        mAdapter.setWidgets(generateSampleMap(2));
-        mAdapter.setWidgets(generateSampleMap(1));
-        verify(mListener, times(1)).onChanged();
-        verify(mListener, times(1)).onItemRangeRemoved(eq(1), eq(1));
-    }
-
-    @Test
-    public void testNotifyItemChanged_PackageIconDiff() throws Exception {
-        mAdapter.setWidgets(generateSampleMap(1));
-        mAdapter.setWidgets(generateSampleMap(1));
-        verify(mListener, times(1)).onChanged();
-        verify(mListener, times(1)).onItemRangeChanged(eq(0), eq(1), isNull());
-    }
-
-    @Test
-    public void testNotifyItemChanged_widgetItemInfoDiff() throws Exception {
-        // TODO: same package name but item number changed
-    }
-
-    @Test
-    public void testNotifyItemInsertedRemoved_hodgepodge() throws Exception {
-        // TODO: insert and remove combined.          curMap
-        // newMap [A, C, D]                           [A, B, E]
-        // B - C < 0, removed B from index 1          [A, E]
-        // E - C > 0, C inserted to index 1           [A, C, E]
-        // E - D > 0, D inserted to index 2           [A, C, D, E]
-        // E - null = -1, E deleted from index 3      [A, C, D]
-    }
-
-    /**
-     * Helper method to generate the sample widget model map that can be used for the tests
-     * @param num the number of WidgetItem the map should contain
-     */
-    private ArrayList<WidgetListRowEntry> generateSampleMap(int num) {
-        ArrayList<WidgetListRowEntry> result = new ArrayList<>();
-        if (num <= 0) return result;
-        ShadowPackageManager spm = shadowOf(mContext.getPackageManager());
-
-        for (int i = 0; i < num; i++) {
-            ComponentName cn = new ComponentName("com.placeholder.apk" + i, "PlaceholderWidet");
-
-            AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
-            widgetInfo.provider = cn;
-            ReflectionHelpers.setField(widgetInfo, "providerInfo", spm.addReceiverIfNotPresent(cn));
-
-            WidgetItem wi = new WidgetItem(LauncherAppWidgetProviderInfo
-                    .fromProviderInfo(mContext, widgetInfo), mTestProfile, mIconCache);
-
-            PackageItemInfo pInfo = new PackageItemInfo(wi.componentName.getPackageName());
-            pInfo.title = pInfo.packageName;
-            pInfo.user = wi.user;
-            pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
-
-            result.add(new WidgetListRowEntry(pInfo, new ArrayList<>(Collections.singleton(wi))));
-        }
-
-        return result;
-    }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
new file mode 100644
index 0000000..9bea2fb
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2017 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 org.mockito.ArgumentMatchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+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 androidx.recyclerview.widget.RecyclerView;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+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.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+
+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 final class WidgetsListAdapterTest {
+
+    private static final String TEST_PACKAGE_1 = "com.google.test.1";
+    private static final String TEST_PACKAGE_2 = "com.google.test.2";
+
+    @Mock private LayoutInflater mMockLayoutInflater;
+    @Mock private WidgetPreviewLoader mMockWidgetCache;
+    @Mock private RecyclerView.AdapterDataObserver mListener;
+    @Mock private IconCache mIconCache;
+
+    private WidgetsListAdapter mAdapter;
+    private InvariantDeviceProfile mTestProfile;
+    private Context mContext;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mTestProfile = new InvariantDeviceProfile();
+        mTestProfile.numRows = 5;
+        mTestProfile.numColumns = 5;
+        mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
+                mIconCache, null, null);
+        mAdapter.registerAdapterDataObserver(mListener);
+
+        doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
+                        .getComponent().getPackageName())
+                .when(mIconCache).getTitleNoCache(any());
+    }
+
+    @Test
+    public void setWidgets_shouldNotifyDataSetChanged() {
+        mAdapter.setWidgets(generateSampleMap(1));
+
+        verify(mListener).onChanged();
+    }
+
+    @Test
+    public void setWidgets_withItemInserted_shouldNotifyItemInserted() {
+        mAdapter.setWidgets(generateSampleMap(1));
+        mAdapter.setWidgets(generateSampleMap(2));
+
+        verify(mListener).onItemRangeInserted(eq(1), eq(1));
+    }
+
+    @Test
+    public void setWidgets_withItemRemoved_shouldNotifyItemRemoved() {
+        mAdapter.setWidgets(generateSampleMap(2));
+        mAdapter.setWidgets(generateSampleMap(1));
+
+        verify(mListener).onItemRangeRemoved(eq(1), eq(1));
+    }
+
+    @Test
+    public void setWidgets_appIconChanged_shouldNotifyItemChanged() {
+        mAdapter.setWidgets(generateSampleMap(1));
+        mAdapter.setWidgets(generateSampleMap(1));
+
+        verify(mListener).onItemRangeChanged(eq(0), eq(1), isNull());
+    }
+
+    @Test
+    public void setWidgets_sameApp_moreWidgets_shouldNotifyItemChangedWithWidgetItemInfoDiff() {
+        // GIVEN the adapter was first populated with test package 1 & test package 2.
+        WidgetsListBaseEntry testPackage1With2WidgetsListEntry =
+                generateSampleAppWithWidgets(TEST_PACKAGE_1, /* numOfWidgets= */ 2);
+        WidgetsListBaseEntry testPackage2With2WidgetsListEntry =
+                generateSampleAppWithWidgets(TEST_PACKAGE_2, /* numOfWidgets= */ 2);
+        mAdapter.setWidgets(
+                List.of(testPackage1With2WidgetsListEntry, testPackage2With2WidgetsListEntry));
+
+        // WHEN the adapter is updated with the same list of apps but test package 2 has 3 widgets
+        // now.
+        WidgetsListBaseEntry testPackage1With3WidgetsListEntry =
+                generateSampleAppWithWidgets(TEST_PACKAGE_2, /* numOfWidgets= */ 2);
+        mAdapter.setWidgets(
+                List.of(testPackage1With2WidgetsListEntry, testPackage1With3WidgetsListEntry));
+
+        // THEN the onItemRangeChanged is invoked.
+        verify(mListener).onItemRangeChanged(eq(1), eq(1), isNull());
+    }
+
+    @Test
+    public void setWidgets_hodgepodge_shouldInvokeExpectedDataObserverCallbacks() {
+        List<WidgetsListBaseEntry> allAppsWithWidgets = generateSampleMap(5);
+        // GIVEN the current widgets list consist of [A, B, E].
+        List<WidgetsListBaseEntry> currentList = List.of(
+                allAppsWithWidgets.get(0), allAppsWithWidgets.get(1), allAppsWithWidgets.get(4));
+        mAdapter.setWidgets(currentList);
+
+        // WHEN the widgets list is updated to [A, C, D].
+        List<WidgetsListBaseEntry> newList = List.of(
+                allAppsWithWidgets.get(0), allAppsWithWidgets.get(2), allAppsWithWidgets.get(3));
+        mAdapter.setWidgets(newList);
+
+        // Computation logic                           | [Intermediate list during computation]
+        // THEN B <> C < 0, removed B from index 1     | [A, E]
+        verify(mListener).onItemRangeRemoved(/* positionStart= */ 1, /* itemCount= */ 1);
+        // THEN E <> C > 0, C inserted to index 1      | [A, C, E]
+        verify(mListener).onItemRangeInserted(/* positionStart= */ 1, /* itemCount= */ 1);
+        // THEN E <> D > 0, D inserted to index 2      | [A, C, D, E]
+        verify(mListener).onItemRangeInserted(/* positionStart= */ 2, /* itemCount= */ 1);
+        // THEN E <> null = -1, E deleted from index 3 | [A, C, D]
+        verify(mListener).onItemRangeRemoved(/* positionStart= */ 3, /* itemCount= */ 1);
+    }
+
+    /**
+     * Helper method to generate the sample widget model map that can be used for the tests
+     * @param num the number of WidgetItem the map should contain
+     */
+    private ArrayList<WidgetsListBaseEntry> generateSampleMap(int num) {
+        ArrayList<WidgetsListBaseEntry> result = new ArrayList<>();
+        if (num <= 0) return result;
+
+        for (int i = 0; i < num; i++) {
+            String packageName = "com.placeholder.apk" + i;
+
+            List<WidgetItem> widgetItems = generateWidgetItems(packageName, /* numOfWidgets= */ 1);
+
+            PackageItemInfo pInfo = new PackageItemInfo(packageName);
+            pInfo.title = pInfo.packageName;
+            pInfo.user = widgetItems.get(0).user;
+            pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
+
+            result.add(new WidgetsListContentEntry(pInfo, /* titleSectionName= */ "", widgetItems));
+        }
+
+        return result;
+    }
+
+    private WidgetsListBaseEntry generateSampleAppWithWidgets(String packageName,
+            int numOfWidgets) {
+        PackageItemInfo appInfo = new PackageItemInfo(packageName);
+        appInfo.title = appInfo.packageName;
+        appInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
+
+        return new WidgetsListContentEntry(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/WidgetsListRowViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinderTest.java
new file mode 100644
index 0000000..4e9e227
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinderTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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 android.os.Looper.getMainLooper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+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.widget.WidgetCell;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+
+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 WidgetsListRowViewHolderBinderTest {
+    private static final String TEST_PACKAGE = "com.google.test";
+    private static final String APP_NAME = "Test app";
+
+    private Context mContext;
+    private WidgetsListRowViewHolderBinder 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 OnLongClickListener mOnLongClickListener;
+    @Mock
+    private OnClickListener mOnIconClickListener;
+    @Mock
+    private IconCache mIconCache;
+    @Mock
+    private WidgetPreviewLoader mWidgetPreviewLoader;
+    @Mock
+    private DeviceProfile mDeviceProfile;
+
+    @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());
+
+        mViewHolderBinder = new WidgetsListRowViewHolderBinder(
+                mContext,
+                LayoutInflater.from(mTestActivity),
+                mOnIconClickListener,
+                mOnLongClickListener,
+                mWidgetPreviewLoader);
+    }
+
+    @After
+    public void tearDown() {
+        mActivityController.destroy();
+    }
+
+    @Test
+    public void bindViewHolder_appWith3Widgets_shouldMatchAppTitle() {
+        WidgetsRowViewHolder viewHolder = mViewHolderBinder.newViewHolder(
+                new FrameLayout(mTestActivity));
+        WidgetsListContentEntry entry = generateSampleAppWithWidgets(
+                APP_NAME,
+                TEST_PACKAGE,
+                /* numOfWidgets= */ 3);
+        mViewHolderBinder.bindViewHolder(viewHolder, entry);
+
+        assertThat(viewHolder.title.getText()).isEqualTo(APP_NAME);
+    }
+
+    @Test
+    public void bindViewHolder_appWith3Widgets_shouldHave3Widgets() {
+        WidgetsRowViewHolder viewHolder = mViewHolderBinder.newViewHolder(
+                new FrameLayout(mTestActivity));
+        WidgetsListContentEntry entry = generateSampleAppWithWidgets(
+                APP_NAME,
+                TEST_PACKAGE,
+                /* numOfWidgets= */ 3);
+        mViewHolderBinder.bindViewHolder(viewHolder, entry);
+        shadowOf(getMainLooper()).idle();
+
+        // THEN the cell container has 5 children: 3 widgets + 2 separators
+        // Index:        0       1        2       3        4
+        // View:  .SampleWidget0 | .SampleWidget1 | .SampleWidget2
+        assertThat(viewHolder.cellContainer.getChildCount()).isEqualTo(5);
+        // Widget 0 label is .SampleWidget0.
+        assertWidgetCellWithLabel(viewHolder.cellContainer.getChildAt(0), ".SampleWidget0");
+        // Widget 1 label is .SampleWidget1.
+        assertWidgetCellWithLabel(viewHolder.cellContainer.getChildAt(2), ".SampleWidget1");
+        // Widget 2 label is .SampleWidget2.
+        assertWidgetCellWithLabel(viewHolder.cellContainer.getChildAt(4), ".SampleWidget2");
+    }
+
+    private WidgetsListContentEntry generateSampleAppWithWidgets(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 WidgetsListContentEntry(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;
+    }
+
+    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);
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
new file mode 100644
index 0000000..a11d0c9
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.model;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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 com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+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.model.WidgetsListContentEntry;
+
+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.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(RobolectricTestRunner.class)
+public final class WidgetsListContentEntryTest {
+    private static final String PACKAGE_NAME = "com.google.test";
+    private static final PackageItemInfo PACKAGE_ITEM_INFO = new PackageItemInfo(PACKAGE_NAME);
+    private static final ComponentName WIDGET_1 = ComponentName.createRelative(PACKAGE_NAME,
+            ".widget1");
+    private static final ComponentName WIDGET_2 = ComponentName.createRelative(PACKAGE_NAME,
+            ".widget2");
+    private static final ComponentName WIDGET_3 = ComponentName.createRelative(PACKAGE_NAME,
+            ".widget3");
+    private static final Map<ComponentName, String> WIDGETS_TO_LABELS = new HashMap();
+
+    static {
+        WIDGETS_TO_LABELS.put(WIDGET_1, "Cat");
+        WIDGETS_TO_LABELS.put(WIDGET_2, "Dog");
+        WIDGETS_TO_LABELS.put(WIDGET_3, "Bird");
+    }
+
+    @Mock private IconCache mIconCache;
+
+    private Context mContext;
+    private InvariantDeviceProfile mTestProfile;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mTestProfile = new InvariantDeviceProfile();
+        mTestProfile.numRows = 5;
+        mTestProfile.numColumns = 5;
+
+        doAnswer(invocation -> {
+            ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
+            return WIDGETS_TO_LABELS.get(componentWithLabel.getComponent());
+        }).when(mIconCache).getTitleNoCache(any());
+    }
+
+    @Test
+    public void unsortedWidgets_diffLabels_shouldSortWidgetItems() {
+        // GIVEN a list of widgets in unsorted order.
+        // Cat 2x3
+        WidgetItem widgetItem1 = createWidgetItem(WIDGET_1, /* spanX= */ 2, /* spanY= */ 3);
+        // Dog 2x3
+        WidgetItem widgetItem2 = createWidgetItem(WIDGET_2, /* spanX= */ 2, /* spanY= */ 3);
+        // Bird 2x3
+        WidgetItem widgetItem3 = createWidgetItem(WIDGET_3, /* spanX= */ 2, /* spanY= */ 3);
+
+        // WHEN creates a WidgetsListRowEntry with the unsorted widgets.
+        WidgetsListContentEntry widgetsListRowEntry = new WidgetsListContentEntry(PACKAGE_ITEM_INFO,
+                /* titleSectionName= */ "T",
+                List.of(widgetItem1, widgetItem2, widgetItem3));
+
+        // THEN the widgets list is sorted by their labels alphabetically: [Bird, Cat, Dog].
+        assertThat(widgetsListRowEntry.mWidgets)
+                .containsExactly(widgetItem3, widgetItem1, widgetItem2)
+                .inOrder();
+        assertThat(widgetsListRowEntry.mTitleSectionName).isEqualTo("T");
+        assertThat(widgetsListRowEntry.mPkgItem).isEqualTo(PACKAGE_ITEM_INFO);
+    }
+
+    @Test
+    public void unsortedWidgets_sameLabels_differentSize_shouldSortWidgetItems() {
+        // GIVEN a list of widgets in unsorted order.
+        // Cat 3x3
+        WidgetItem widgetItem1 = createWidgetItem(WIDGET_1, /* spanX= */ 3, /* spanY= */ 3);
+        // Cat 1x2
+        WidgetItem widgetItem2 = createWidgetItem(WIDGET_1, /* spanX= */ 1, /* spanY= */ 2);
+        // Cat 2x2
+        WidgetItem widgetItem3 = createWidgetItem(WIDGET_1, /* spanX= */ 2, /* spanY= */ 2);
+
+        // WHEN creates a WidgetsListRowEntry with the unsorted widgets.
+        WidgetsListContentEntry widgetsListRowEntry = new WidgetsListContentEntry(PACKAGE_ITEM_INFO,
+                /* titleSectionName= */ "T",
+                List.of(widgetItem1, widgetItem2, widgetItem3));
+
+        // THEN the widgets list is sorted by their gird sizes in an ascending order:
+        // [1x2, 2x2, 3x3].
+        assertThat(widgetsListRowEntry.mWidgets)
+                .containsExactly(widgetItem2, widgetItem3, widgetItem1)
+                .inOrder();
+        assertThat(widgetsListRowEntry.mTitleSectionName).isEqualTo("T");
+        assertThat(widgetsListRowEntry.mPkgItem).isEqualTo(PACKAGE_ITEM_INFO);
+    }
+
+    @Test
+    public void unsortedWidgets_hodgepodge_shouldSortWidgetItems() {
+        // GIVEN a list of widgets in unsorted order.
+        // Cat 3x3
+        WidgetItem widgetItem1 = createWidgetItem(WIDGET_1, /* spanX= */ 3, /* spanY= */ 3);
+        // Cat 1x2
+        WidgetItem widgetItem2 = createWidgetItem(WIDGET_1, /* spanX= */ 1, /* spanY= */ 2);
+        // Dog 2x2
+        WidgetItem widgetItem3 = createWidgetItem(WIDGET_2, /* spanX= */ 2, /* spanY= */ 2);
+        // Bird 2x2
+        WidgetItem widgetItem4 = createWidgetItem(WIDGET_3, /* spanX= */ 2, /* spanY= */ 2);
+
+        // WHEN creates a WidgetsListRowEntry with the unsorted widgets.
+        WidgetsListContentEntry widgetsListRowEntry = new WidgetsListContentEntry(PACKAGE_ITEM_INFO,
+                /* titleSectionName= */ "T",
+                List.of(widgetItem1, widgetItem2, widgetItem3, widgetItem4));
+
+        // THEN the widgets list is first sorted by labels alphabetically. Then, for widgets with
+        // same labels, they are sorted by their gird sizes in an ascending order:
+        // [Bird 2x2, Cat 1x2, Cat 3x3, Dog 2x2]
+        assertThat(widgetsListRowEntry.mWidgets)
+                .containsExactly(widgetItem4, widgetItem2, widgetItem1, widgetItem3)
+                .inOrder();
+        assertThat(widgetsListRowEntry.mTitleSectionName).isEqualTo("T");
+        assertThat(widgetsListRowEntry.mPkgItem).isEqualTo(PACKAGE_ITEM_INFO);
+    }
+
+    private WidgetItem createWidgetItem(ComponentName componentName, int spanX, int spanY) {
+        String label = WIDGETS_TO_LABELS.get(componentName);
+        ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
+        AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
+        widgetInfo.provider = componentName;
+        ReflectionHelpers.setField(widgetInfo, "providerInfo",
+                packageManager.addReceiverIfNotPresent(componentName));
+
+        LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
+                LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo);
+        launcherAppWidgetProviderInfo.spanX = spanX;
+        launcherAppWidgetProviderInfo.spanY = spanY;
+        launcherAppWidgetProviderInfo.label = label;
+
+        return new WidgetItem(launcherAppWidgetProviderInfo, mTestProfile, mIconCache);
+    }
+}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 344ae0a..2df9cbe 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -181,10 +181,10 @@
 import com.android.launcher3.widget.PendingAppWidgetHostView;
 import com.android.launcher3.widget.WidgetAddFlowHandler;
 import com.android.launcher3.widget.WidgetHostViewLoader;
-import com.android.launcher3.widget.WidgetListRowEntry;
 import com.android.launcher3.widget.WidgetManagerHelper;
-import com.android.launcher3.widget.WidgetsFullSheet;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.picker.WidgetsFullSheet;
 import com.android.systemui.plugins.OverlayPlugin;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.shared.LauncherExterns;
@@ -2552,7 +2552,7 @@
     }
 
     @Override
-    public void bindAllWidgets(final ArrayList<WidgetListRowEntry> allWidgets) {
+    public void bindAllWidgets(final List<WidgetsListBaseEntry> allWidgets) {
         mPopupDataProvider.setAllWidgets(allWidgets);
     }
 
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 51504ce..76c4518 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -4,12 +4,14 @@
 
 import android.annotation.TargetApi;
 import android.content.Context;
+import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.os.Build;
 import android.util.AttributeSet;
 import android.view.ViewDebug;
 import android.view.WindowInsets;
 
+import com.android.launcher3.graphics.SysUiScrim;
 import com.android.launcher3.statemanager.StatefulActivity;
 
 import java.util.Collections;
@@ -31,6 +33,8 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mForceHideBackArrow;
 
+    private SysUiScrim mSysUiScrim;
+
     public LauncherRootView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mActivity = StatefulActivity.fromContext(context);
@@ -89,6 +93,18 @@
         }
     }
 
+    public void setSysUiScrim(SysUiScrim scrim) {
+        mSysUiScrim = scrim;
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        if (mSysUiScrim != null) {
+            mSysUiScrim.draw(canvas);
+        }
+        super.dispatchDraw(canvas);
+    }
+
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
@@ -119,4 +135,4 @@
 
         void onWindowVisibilityChanged(int visibility);
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 4b95372..2528c03 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -81,6 +81,7 @@
 import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.graphics.WorkspaceDragScrim;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.StatsLogManager;
@@ -199,6 +200,9 @@
     private boolean mStripScreensOnPageStopMoving = false;
 
     private DragPreviewProvider mOutlineProvider = null;
+
+    private WorkspaceDragScrim mWorkspaceDragScrim;
+
     private boolean mWorkspaceFadeInAdjacentScreens;
 
     final WallpaperOffsetInterpolator mWallpaperOffset;
@@ -1161,6 +1165,19 @@
         }
     }
 
+    public void setWorkspaceDragScrim(WorkspaceDragScrim workspaceDragScrim) {
+        mWorkspaceDragScrim = workspaceDragScrim;
+    }
+
+    @Override
+    public void invalidate() {
+        // The workspace scrim may need to be re-rendered based on the workspace scroll
+        if (mWorkspaceDragScrim != null) {
+            mWorkspaceDragScrim.invalidate();
+        }
+        super.invalidate();
+    }
+
     @Override
     public void computeScroll() {
         super.computeScroll();
@@ -2027,7 +2044,7 @@
         }
         // Invalidating the scrim will also force this CellLayout
         // to be invalidated so that it is highlighted if necessary.
-        mLauncher.getDragLayer().getScrim().invalidate();
+        mLauncher.getDragLayer().getWorkspaceDragScrim().invalidate();
     }
 
     public CellLayout getCurrentDragOverlappingLayout() {
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index cd938e1..0e0ddfb 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -32,7 +32,7 @@
 import static com.android.launcher3.anim.Interpolators.ZOOM_OUT;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
-import static com.android.launcher3.graphics.WorkspaceAndHotseatScrim.SYSUI_PROGRESS;
+import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
@@ -49,7 +49,8 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.anim.SpringAnimationBuilder;
-import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
+import com.android.launcher3.graphics.SysUiScrim;
+import com.android.launcher3.graphics.WorkspaceDragScrim;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.DynamicResource;
 import com.android.systemui.plugins.ResourceProvider;
@@ -183,10 +184,12 @@
     }
 
     public void setScrim(PropertySetter propertySetter, LauncherState state) {
-        WorkspaceAndHotseatScrim scrim = mLauncher.getDragLayer().getScrim();
-        propertySetter.setFloat(scrim, SCRIM_PROGRESS, state.getWorkspaceScrimAlpha(mLauncher),
-                LINEAR);
-        propertySetter.setFloat(scrim, SYSUI_PROGRESS,
+        WorkspaceDragScrim workspaceDragScrim = mLauncher.getDragLayer().getWorkspaceDragScrim();
+        propertySetter.setFloat(workspaceDragScrim, SCRIM_PROGRESS,
+                state.getWorkspaceScrimAlpha(mLauncher), LINEAR);
+
+        SysUiScrim sysUiScrim = mLauncher.getDragLayer().getSysUiScrim();
+        propertySetter.setFloat(sysUiScrim, SYSUI_PROGRESS,
                 state.hasFlag(FLAG_HAS_SYS_UI_SCRIM) ? 1 : 0, LINEAR);
     }
 
diff --git a/src/com/android/launcher3/allapps/search/LiveSearchManager.java b/src/com/android/launcher3/allapps/search/LiveSearchManager.java
index d51c786..748ba50 100644
--- a/src/com/android/launcher3/allapps/search/LiveSearchManager.java
+++ b/src/com/android/launcher3/allapps/search/LiveSearchManager.java
@@ -153,11 +153,11 @@
             clearWidgetHost();
         }
 
-        if (finalState.equals(ALL_APPS)) {
+        if (ALL_APPS.equals(finalState)) {
             // creates new instance ID since new all apps session is started.
             mLogInstanceId = new InstanceIdSequence().newInstanceId();
             allAppsLogger().log(LAUNCHER_ALLAPPS_ENTRY);
-        } else if (mPrevLauncherState.equals(ALL_APPS)
+        } else if (ALL_APPS.equals(mPrevLauncherState)
                 // Check if mLogInstanceId is not null; to avoid NPE when LAUNCHER_ALLAPPS_EXIT is
                 // triggered multiple times
                 && mLogInstanceId != null) {
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index e71c12d..7a6b4f9 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -41,12 +41,14 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DropTargetBar;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherRootView;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.graphics.OverviewScrim;
-import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
+import com.android.launcher3.graphics.SysUiScrim;
+import com.android.launcher3.graphics.WorkspaceDragScrim;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.BaseDragLayer;
@@ -82,8 +84,10 @@
 
     // Related to adjacent page hints
     private final ViewGroupFocusHelper mFocusIndicatorHelper;
-    private final WorkspaceAndHotseatScrim mWorkspaceScrim;
     private final OverviewScrim mOverviewScrim;
+    private WorkspaceDragScrim mWorkspaceDragScrim;
+    private SysUiScrim mSysUiScrim;
+    private LauncherRootView mRootView;
 
     /**
      * Used to create a new DragLayer from XML.
@@ -99,14 +103,23 @@
         setChildrenDrawingOrderEnabled(true);
 
         mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
-        mWorkspaceScrim = new WorkspaceAndHotseatScrim(this);
         mOverviewScrim = new OverviewScrim(this);
     }
 
     public void setup(DragController dragController, Workspace workspace) {
         mDragController = dragController;
-        mWorkspaceScrim.setWorkspace(workspace);
         recreateControllers();
+
+        mWorkspaceDragScrim = new WorkspaceDragScrim((this));
+        mWorkspaceDragScrim.setWorkspace(workspace);
+
+        // We delegate drawing of the workspace scrim to LauncherRootView (one level up), so as
+        // to avoid artifacts when translating the entire drag layer in the -1 transition.
+        mRootView = (LauncherRootView) getParent();
+        mSysUiScrim = new SysUiScrim(mRootView);
+        mRootView.setSysUiScrim(mSysUiScrim);
+
+
     }
 
     @Override
@@ -515,7 +528,7 @@
     @Override
     protected void dispatchDraw(Canvas canvas) {
         // Draw the background below children.
-        mWorkspaceScrim.draw(canvas);
+        mWorkspaceDragScrim.draw(canvas);
         mOverviewScrim.updateCurrentScrimmedView(this);
         mFocusIndicatorHelper.draw(canvas);
         super.dispatchDraw(canvas);
@@ -535,18 +548,22 @@
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
-        mWorkspaceScrim.setSize(w, h);
+        mSysUiScrim.setSize(w, h);
     }
 
     @Override
     public void setInsets(Rect insets) {
         super.setInsets(insets);
-        mWorkspaceScrim.onInsetsChanged(insets, mAllowSysuiScrims);
+        mSysUiScrim.onInsetsChanged(insets, mAllowSysuiScrims);
         mOverviewScrim.onInsetsChanged(insets);
     }
 
-    public WorkspaceAndHotseatScrim getScrim() {
-        return mWorkspaceScrim;
+    public WorkspaceDragScrim getWorkspaceDragScrim() {
+        return mWorkspaceDragScrim;
+    }
+
+    public SysUiScrim getSysUiScrim() {
+        return mSysUiScrim;
     }
 
     public OverviewScrim getOverviewScrim() {
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
similarity index 83%
rename from src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
rename to src/com/android/launcher3/graphics/SysUiScrim.java
index 7b7ab5e..d9c648b 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -34,7 +34,6 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.graphics.Region;
 import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
 import android.util.DisplayMetrics;
@@ -44,41 +43,39 @@
 
 import androidx.core.graphics.ColorUtils;
 
-import com.android.launcher3.CellLayout;
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 import com.android.launcher3.util.Themes;
 
 /**
  * View scrim which draws behind hotseat and workspace
  */
-public class WorkspaceAndHotseatScrim extends Scrim {
+public class SysUiScrim extends Scrim {
 
-    public static final FloatProperty<WorkspaceAndHotseatScrim> SYSUI_PROGRESS =
-            new FloatProperty<WorkspaceAndHotseatScrim>("sysUiProgress") {
+    public static final FloatProperty<SysUiScrim> SYSUI_PROGRESS =
+            new FloatProperty<SysUiScrim>("sysUiProgress") {
                 @Override
-                public Float get(WorkspaceAndHotseatScrim scrim) {
+                public Float get(SysUiScrim scrim) {
                     return scrim.mSysUiProgress;
                 }
 
                 @Override
-                public void setValue(WorkspaceAndHotseatScrim scrim, float value) {
+                public void setValue(SysUiScrim scrim, float value) {
                     scrim.setSysUiProgress(value);
                 }
             };
 
-    private static final FloatProperty<WorkspaceAndHotseatScrim> SYSUI_ANIM_MULTIPLIER =
-            new FloatProperty<WorkspaceAndHotseatScrim>("sysUiAnimMultiplier") {
+    private static final FloatProperty<SysUiScrim> SYSUI_ANIM_MULTIPLIER =
+            new FloatProperty<SysUiScrim>("sysUiAnimMultiplier") {
                 @Override
-                public Float get(WorkspaceAndHotseatScrim scrim) {
+                public Float get(SysUiScrim scrim) {
                     return scrim.mSysUiAnimMultiplier;
                 }
 
                 @Override
-                public void setValue(WorkspaceAndHotseatScrim scrim, float value) {
+                public void setValue(SysUiScrim scrim, float value) {
                     scrim.mSysUiAnimMultiplier = value;
                     scrim.reapplySysUiAlpha();
                 }
@@ -108,10 +105,6 @@
     private static final int ALPHA_MASK_BITMAP_DP = 200;
     private static final int ALPHA_MASK_WIDTH_DP = 2;
 
-    private final Rect mHighlightRect = new Rect();
-
-    private Workspace mWorkspace;
-
     private boolean mDrawTopScrim, mDrawBottomScrim;
 
     private final RectF mFinalMaskRect = new RectF();
@@ -127,9 +120,8 @@
     private boolean mAnimateScrimOnNextDraw = false;
     private float mSysUiAnimMultiplier = 1;
 
-    public WorkspaceAndHotseatScrim(View view) {
+    public SysUiScrim(View view) {
         super(view);
-
         mMaskHeight = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_DP,
                 view.getResources().getDisplayMetrics());
         mTopScrim = Themes.getAttrDrawable(view.getContext(), R.attr.workspaceStatusBarScrim);
@@ -139,28 +131,10 @@
         onExtractedColorsChanged(mWallpaperColorInfo);
     }
 
-    public void setWorkspace(Workspace workspace)  {
-        mWorkspace = workspace;
-    }
-
+    /**
+     * Draw the top and bottom scrims
+     */
     public void draw(Canvas canvas) {
-        // Draw the background below children.
-        if (mScrimAlpha > 0) {
-            // Update the scroll position first to ensure scrim cutout is in the right place.
-            mWorkspace.computeScrollWithoutInvalidation();
-            CellLayout currCellLayout = mWorkspace.getCurrentDragOverlappingLayout();
-            canvas.save();
-            if (currCellLayout != null && currCellLayout != mLauncher.getHotseat()) {
-                // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
-                mLauncher.getDragLayer()
-                        .getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
-                canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
-            }
-
-            super.draw(canvas);
-            canvas.restore();
-        }
-
         if (!mHideSysUiScrim) {
             if (mSysUiProgress <= 0) {
                 mAnimateScrimOnNextDraw = false;
@@ -247,6 +221,11 @@
         super.onExtractedColorsChanged(wallpaperColorInfo);
     }
 
+    /**
+     * Set the width and height of the view being scrimmed
+     * @param w
+     * @param h
+     */
     public void setSize(int w, int h) {
         if (mTopScrim != null) {
             mTopScrim.setBounds(0, 0, w, h);
diff --git a/src/com/android/launcher3/graphics/WorkspaceDragScrim.java b/src/com/android/launcher3/graphics/WorkspaceDragScrim.java
new file mode 100644
index 0000000..d8dc563
--- /dev/null
+++ b/src/com/android/launcher3/graphics/WorkspaceDragScrim.java
@@ -0,0 +1,74 @@
+/*
+ * 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.graphics;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.View;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Workspace;
+
+/**
+ * Scrim drawn during SpringLoaded State (ie. Drag and Drop). Darkens the workspace except for
+ * the focused CellLayout.
+ */
+public class WorkspaceDragScrim extends Scrim {
+
+    private final Rect mHighlightRect = new Rect();
+
+    private Workspace mWorkspace;
+
+    public WorkspaceDragScrim(View view) {
+        super(view);
+        onExtractedColorsChanged(mWallpaperColorInfo);
+    }
+
+    /**
+     * Set the workspace that this scrim is acting on
+     * @param workspace
+     */
+    public void setWorkspace(Workspace workspace)  {
+        mWorkspace = workspace;
+        mWorkspace.setWorkspaceDragScrim(this);
+    }
+
+    /**
+     * Cut out the focused paged of the Workspace and then draw the scrim
+     * @param canvas
+     */
+    public void draw(Canvas canvas) {
+        // Draw the background below children.
+        if (mScrimAlpha > 0) {
+            // Update the scroll position first to ensure scrim cutout is in the right place.
+            mWorkspace.computeScrollWithoutInvalidation();
+            CellLayout currCellLayout = mWorkspace.getCurrentDragOverlappingLayout();
+            canvas.save();
+            if (currCellLayout != null && currCellLayout != mLauncher.getHotseat()) {
+                // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
+                mLauncher.getDragLayer()
+                        .getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
+                canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
+            }
+
+            super.draw(canvas);
+            canvas.restore();
+        }
+    }
+
+}
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index d1e5017..543ee2a 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -28,7 +28,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -123,7 +123,7 @@
     }
 
     public void bindUpdatedWidgets(BgDataModel dataModel) {
-        final ArrayList<WidgetListRowEntry> widgets =
+        final ArrayList<WidgetsListBaseEntry> widgets =
                 dataModel.widgetsModel.getWidgetsList(mApp.getContext());
         scheduleCallbackTask(c -> c.bindAllWidgets(widgets));
     }
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 2d860a4..1d7d1a2 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -50,7 +50,7 @@
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.ViewOnDrawExecutor;
-import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -467,7 +467,7 @@
         void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
         void bindRestoreItemsChange(HashSet<ItemInfo> updates);
         void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
-        void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets);
+        void bindAllWidgets(List<WidgetsListBaseEntry> widgets);
         void onPageBoundSynchronously(int page);
         void executeOnNextDraw(ViewOnDrawExecutor executor);
         void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 76048ba..1853632 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -31,10 +31,10 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.ShortcutUtil;
-import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -59,7 +59,7 @@
     /** Maps packages to their DotInfo's . */
     private Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>();
     /** Maps packages to their Widgets */
-    private ArrayList<WidgetListRowEntry> mAllWidgets = new ArrayList<>();
+    private List<WidgetsListBaseEntry> mAllWidgets = List.of();
 
     private PopupDataChangeListener mChangeListener = PopupDataChangeListener.INSTANCE;
 
@@ -187,7 +187,7 @@
         notificationListener.cancelNotificationFromLauncher(notificationKey);
     }
 
-    public void setAllWidgets(ArrayList<WidgetListRowEntry> allWidgets) {
+    public void setAllWidgets(List<WidgetsListBaseEntry> allWidgets) {
         mAllWidgets = allWidgets;
         mChangeListener.onWidgetsBound();
     }
@@ -196,14 +196,15 @@
         mChangeListener = listener == null ? PopupDataChangeListener.INSTANCE : listener;
     }
 
-    public ArrayList<WidgetListRowEntry> getAllWidgets() {
+    public List<WidgetsListBaseEntry> getAllWidgets() {
         return mAllWidgets;
     }
 
     public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
         return mAllWidgets.stream()
-                .filter(row -> row.pkgItem.packageName.equals(packageUserKey.mPackageName))
-                .flatMap(row -> row.widgets.stream())
+                .filter(row -> row instanceof WidgetsListContentEntry
+                        && row.mPkgItem.packageName.equals(packageUserKey.mPackageName))
+                .flatMap(row -> ((WidgetsListContentEntry) row).mWidgets.stream())
                 .filter(widget -> packageUserKey.mUser.equals(widget.user))
                 .collect(Collectors.toList());
     }
diff --git a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
new file mode 100644
index 0000000..4653774
--- /dev/null
+++ b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
@@ -0,0 +1,44 @@
+/*
+ * 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.recyclerview;
+
+import android.view.ViewGroup;
+
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+/**
+ * Creates and populates views with data
+ *
+ * @param <T> A data model which is used to populate the {@link ViewHolder}.
+ * @param <V> A subclass of {@link ViewHolder} which holds references to views.
+ */
+public interface ViewHolderBinder<T, V extends ViewHolder> {
+    /**
+     * Creates a new view, and attach it to the parent {@link ViewGroup}. Then, populates UI
+     * references in a {@link ViewHolder}.
+     */
+    V newViewHolder(ViewGroup parent);
+
+    /** Populate UI references in {@link ViewHolder} with data. */
+    void bindViewHolder(V viewHolder, T data);
+
+    /**
+     * Called when the view is recycled. Views are recycled in batches once they are sufficiently
+     * far off screen that it is unlikely the user will scroll back to them soon. Optionally
+     * override this to free expensive resources.
+     */
+    default void unbindViewHolder(V viewHolder) {}
+}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 0266345..0e8d4ae 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -46,7 +46,7 @@
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -232,7 +232,7 @@
     public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
 
     @Override
-    public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets) { }
+    public void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
 
     @Override
     public void onPageBoundSynchronously(int page) { }
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index d4a132e..4261d08 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -33,7 +33,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.widget.WidgetsFullSheet;
+import com.android.launcher3.widget.picker.WidgetsFullSheet;
 
 import java.util.concurrent.ExecutionException;
 import java.util.function.Function;
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 10cd04c..25ecea5 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -543,7 +543,8 @@
             // case the user started interacting with it before the animation finished.
             mLauncher.getStateManager().goToState(targetState, false /* animated */);
         }
-        mLauncher.getDragLayer().getScrim().createSysuiMultiplierAnim(1f).setDuration(0).start();
+        mLauncher.getDragLayer().getSysUiScrim().createSysuiMultiplierAnim(
+                1f).setDuration(0).start();
     }
 
     private void logReachedState(LauncherState targetState) {
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 80f0981..addaf9c 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -49,7 +49,7 @@
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.widget.WidgetsFullSheet;
+import com.android.launcher3.widget.picker.WidgetsFullSheet;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/src/com/android/launcher3/widget/WidgetListRowEntry.java b/src/com/android/launcher3/widget/WidgetListRowEntry.java
deleted file mode 100644
index 17e4673..0000000
--- a/src/com/android/launcher3/widget/WidgetListRowEntry.java
+++ /dev/null
@@ -1,48 +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.widget;
-
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.PackageItemInfo;
-
-import java.util.ArrayList;
-
-/**
- * Holder class to store all the information related to a single row in the widget list
- */
-public class WidgetListRowEntry {
-
-    public final PackageItemInfo pkgItem;
-
-    public final ArrayList<WidgetItem> widgets;
-
-    /**
-     * Character that is used as a section name for the {@link ItemInfo#title}.
-     * (e.g., "G" will be stored if title is "Google")
-     */
-    public String titleSectionName;
-
-    public WidgetListRowEntry(PackageItemInfo pkgItem, ArrayList<WidgetItem> items) {
-        this.pkgItem = pkgItem;
-        this.widgets = items;
-    }
-
-    @Override
-    public String toString() {
-        return pkgItem.packageName + ":" + widgets.size();
-    }
-}
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
deleted file mode 100644
index 5bf9690..0000000
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2015 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.content.Context;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnLongClickListener;
-import android.view.ViewGroup;
-
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.Adapter;
-
-import com.android.launcher3.R;
-import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.util.LabelComparator;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * List view adapter for the widget tray.
- *
- * <p>Memory vs. Performance:
- * The less number of types of views are inserted into a {@link RecyclerView}, the more recycling
- * happens and less memory is consumed. {@link #getItemViewType} was not overridden as there is
- * only a single type of view.
- */
-public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
-
-    private static final String TAG = "WidgetsListAdapter";
-    private static final boolean DEBUG = false;
-
-    private final WidgetPreviewLoader mWidgetPreviewLoader;
-    private final LayoutInflater mLayoutInflater;
-
-    private final OnClickListener mIconClickListener;
-    private final OnLongClickListener mIconLongClickListener;
-    private final int mIndent;
-    private ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>();
-    private final WidgetsDiffReporter mDiffReporter;
-
-    private boolean mApplyBitmapDeferred;
-
-    public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
-            WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
-            OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
-        mLayoutInflater = layoutInflater;
-        mWidgetPreviewLoader = widgetPreviewLoader;
-        mIconClickListener = iconClickListener;
-        mIconLongClickListener = iconLongClickListener;
-        mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent);
-        mDiffReporter = new WidgetsDiffReporter(iconCache, this);
-    }
-
-    /**
-     * Defers applying bitmap on all the {@link WidgetCell} in the {@param rv}
-     *
-     * @see WidgetCell#setApplyBitmapDeferred(boolean)
-     */
-    public void setApplyBitmapDeferred(boolean isDeferred, RecyclerView rv) {
-        mApplyBitmapDeferred = isDeferred;
-
-        for (int i = rv.getChildCount() - 1; i >= 0; i--) {
-            WidgetsRowViewHolder holder = (WidgetsRowViewHolder)
-                    rv.getChildViewHolder(rv.getChildAt(i));
-            for (int j = holder.cellContainer.getChildCount() - 1; j >= 0; j--) {
-                View v = holder.cellContainer.getChildAt(j);
-                if (v instanceof WidgetCell) {
-                    ((WidgetCell) v).setApplyBitmapDeferred(mApplyBitmapDeferred);
-                }
-            }
-        }
-    }
-
-    /**
-     * Update the widget list.
-     */
-    public void setWidgets(ArrayList<WidgetListRowEntry> tempEntries) {
-        WidgetListRowEntryComparator rowComparator = new WidgetListRowEntryComparator();
-        Collections.sort(tempEntries, rowComparator);
-        mDiffReporter.process(mEntries, tempEntries, rowComparator);
-    }
-
-    @Override
-    public int getItemCount() {
-        return mEntries.size();
-    }
-
-    public String getSectionName(int pos) {
-        return mEntries.get(pos).titleSectionName;
-    }
-
-    @Override
-    public void onBindViewHolder(WidgetsRowViewHolder holder, int pos) {
-        WidgetListRowEntry entry = mEntries.get(pos);
-        List<WidgetItem> infoList = entry.widgets;
-
-        ViewGroup row = holder.cellContainer;
-        if (DEBUG) {
-            Log.d(TAG, String.format(
-                    "onBindViewHolder [pos=%d, widget#=%d, row.getChildCount=%d]",
-                    pos, infoList.size(), row.getChildCount()));
-        }
-
-        // Add more views.
-        // if there are too many, hide them.
-        int expectedChildCount = infoList.size() + Math.max(0, infoList.size() - 1);
-        int childCount = row.getChildCount();
-
-        if (expectedChildCount > childCount) {
-            for (int i = childCount; i < expectedChildCount; i++) {
-                if ((i & 1) == 1) {
-                    // Add a divider for odd index
-                    mLayoutInflater.inflate(R.layout.widget_list_divider, row);
-                } else {
-                    // Add cell for even index
-                    WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
-                            R.layout.widget_cell, row, false);
-
-                    // set up touch.
-                    widget.setOnClickListener(mIconClickListener);
-                    widget.setOnLongClickListener(mIconLongClickListener);
-                    row.addView(widget);
-                }
-            }
-        } else if (expectedChildCount < childCount) {
-            for (int i = expectedChildCount; i < childCount; i++) {
-                row.getChildAt(i).setVisibility(View.GONE);
-            }
-        }
-
-        // Bind the views in the application info section.
-        holder.title.applyFromItemInfoWithIcon(entry.pkgItem);
-
-        // Bind the view in the widget horizontal tray region.
-        for (int i = 0; i < infoList.size(); i++) {
-            WidgetCell widget = (WidgetCell) row.getChildAt(2 * i);
-            widget.applyFromCellItem(infoList.get(i), mWidgetPreviewLoader);
-            widget.setApplyBitmapDeferred(mApplyBitmapDeferred);
-            widget.ensurePreview();
-            widget.setVisibility(View.VISIBLE);
-
-            if (i > 0) {
-                row.getChildAt(2 * i - 1).setVisibility(View.VISIBLE);
-            }
-        }
-    }
-
-    @Override
-    public WidgetsRowViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        if (DEBUG) {
-            Log.v(TAG, "\nonCreateViewHolder");
-        }
-
-        ViewGroup container = (ViewGroup) mLayoutInflater.inflate(
-                R.layout.widgets_list_row_view, parent, false);
-
-        // if the end padding is 0, then container view (horizontal scroll view) doesn't respect
-        // the end of the linear layout width + the start padding and doesn't allow scrolling.
-        container.findViewById(R.id.widgets_cell_list).setPaddingRelative(mIndent, 0, 1, 0);
-
-        return new WidgetsRowViewHolder(container);
-    }
-
-    @Override
-    public void onViewRecycled(WidgetsRowViewHolder holder) {
-        int total = holder.cellContainer.getChildCount();
-        for (int i = 0; i < total; i += 2) {
-            WidgetCell widget = (WidgetCell) holder.cellContainer.getChildAt(i);
-            widget.clear();
-        }
-    }
-
-    public boolean onFailedToRecycleView(WidgetsRowViewHolder holder) {
-        // If child views are animating, then the RecyclerView may choose not to recycle the view,
-        // causing extraneous onCreateViewHolder() calls.  It is safe in this case to continue
-        // recycling this view, and take care in onViewRecycled() to cancel any existing
-        // animations.
-        return true;
-    }
-
-    @Override
-    public long getItemId(int pos) {
-        return pos;
-    }
-
-    /**
-     * Comparator for sorting WidgetListRowEntry based on package title
-     */
-    public static class WidgetListRowEntryComparator implements Comparator<WidgetListRowEntry> {
-
-        private final LabelComparator mComparator = new LabelComparator();
-
-        @Override
-        public int compare(WidgetListRowEntry a, WidgetListRowEntry b) {
-            return mComparator.compare(a.pkgItem.title.toString(), b.pkgItem.title.toString());
-        }
-    }
-}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
new file mode 100644
index 0000000..10ea7db
--- /dev/null
+++ b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.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.model;
+
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.PackageItemInfo;
+
+/** Holder class to store the package information of an entry shown in the widgets list. */
+public abstract class WidgetsListBaseEntry {
+    public final PackageItemInfo mPkgItem;
+
+    /**
+     * Character that is used as a section name for the {@link ItemInfo#title}.
+     * (e.g., "G" will be stored if title is "Google")
+     */
+    public final String mTitleSectionName;
+
+    public WidgetsListBaseEntry(PackageItemInfo pkgItem, String titleSectionName) {
+        mPkgItem = pkgItem;
+        mTitleSectionName = titleSectionName;
+    }
+}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
new file mode 100644
index 0000000..407f194
--- /dev/null
+++ b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
@@ -0,0 +1,44 @@
+/*
+ * 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.widget.model;
+
+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
+ * shown in the {@link com.android.launcher3.widget.picker.WidgetsFullSheet}.
+ */
+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());
+    }
+
+    @Override
+    public String toString() {
+        return mPkgItem.packageName + ":" + mWidgets.size();
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java
similarity index 65%
rename from src/com/android/launcher3/widget/WidgetsDiffReporter.java
rename to src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java
index df6e2c3..398d9ba 100644
--- a/src/com/android/launcher3/widget/WidgetsDiffReporter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.widget;
+package com.android.launcher3.widget.picker;
 
 import android.util.Log;
 
@@ -22,7 +22,9 @@
 
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.widget.WidgetsListAdapter.WidgetListRowEntryComparator;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+import com.android.launcher3.widget.picker.WidgetsListAdapter.WidgetListBaseRowEntryComparator;
 
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -43,8 +45,13 @@
         mListener = listener;
     }
 
-    public void process(ArrayList<WidgetListRowEntry> currentEntries,
-            ArrayList<WidgetListRowEntry> newEntries, WidgetListRowEntryComparator comparator) {
+    /**
+     * Notifies the difference between {@code currentEntries} & {@code newEntries} by calling the
+     * relevant {@link androidx.recyclerview.widget.RecyclerView.RecyclerViewDataObserver} methods.
+     */
+    public void process(ArrayList<WidgetsListBaseEntry> currentEntries,
+            ArrayList<WidgetsListBaseEntry> newEntries,
+            WidgetListBaseRowEntryComparator comparator) {
         if (DEBUG) {
             Log.d(TAG, "process oldEntries#=" + currentEntries.size()
                     + " newEntries#=" + newEntries.size());
@@ -62,20 +69,20 @@
             }
             return;
         }
-        ArrayList<WidgetListRowEntry> orgEntries =
-                (ArrayList<WidgetListRowEntry>) currentEntries.clone();
-        Iterator<WidgetListRowEntry> orgIter = orgEntries.iterator();
-        Iterator<WidgetListRowEntry> newIter = newEntries.iterator();
+        ArrayList<WidgetsListBaseEntry> orgEntries =
+                (ArrayList<WidgetsListBaseEntry>) currentEntries.clone();
+        Iterator<WidgetsListBaseEntry> orgIter = orgEntries.iterator();
+        Iterator<WidgetsListBaseEntry> newIter = newEntries.iterator();
 
-        WidgetListRowEntry orgRowEntry = orgIter.next();
-        WidgetListRowEntry newRowEntry = newIter.next();
+        WidgetsListBaseEntry orgRowEntry = orgIter.next();
+        WidgetsListBaseEntry newRowEntry = newIter.next();
 
         do {
             int diff = comparePackageName(orgRowEntry, newRowEntry, comparator);
             if (DEBUG) {
                 Log.d(TAG, String.format("diff=%d orgRowEntry (%s) newRowEntry (%s)",
-                        diff, orgRowEntry != null? orgRowEntry.toString() : null,
-                        newRowEntry != null? newRowEntry.toString() : null));
+                        diff, orgRowEntry != null ? orgRowEntry.toString() : null,
+                        newRowEntry != null ? newRowEntry.toString() : null));
             }
             int index = -1;
             if (diff < 0) {
@@ -83,17 +90,17 @@
                 mListener.notifyItemRemoved(index);
                 if (DEBUG) {
                     Log.d(TAG, String.format("notifyItemRemoved called (%d)%s", index,
-                            orgRowEntry.titleSectionName));
+                            orgRowEntry.mTitleSectionName));
                 }
                 currentEntries.remove(index);
                 orgRowEntry = orgIter.hasNext() ? orgIter.next() : null;
             } else if (diff > 0) {
-                index = orgRowEntry != null? currentEntries.indexOf(orgRowEntry):
-                        currentEntries.size();
+                index = orgRowEntry != null ? currentEntries.indexOf(orgRowEntry)
+                        : currentEntries.size();
                 currentEntries.add(index, newRowEntry);
                 if (DEBUG) {
                     Log.d(TAG, String.format("notifyItemInserted called (%d)%s", index,
-                            newRowEntry.titleSectionName));
+                            newRowEntry.mTitleSectionName));
                 }
                 newRowEntry = newIter.hasNext() ? newIter.next() : null;
                 mListener.notifyItemInserted(index);
@@ -102,14 +109,14 @@
                 // same package name but,
                 // did the icon, title, etc, change?
                 // or did the widget size and desc, span, etc change?
-                if (!isSamePackageItemInfo(orgRowEntry.pkgItem, newRowEntry.pkgItem) ||
-                        !orgRowEntry.widgets.equals(newRowEntry.widgets)) {
+                if (!isSamePackageItemInfo(orgRowEntry.mPkgItem, newRowEntry.mPkgItem)
+                        || !areWidgetsEqual(orgRowEntry, newRowEntry)) {
                     index = currentEntries.indexOf(orgRowEntry);
                     currentEntries.set(index, newRowEntry);
                     mListener.notifyItemChanged(index);
                     if (DEBUG) {
                         Log.d(TAG, String.format("notifyItemChanged called (%d)%s", index,
-                                newRowEntry.titleSectionName));
+                                newRowEntry.mTitleSectionName));
                     }
                 }
                 orgRowEntry = orgIter.hasNext() ? orgIter.next() : null;
@@ -122,10 +129,11 @@
      * Compare package name using the same comparator as in {@link WidgetsListAdapter}.
      * Also handle null row pointers.
      */
-    private int comparePackageName(WidgetListRowEntry curRow, WidgetListRowEntry newRow,
-            WidgetListRowEntryComparator comparator) {
+    private int comparePackageName(WidgetsListBaseEntry curRow, WidgetsListBaseEntry newRow,
+            WidgetListBaseRowEntryComparator comparator) {
         if (curRow == null && newRow == null) {
-            throw new IllegalStateException("Cannot compare PackageItemInfo if both rows are null.");
+            throw new IllegalStateException(
+                    "Cannot compare PackageItemInfo if both rows are null.");
         }
 
         if (curRow == null && newRow != null) {
@@ -136,6 +144,17 @@
         return comparator.compare(curRow, newRow);
     }
 
+    private boolean areWidgetsEqual(WidgetsListBaseEntry curRow,
+            WidgetsListBaseEntry newRow) {
+        if (!(curRow instanceof WidgetsListContentEntry)
+                || !(newRow instanceof WidgetsListContentEntry)) {
+            return false;
+        }
+        WidgetsListContentEntry orgRowEntry = (WidgetsListContentEntry) curRow;
+        WidgetsListContentEntry newRowEntry = (WidgetsListContentEntry) newRow;
+        return orgRowEntry.mWidgets.equals(newRowEntry.mWidgets);
+    }
+
     private boolean isSamePackageItemInfo(PackageItemInfo curInfo, PackageItemInfo newInfo) {
         return curInfo.bitmap.icon.equals(newInfo.bitmap.icon)
                 && !mIconCache.isDefaultIcon(curInfo.bitmap, curInfo.user);
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
similarity index 95%
rename from src/com/android/launcher3/widget/WidgetsFullSheet.java
rename to src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 4c8c339..03623d5 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.widget;
+package com.android.launcher3.widget.picker;
 
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
@@ -42,6 +42,7 @@
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.TopRoundedCornerView;
+import com.android.launcher3.widget.BaseWidgetSheet;
 
 /**
  * Popup for showing the full list of available widgets
@@ -218,8 +219,8 @@
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mNoIntercept = false;
             RecyclerViewFastScroller scroller = mRecyclerView.getScrollbar();
-            if (scroller.getThumbOffsetY() >= 0 &&
-                    getPopupContainer().isEventOverView(scroller, ev)) {
+            if (scroller.getThumbOffsetY() >= 0
+                    && getPopupContainer().isEventOverView(scroller, ev)) {
                 mNoIntercept = true;
             } else if (getPopupContainer().isEventOverView(mContent, ev)) {
                 mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, getPopupContainer());
@@ -228,6 +229,7 @@
         return super.onControllerInterceptTouchEvent(ev);
     }
 
+    /** Shows the {@link WidgetsFullSheet} on the launcher. */
     public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
         WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
                 .inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
@@ -237,6 +239,7 @@
         return sheet;
     }
 
+    /** Gets the {@link WidgetsRecyclerView} which shows all widgets in {@link WidgetsFullSheet}. */
     @VisibleForTesting
     public static WidgetsRecyclerView getWidgetsView(Launcher launcher) {
         return launcher.findViewById(R.id.widgets_list_view);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
new file mode 100644
index 0000000..9d30842
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2015 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.content.Context;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+import com.android.launcher3.R;
+import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.recyclerview.ViewHolderBinder;
+import com.android.launcher3.util.LabelComparator;
+import com.android.launcher3.widget.WidgetCell;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * List view adapter for the widget tray.
+ *
+ * <p>Memory vs. Performance:
+ * The less number of types of views are inserted into a {@link RecyclerView}, the more recycling
+ * happens and less memory is consumed.
+ */
+public class WidgetsListAdapter extends Adapter<ViewHolder> {
+
+    private static final String TAG = "WidgetsListAdapter";
+    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 final WidgetsDiffReporter mDiffReporter;
+    private final SparseArray<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
+    private final WidgetsListRowViewHolderBinder mWidgetsListRowViewHolderBinder;
+
+    private ArrayList<WidgetsListBaseEntry> mEntries = new ArrayList<>();
+
+    public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
+            WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
+            OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
+        mDiffReporter = new WidgetsDiffReporter(iconCache, this);
+        mWidgetsListRowViewHolderBinder = new WidgetsListRowViewHolderBinder(context,
+                layoutInflater, iconClickListener, iconLongClickListener, widgetPreviewLoader);
+        mViewHolderBinders.put(VIEW_TYPE_WIDGETS_LIST, mWidgetsListRowViewHolderBinder);
+    }
+
+    /**
+     * Defers applying bitmap on all the {@link WidgetCell} in the {@param rv}.
+     *
+     * @see WidgetCell#setApplyBitmapDeferred(boolean)
+     */
+    public void setApplyBitmapDeferred(boolean isDeferred, RecyclerView rv) {
+        mWidgetsListRowViewHolderBinder.setApplyBitmapDeferred(isDeferred);
+
+        for (int i = rv.getChildCount() - 1; i >= 0; i--) {
+            ViewHolder viewHolder = rv.getChildViewHolder(rv.getChildAt(i));
+            if (viewHolder.getItemViewType() == VIEW_TYPE_WIDGETS_LIST) {
+                WidgetsRowViewHolder holder = (WidgetsRowViewHolder) viewHolder;
+                for (int j = holder.cellContainer.getChildCount() - 1; j >= 0; j--) {
+                    View v = holder.cellContainer.getChildAt(j);
+                    if (v instanceof WidgetCell) {
+                        ((WidgetCell) v).setApplyBitmapDeferred(isDeferred);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public int getItemCount() {
+        return mEntries.size();
+    }
+
+    /** Gets the section name for {@link com.android.launcher3.views.RecyclerViewFastScroller}. */
+    public String getSectionName(int pos) {
+        return mEntries.get(pos).mTitleSectionName;
+    }
+
+    /** Updates the widget list. */
+    public void setWidgets(List<WidgetsListBaseEntry> tempEntries) {
+        ArrayList<WidgetsListBaseEntry> newEntries = new ArrayList<>(tempEntries);
+        WidgetListBaseRowEntryComparator rowComparator = new WidgetListBaseRowEntryComparator();
+        Collections.sort(newEntries, rowComparator);
+        mDiffReporter.process(mEntries, newEntries, rowComparator);
+    }
+
+    @Override
+    public void onBindViewHolder(ViewHolder holder, int pos) {
+        ViewHolderBinder viewHolderBinder = mViewHolderBinders.get(getItemViewType(pos));
+        viewHolderBinder.bindViewHolder(holder, mEntries.get(pos));
+    }
+
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        if (DEBUG) {
+            Log.v(TAG, "\nonCreateViewHolder");
+        }
+
+        return mViewHolderBinders.get(viewType).newViewHolder(parent);
+    }
+
+    @Override
+    public void onViewRecycled(ViewHolder holder) {
+        mViewHolderBinders.get(holder.getItemViewType()).unbindViewHolder(holder);
+    }
+
+    @Override
+    public boolean onFailedToRecycleView(ViewHolder holder) {
+        // If child views are animating, then the RecyclerView may choose not to recycle the view,
+        // causing extraneous onCreateViewHolder() calls.  It is safe in this case to continue
+        // recycling this view, and take care in onViewRecycled() to cancel any existing
+        // animations.
+        return true;
+    }
+
+    @Override
+    public long getItemId(int pos) {
+        return pos;
+    }
+
+    @Override
+    public int getItemViewType(int pos) {
+        WidgetsListBaseEntry entry = mEntries.get(pos);
+        if (entry instanceof WidgetsListContentEntry) {
+            return VIEW_TYPE_WIDGETS_LIST;
+        }
+        throw new UnsupportedOperationException("ViewHolderBinder not found for " + entry);
+    }
+
+    /** Comparator for sorting WidgetListRowEntry based on package title. */
+    public static class WidgetListBaseRowEntryComparator implements
+            Comparator<WidgetsListBaseEntry> {
+
+        private final LabelComparator mComparator = new LabelComparator();
+
+        @Override
+        public int compare(WidgetsListBaseEntry a, WidgetsListBaseEntry b) {
+            return mComparator.compare(a.mPkgItem.title.toString(), b.mPkgItem.title.toString());
+        }
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinder.java
new file mode 100644
index 0000000..22a8d00
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinder.java
@@ -0,0 +1,150 @@
+/*
+ * 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.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+
+import com.android.launcher3.R;
+import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.recyclerview.ViewHolderBinder;
+import com.android.launcher3.widget.WidgetCell;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+
+import java.util.List;
+
+/**
+ * Binds data from {@link WidgetsListContentEntry} to UI elements in {@link WidgetsRowViewHolder}.
+ */
+public class WidgetsListRowViewHolderBinder
+        implements ViewHolderBinder<WidgetsListContentEntry, WidgetsRowViewHolder> {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "WidgetsListRowViewHolderBinder";
+
+    private final LayoutInflater mLayoutInflater;
+    private final int mIndent;
+    private final OnClickListener mIconClickListener;
+    private final OnLongClickListener mIconLongClickListener;
+    private final WidgetPreviewLoader mWidgetPreviewLoader;
+    private boolean mApplyBitmapDeferred = false;
+
+    public WidgetsListRowViewHolderBinder(
+            Context context,
+            LayoutInflater layoutInflater,
+            OnClickListener iconClickListener,
+            OnLongClickListener iconLongClickListener,
+            WidgetPreviewLoader widgetPreviewLoader) {
+        mLayoutInflater = layoutInflater;
+        mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent);
+        mIconClickListener = iconClickListener;
+        mIconLongClickListener = iconLongClickListener;
+        mWidgetPreviewLoader = widgetPreviewLoader;
+    }
+
+    /**
+     * Defers applying bitmap on all the {@link WidgetCell} at
+     * {@link #bindViewHolder(WidgetsRowViewHolder, WidgetsListContentEntry)} if
+     * {@code applyBitmapDeferred} is {@code true}.
+     */
+    public void setApplyBitmapDeferred(boolean applyBitmapDeferred) {
+        mApplyBitmapDeferred = applyBitmapDeferred;
+    }
+
+    @Override
+    public WidgetsRowViewHolder newViewHolder(ViewGroup parent) {
+        if (DEBUG) {
+            Log.v(TAG, "\nonCreateViewHolder");
+        }
+
+        ViewGroup container = (ViewGroup) mLayoutInflater.inflate(
+                R.layout.widgets_list_row_view, parent, false);
+
+        // if the end padding is 0, then container view (horizontal scroll view) doesn't respect
+        // the end of the linear layout width + the start padding and doesn't allow scrolling.
+        container.findViewById(R.id.widgets_cell_list).setPaddingRelative(mIndent, 0, 1, 0);
+
+        return new WidgetsRowViewHolder(container);
+    }
+
+    @Override
+    public void bindViewHolder(WidgetsRowViewHolder holder, WidgetsListContentEntry entry) {
+        List<WidgetItem> infoList = entry.mWidgets;
+
+        ViewGroup row = holder.cellContainer;
+        if (DEBUG) {
+            Log.d(TAG, String.format("onBindViewHolder [widget#=%d, row.getChildCount=%d]",
+                    infoList.size(), row.getChildCount()));
+        }
+
+        // Add more views.
+        // if there are too many, hide them.
+        int expectedChildCount = infoList.size() + Math.max(0, infoList.size() - 1);
+        int childCount = row.getChildCount();
+
+        if (expectedChildCount > childCount) {
+            for (int i = childCount; i < expectedChildCount; i++) {
+                if ((i & 1) == 1) {
+                    // Add a divider for odd index
+                    mLayoutInflater.inflate(R.layout.widget_list_divider, row);
+                } else {
+                    // Add cell for even index
+                    WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
+                            R.layout.widget_cell, row, false);
+
+                    // set up touch.
+                    widget.setOnClickListener(mIconClickListener);
+                    widget.setOnLongClickListener(mIconLongClickListener);
+                    row.addView(widget);
+                }
+            }
+        } else if (expectedChildCount < childCount) {
+            for (int i = expectedChildCount; i < childCount; i++) {
+                row.getChildAt(i).setVisibility(View.GONE);
+            }
+        }
+
+        // Bind the views in the application info section.
+        holder.title.applyFromItemInfoWithIcon(entry.mPkgItem);
+
+        // Bind the view in the widget horizontal tray region.
+        for (int i = 0; i < infoList.size(); i++) {
+            WidgetCell widget = (WidgetCell) row.getChildAt(2 * i);
+            widget.applyFromCellItem(infoList.get(i), mWidgetPreviewLoader);
+            widget.setApplyBitmapDeferred(mApplyBitmapDeferred);
+            widget.ensurePreview();
+            widget.setVisibility(View.VISIBLE);
+
+            if (i > 0) {
+                row.getChildAt(2 * i - 1).setVisibility(View.VISIBLE);
+            }
+        }
+    }
+
+    @Override
+    public void unbindViewHolder(WidgetsRowViewHolder holder) {
+        int total = holder.cellContainer.getChildCount();
+        for (int i = 0; i < total; i += 2) {
+            WidgetCell widget = (WidgetCell) holder.cellContainer.getChildAt(i);
+            widget.clear();
+        }
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
similarity index 97%
rename from src/com/android/launcher3/widget/WidgetsRecyclerView.java
rename to src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index 69de12b..52e9496 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.widget;
+package com.android.launcher3.widget.picker;
 
 import android.content.Context;
 import android.graphics.Point;
@@ -22,13 +22,13 @@
 import android.view.MotionEvent;
 import android.view.View;
 
-import com.android.launcher3.BaseRecyclerView;
-import com.android.launcher3.R;
-
 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.R;
+
 /**
  * The widgets recycler view.
  */
@@ -89,7 +89,7 @@
         LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
         layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
 
-        int posInt = (int) ((touchFraction == 1)? pos -1 : pos);
+        int posInt = (int) ((touchFraction == 1) ? pos - 1 : pos);
         return mAdapter.getSectionName(posInt);
     }
 
@@ -171,4 +171,4 @@
     @Override
     public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/launcher3/widget/WidgetsRowViewHolder.java b/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
similarity index 86%
rename from src/com/android/launcher3/widget/WidgetsRowViewHolder.java
rename to src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
index d26edb6..9be079e 100644
--- a/src/com/android/launcher3/widget/WidgetsRowViewHolder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
@@ -13,16 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.widget;
+package com.android.launcher3.widget.picker;
 
 import android.view.ViewGroup;
 
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.R;
 
-import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-
-public class WidgetsRowViewHolder extends ViewHolder {
+/** A {@link ViewHolder} for a row in the full widget picker. */
+public final class WidgetsRowViewHolder extends ViewHolder {
 
     public final ViewGroup cellContainer;
     public final BubbleTextView title;
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
index 269af7b..73b1601 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
@@ -21,10 +21,10 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 
-import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}.
@@ -47,7 +47,7 @@
 
     @Override
     public void bindWidgets() {
-        final ArrayList<WidgetListRowEntry> widgets =
+        final List<WidgetsListBaseEntry> widgets =
                 mBgDataModel.widgetsModel.getWidgetsList(mApp.getContext());
         executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor);
     }
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index b4e45f8..f27922b 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -28,12 +28,12 @@
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.widget.WidgetItemComparator;
-import com.android.launcher3.widget.WidgetListRowEntry;
 import com.android.launcher3.widget.WidgetManagerHelper;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+import com.android.launcher3.widget.picker.WidgetsDiffReporter;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -60,24 +60,23 @@
     private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsList = new HashMap<>();
 
     /**
-     * Returns a list of {@link WidgetListRowEntry}. All {@link WidgetItem} in a single row
-     * are sorted (based on label and user), but the overall list of {@link WidgetListRowEntry}s
-     * is not sorted. This list is sorted at the UI when using
-     * {@link com.android.launcher3.widget.WidgetsDiffReporter}
+     * Returns a list of {@link WidgetsListBaseEntry}. All {@link WidgetItem} in a single row
+     * are sorted (based on label and user), but the overall list of
+     * {@link WidgetsListBaseEntry}s is not sorted. This list is sorted at the UI when using
+     * {@link WidgetsDiffReporter}
      *
-     * @see com.android.launcher3.widget.WidgetsListAdapter#setWidgets(ArrayList)
+     * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
      */
-    public synchronized ArrayList<WidgetListRowEntry> getWidgetsList(Context context) {
-        ArrayList<WidgetListRowEntry> result = new ArrayList<>();
+    public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsList(Context context) {
+        ArrayList<WidgetsListBaseEntry> result = new ArrayList<>();
         AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
 
-        WidgetItemComparator widgetComparator = new WidgetItemComparator();
         for (Map.Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
-            WidgetListRowEntry row = new WidgetListRowEntry(
-                    entry.getKey(), new ArrayList<>(entry.getValue()));
-            row.titleSectionName = (row.pkgItem.title == null) ? "" :
-                    indexer.computeSectionName(row.pkgItem.title);
-            Collections.sort(row.widgets, widgetComparator);
+            PackageItemInfo pkgItem = entry.getKey();
+            String sectionName = (pkgItem.title == null) ? "" :
+                    indexer.computeSectionName(pkgItem.title);
+            WidgetsListContentEntry row =
+                    new WidgetsListContentEntry(pkgItem, sectionName, entry.getValue());
             result.add(row);
         }
         return result;
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 1648bdd..4c9a8e7 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -37,8 +37,8 @@
 import com.android.launcher3.tapl.Widgets;
 import com.android.launcher3.tapl.Workspace;
 import com.android.launcher3.views.OptionsPopupView;
-import com.android.launcher3.widget.WidgetsFullSheet;
-import com.android.launcher3.widget.WidgetsRecyclerView;
+import com.android.launcher3.widget.picker.WidgetsFullSheet;
+import com.android.launcher3.widget.picker.WidgetsRecyclerView;
 
 import org.junit.Before;
 import org.junit.Ignore;