Merge "Fix squished hotseat due to bubbles" into main
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 0b18633..e925af6 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -28,7 +28,6 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ClipData;
 import android.content.ClipDescription;
-import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
@@ -48,11 +47,11 @@
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.widget.WidgetCell;
 import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.picker.WidgetsFullSheet;
+import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -110,8 +109,8 @@
     private WidgetsModel mModel;
     private LauncherAppState mApp;
     private WidgetPredictionsRequester mWidgetPredictionsRequester;
-    private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {
-    });
+    private final WidgetPickerDataProvider mWidgetPickerDataProvider =
+            new WidgetPickerDataProvider();
 
     private int mDesiredWidgetWidth;
     private int mDesiredWidgetHeight;
@@ -215,8 +214,8 @@
 
     @NonNull
     @Override
-    public PopupDataProvider getPopupDataProvider() {
-        return mPopupDataProvider;
+    public WidgetPickerDataProvider getWidgetPickerDataProvider() {
+        return mWidgetPickerDataProvider;
     }
 
     @Override
@@ -293,8 +292,6 @@
     private void refreshAndBindWidgets() {
         MODEL_EXECUTOR.execute(() -> {
             LauncherAppState app = LauncherAppState.getInstance(this);
-            Context context = app.getContext();
-
             mModel.update(app, null);
             bindWidgets(mModel.getWidgetsByPackageItem());
             // Open sheet once widgets are available, so that it doesn't interrupt the open
@@ -317,7 +314,8 @@
                 shouldShowDefaultWidgets() ? builder.build(widgets,
                         mDefaultWidgetsFilter) : List.of();
 
-        MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(allWidgets, defaultWidgets));
+        MAIN_EXECUTOR.execute(
+                () -> mWidgetPickerDataProvider.setWidgets(allWidgets, defaultWidgets));
     }
 
     private void openWidgetsSheet() {
@@ -332,7 +330,7 @@
     private void bindRecommendedWidgets(List<ItemInfo> recommendedWidgets) {
         // Bind recommendations once picker has finished open animation.
         MAIN_EXECUTOR.getHandler().postDelayed(
-                () -> mPopupDataProvider.setRecommendedWidgets(recommendedWidgets),
+                () -> mWidgetPickerDataProvider.setWidgetRecommendations(recommendedWidgets),
                 mDeviceProfile.bottomSheetOpenDuration);
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 5176d74..bbc6b42 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -515,7 +515,7 @@
         } else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
             mHotseatPredictionController.setPredictedItems(item);
         } else if (item.containerId == Favorites.CONTAINER_WIDGETS_PREDICTION) {
-            getPopupDataProvider().setRecommendedWidgets(item.items);
+            getWidgetPickerDataProvider().setWidgetRecommendations(item.items);
         }
     }
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5949732..6c706be 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -266,6 +266,7 @@
 import com.android.launcher3.widget.custom.CustomWidgetManager;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.picker.WidgetsFullSheet;
+import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
 import com.android.launcher3.widget.util.WidgetSizes;
 import com.android.systemui.plugins.LauncherOverlayPlugin;
 import com.android.systemui.plugins.PluginListener;
@@ -371,6 +372,7 @@
     private LauncherAccessibilityDelegate mAccessibilityDelegate;
 
     private PopupDataProvider mPopupDataProvider;
+    private WidgetPickerDataProvider mWidgetPickerDataProvider;
 
     // We only want to get the SharedPreferences once since it does an FS stat each time we get
     // it from the context.
@@ -532,6 +534,7 @@
                 mFocusHandler, new CellLayout(mWorkspace.getContext(), mWorkspace));
 
         mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
+        mWidgetPickerDataProvider = new WidgetPickerDataProvider();
 
         boolean internalStateHandled = ACTIVITY_TRACKER.handleCreate(this);
         if (internalStateHandled) {
@@ -2702,6 +2705,7 @@
         mDragLayer.dump(prefix, writer);
         mStateManager.dump(prefix, writer);
         mPopupDataProvider.dump(prefix, writer);
+        mWidgetPickerDataProvider.dump(prefix, writer);
         mDeviceProfile.dump(this, prefix, writer);
         mAppsView.getAppsStore().dump(prefix, writer);
 
@@ -3011,6 +3015,12 @@
         return mPopupDataProvider;
     }
 
+    @NonNull
+    @Override
+    public WidgetPickerDataProvider getWidgetPickerDataProvider() {
+        return mWidgetPickerDataProvider;
+    }
+
     @Override
     public DotInfo getDotInfoForItem(ItemInfo info) {
         return mPopupDataProvider.getDotInfoForItem(info);
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index b41da0f..08ccfb2 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -115,9 +115,7 @@
         if (BuildCompat.isAtLeastV() && Flags.enableSupportForArchiving()) {
             ArchiveCompatibilityParams params = new ArchiveCompatibilityParams();
             params.setEnableUnarchivalConfirmation(false);
-            if (Flags.enableNewArchivingIcon()) {
-                params.setEnableIconOverlay(false);
-            }
+            params.setEnableIconOverlay(!Flags.enableNewArchivingIcon());
             launcherApps.setArchiveCompatibility(params);
         }
 
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index 83c34ce..d57f8a0 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -254,8 +254,8 @@
         PopupContainerWithArrow.dismissInvalidPopup(launcher)
     }
 
-    override fun bindAllWidgets(allWidgets: List<WidgetsListBaseEntry?>?) {
-        launcher.popupDataProvider.allWidgets = allWidgets
+    override fun bindAllWidgets(allWidgets: List<WidgetsListBaseEntry>) {
+        launcher.widgetPickerDataProvider.setWidgets(allWidgets, /* defaultWidgets= */ listOf())
     }
 
     /** Returns the ids of the workspaces to bind. */
@@ -304,7 +304,8 @@
         }
 
         val widgetsListBaseEntry: WidgetsListBaseEntry =
-            launcher.popupDataProvider.allWidgets.firstOrNull { item: WidgetsListBaseEntry ->
+            launcher.widgetPickerDataProvider.get().allWidgets.firstOrNull {
+                item: WidgetsListBaseEntry ->
                 item.mPkgItem.packageName == BuildConfig.APPLICATION_ID
             } ?: return
 
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 56a7fef..ad660d2 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -71,6 +71,7 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.Flags;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.R;
@@ -764,6 +765,16 @@
         }
     }
 
+    /**
+     * Force header height update with an offset. Used by {@link UniversalSearchInputView} to
+     * request {@link FloatingHeaderView} to update its maxTranslation for multiline search bar.
+     */
+    public void forceUpdateHeaderHeight(int offset) {
+        if (Flags.multilineSearchBar()) {
+            mHeader.updateSearchBarOffset(offset);
+        }
+    }
+
     protected void updateHeaderScroll(int scrolledOffset) {
         float prog1 = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f);
         int headerColor = getHeaderColor(prog1);
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 92c589c..a2bd5dd 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -30,6 +30,7 @@
 import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder;
@@ -104,6 +105,8 @@
     private boolean mFloatingRowsCollapsed;
     // Total height of all current floating rows. Collapsed rows == 0 height.
     private int mFloatingRowsHeight;
+    // Offset of search bar. Adds to the floating view height when multi-line is supported.
+    private int mSearchBarOffset = 0;
 
     // This is initialized once during inflation and stays constant after that. Fixed views
     // cannot be added or removed dynamically.
@@ -198,6 +201,14 @@
         }
     }
 
+    /**
+     * Offset floating rows height by search bar
+     */
+    void updateSearchBarOffset(int offset) {
+        mSearchBarOffset = offset;
+        onHeightUpdated();
+    }
+
     @Override
     public void onPluginDisconnected(AllAppsRow plugin) {
         PluginHeaderRow row = mPluginRows.get(plugin);
@@ -258,9 +269,18 @@
         mTabLayout.setVisibility(mTabsHidden ? GONE : visibility);
     }
 
+    /** Returns whether search bar has multi-line support, and is currently in multi-line state. */
+    private boolean isSearchBarMultiline() {
+        return Flags.multilineSearchBar() && mSearchBarOffset > 0;
+    }
+
     private void updateExpectedHeight() {
         updateFloatingRowsHeight();
         mMaxTranslation = 0;
+        boolean shouldAddSearchBarHeight = isSearchBarMultiline() && !Flags.floatingSearchBar();
+        if (shouldAddSearchBarHeight) {
+            mMaxTranslation += mSearchBarOffset;
+        }
         if (mFloatingRowsCollapsed) {
             return;
         }
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 7e139c3..8a5e388 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -24,28 +24,20 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.dot.DotInfo;
-import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.notification.NotificationKeyData;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.ShortcutUtil;
-import com.android.launcher3.widget.PendingAddWidgetInfo;
-import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-import com.android.launcher3.widget.model.WidgetsListContentEntry;
-import com.android.launcher3.widget.picker.WidgetRecommendationCategory;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.function.Consumer;
-import java.util.function.Function;
 import java.util.function.Predicate;
-import java.util.stream.Collectors;
 
 /**
  * Provides data for the popup menu that appears after long-clicking on apps.
@@ -62,18 +54,6 @@
     /** Maps packages to their DotInfo's . */
     private Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>();
 
-    /** All installed widgets. */
-    private List<WidgetsListBaseEntry> mAllWidgets = List.of();
-    /**
-     * Selectively chosen installed widgets which may be preferred for default display over the list
-     * of all widgets.
-     */
-    private List<WidgetsListBaseEntry> mDefaultWidgets = List.of();
-    /** Widgets that can be recommended to the users. */
-    private List<ItemInfo> mRecommendedWidgets = List.of();
-
-    private PopupDataChangeListener mChangeListener = PopupDataChangeListener.INSTANCE;
-
     public PopupDataProvider(Consumer<Predicate<PackageUserKey>> notificationDotsChangeListener) {
         mNotificationDotsChangeListener = notificationDotsChangeListener;
     }
@@ -188,124 +168,8 @@
         })) ? dotInfo : null;
     }
 
-    /**
-     * Sets a list of recommended widgets ordered by their order of appearance in the widgets
-     * recommendation UI.
-     */
-    public void setRecommendedWidgets(List<ItemInfo> recommendedWidgets) {
-        mRecommendedWidgets = recommendedWidgets;
-        mChangeListener.onRecommendedWidgetsBound();
-    }
-
-    public void setAllWidgets(List<WidgetsListBaseEntry> allWidgets) {
-        mAllWidgets = allWidgets;
-        mDefaultWidgets = List.of();
-        mChangeListener.onWidgetsBound();
-    }
-
-    /**
-     * Sets the list of widgets to be displayed by default and a complete list that can be displayed
-     * when user chooses to show all widgets.
-     */
-    public void setAllWidgets(List<WidgetsListBaseEntry> allWidgets,
-            List<WidgetsListBaseEntry> defaultWidgets) {
-        mAllWidgets = allWidgets;
-        mDefaultWidgets = defaultWidgets;
-        mChangeListener.onWidgetsBound();
-    }
-
-    public void setChangeListener(PopupDataChangeListener listener) {
-        mChangeListener = listener == null ? PopupDataChangeListener.INSTANCE : listener;
-    }
-
-    public List<WidgetsListBaseEntry> getAllWidgets() {
-        return mAllWidgets;
-    }
-
-    /**
-     * Returns a "selectively" chosen list of widgets that may be preferred to be shown by default
-     * instead of a complete list.
-     */
-    public List<WidgetsListBaseEntry> getDefaultWidgets() {
-        return mDefaultWidgets;
-    }
-
-    /** Returns a list of recommended widgets. */
-    public List<WidgetItem> getRecommendedWidgets() {
-        HashMap<ComponentKey, WidgetItem> allWidgetItems = new HashMap<>();
-        mAllWidgets.stream()
-                .filter(entry -> entry instanceof WidgetsListContentEntry)
-                .forEach(entry -> ((WidgetsListContentEntry) entry).mWidgets
-                        .forEach(widget -> allWidgetItems.put(
-                                new ComponentKey(widget.componentName, widget.user), widget)));
-        return mRecommendedWidgets.stream()
-                .map(recommendedWidget -> allWidgetItems.get(
-                        new ComponentKey(recommendedWidget.getTargetComponent(),
-                                recommendedWidget.user)))
-                .filter(Objects::nonNull)
-                .collect(Collectors.toList());
-    }
-
-    /** Returns the recommended widgets mapped by their category. */
-    @NonNull
-    public Map<WidgetRecommendationCategory, List<WidgetItem>> getCategorizedRecommendedWidgets() {
-        Map<ComponentKey, WidgetItem> allWidgetItems = mAllWidgets.stream()
-                .filter(entry -> entry instanceof WidgetsListContentEntry)
-                .flatMap(entry -> entry.mWidgets.stream())
-                .distinct()
-                .collect(Collectors.toMap(
-                        widget -> new ComponentKey(widget.componentName, widget.user),
-                        Function.identity()
-                ));
-        return mRecommendedWidgets.stream()
-                .filter(itemInfo -> itemInfo instanceof PendingAddWidgetInfo
-                        && ((PendingAddWidgetInfo) itemInfo).recommendationCategory != null)
-                .collect(Collectors.groupingBy(
-                        it -> ((PendingAddWidgetInfo) it).recommendationCategory,
-                        Collectors.collectingAndThen(
-                                Collectors.toList(),
-                                list -> list.stream()
-                                        .map(it -> allWidgetItems.get(
-                                                new ComponentKey(it.getTargetComponent(),
-                                                        it.user)))
-                                        .filter(Objects::nonNull)
-                                        .collect(Collectors.toList())
-                        )
-                ));
-    }
-
-    public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
-        return mAllWidgets.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());
-    }
-
-    /** Gets the WidgetsListContentEntry for the currently selected header. */
-    public WidgetsListContentEntry getSelectedAppWidgets(PackageUserKey packageUserKey,
-            boolean useDefault) {
-        List<WidgetsListBaseEntry> widgets = useDefault ? mDefaultWidgets : mAllWidgets;
-        return (WidgetsListContentEntry) widgets.stream()
-                .filter(row -> row instanceof WidgetsListContentEntry
-                        && PackageUserKey.fromPackageItemInfo(row.mPkgItem).equals(packageUserKey))
-                .findAny()
-                .orElse(null);
-    }
-
     public void dump(String prefix, PrintWriter writer) {
         writer.println(prefix + "PopupDataProvider:");
         writer.println(prefix + "\tmPackageUserToDotInfos:" + mPackageUserToDotInfos);
     }
-
-    public interface PopupDataChangeListener {
-
-        PopupDataChangeListener INSTANCE = new PopupDataChangeListener() { };
-
-        default void onWidgetsBound() { }
-
-        /** A callback to get notified when recommended widgets are bound. */
-        default void onRecommendedWidgetsBound() { }
-    }
 }
diff --git a/src/com/android/launcher3/popup/PopupLiveUpdateHandler.java b/src/com/android/launcher3/popup/PopupLiveUpdateHandler.java
index 4c94f94..1fd3557 100644
--- a/src/com/android/launcher3/popup/PopupLiveUpdateHandler.java
+++ b/src/com/android/launcher3/popup/PopupLiveUpdateHandler.java
@@ -19,6 +19,8 @@
 import android.view.View;
 
 import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
+import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider.WidgetPickerDataChangeListener;
 
 /**
  * Utility class to handle updates while the popup is visible (like widgets and
@@ -27,7 +29,7 @@
  * @param <T> The activity on which the popup shows
  */
 public abstract class PopupLiveUpdateHandler<T extends Context & ActivityContext> implements
-        PopupDataProvider.PopupDataChangeListener, View.OnAttachStateChangeListener {
+        WidgetPickerDataChangeListener, View.OnAttachStateChangeListener {
 
     protected final T mContext;
     protected final PopupContainerWithArrow<T> mPopupContainerWithArrow;
@@ -40,19 +42,25 @@
 
     @Override
     public void onViewAttachedToWindow(View view) {
-        PopupDataProvider popupDataProvider = mContext.getPopupDataProvider();
+        WidgetPickerDataProvider widgetsDataProvider = mContext.getWidgetPickerDataProvider();
 
-        if (popupDataProvider != null) {
-            popupDataProvider.setChangeListener(this);
+        if (widgetsDataProvider != null) {
+            widgetsDataProvider.setChangeListener(this);
         }
     }
 
     @Override
     public void onViewDetachedFromWindow(View view) {
-        PopupDataProvider popupDataProvider = mContext.getPopupDataProvider();
+        WidgetPickerDataProvider widgetsDataProvider = mContext.getWidgetPickerDataProvider();
 
-        if (popupDataProvider != null) {
-            popupDataProvider.setChangeListener(null);
+        if (widgetsDataProvider != null) {
+            widgetsDataProvider.setChangeListener(null);
         }
     }
+
+    @Override
+    public void onWidgetsBound() {} // NO_OP
+
+    @Override
+    public void onRecommendedWidgetsBound() {} // NO_OP
 }
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 83e9810..f7e1168 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -5,6 +5,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_DONT_SUGGEST_APP_TAP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP;
+import static com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.findAllWidgetsForPackageUser;
 
 import android.app.ActivityOptions;
 import android.content.ComponentName;
@@ -28,7 +29,6 @@
 import com.android.launcher3.SecondaryDropTarget;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.PrivateProfileManager;
-import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.UserCache;
@@ -39,9 +39,9 @@
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.widget.WidgetsBottomSheet;
+import com.android.launcher3.widget.picker.model.data.WidgetPickerData;
 
 import java.util.Arrays;
-import java.util.List;
 
 /**
  * Represents a system shortcut for a given app. The shortcut should have a label and icon, and an
@@ -107,11 +107,12 @@
     }
 
     public static final Factory<ActivityContext> WIDGETS = (context, itemInfo, originalView) -> {
-        if (itemInfo.getTargetComponent() == null) return null;
-        final List<WidgetItem> widgets =
-                context.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
-                        itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
-        if (widgets.isEmpty()) {
+        final PackageUserKey packageUserKey = PackageUserKey.fromItemInfo(itemInfo);
+        if (packageUserKey == null) return null;
+
+        final WidgetPickerData data = context.getWidgetPickerDataProvider().get();
+        if (findAllWidgetsForPackageUser(data, packageUserKey).isEmpty()) {
+            // hides widget picker shortcut if there are no widgets for the package.
             return null;
         }
         return new Widgets(context, itemInfo, originalView);
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 0299a23..9b3292d 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -59,6 +59,7 @@
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer;
+import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -76,6 +77,7 @@
     private View mAppsButton;
 
     private PopupDataProvider mPopupDataProvider;
+    private WidgetPickerDataProvider mWidgetPickerDataProvider;
 
     private boolean mAppDrawerShown = false;
 
@@ -315,6 +317,11 @@
     }
 
     @Override
+    public WidgetPickerDataProvider getWidgetPickerDataProvider() {
+        return mWidgetPickerDataProvider;
+    }
+
+    @Override
     public OnClickListener getItemOnClickListener() {
         return this::onIconClicked;
     }
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index cfac91a..d3160e0 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -81,6 +81,7 @@
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.ViewCache;
+import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
 
 import java.util.List;
 
@@ -266,6 +267,14 @@
         return null;
     }
 
+    /**
+     * Returns the {@link WidgetPickerDataProvider} that can be used to read widgets for display.
+     */
+    @Nullable
+    default WidgetPickerDataProvider getWidgetPickerDataProvider() {
+        return null;
+    }
+
     @Nullable
     default StringCache getStringCache() {
         return null;
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index c59e295..1c0d94c 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -45,13 +45,13 @@
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.launcher3.views.AbstractSlideInView;
+import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider.WidgetPickerDataChangeListener;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -60,7 +60,7 @@
  */
 public abstract class BaseWidgetSheet extends AbstractSlideInView<BaseActivity>
         implements OnClickListener, OnLongClickListener,
-        PopupDataProvider.PopupDataChangeListener, Insettable, OnDeviceProfileChangeListener {
+        WidgetPickerDataChangeListener, Insettable, OnDeviceProfileChangeListener {
     /** The default number of cells that can fit horizontally in a widget sheet. */
     public static final int DEFAULT_MAX_HORIZONTAL_SPANS = 4;
 
@@ -106,14 +106,14 @@
         WindowInsets windowInsets = WindowManagerProxy.INSTANCE.get(getContext())
                 .normalizeWindowInsets(getContext(), getRootWindowInsets(), new Rect());
         mNavBarScrimHeight = getNavBarScrimHeight(windowInsets);
-        mActivityContext.getPopupDataProvider().setChangeListener(this);
+        mActivityContext.getWidgetPickerDataProvider().setChangeListener(this);
         mActivityContext.addOnDeviceProfileChangeListener(this);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        mActivityContext.getPopupDataProvider().setChangeListener(null);
+        mActivityContext.getWidgetPickerDataProvider().setChangeListener(null);
         mActivityContext.removeOnDeviceProfileChangeListener(this);
     }
 
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 894099d..ddbd291 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.widget;
 
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_BOTTOM_WIDGETS_TRAY;
+import static com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.findAllWidgetsForPackageUser;
 
 import android.content.Context;
 import android.graphics.Rect;
@@ -40,6 +41,7 @@
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.picker.model.data.WidgetPickerData;
 import com.android.launcher3.widget.util.WidgetsTableUtils;
 
 import java.util.List;
@@ -124,10 +126,10 @@
 
     @Override
     public void onWidgetsBound() {
-        List<WidgetItem> widgets = mActivityContext.getPopupDataProvider().getWidgetsForPackageUser(
-                new PackageUserKey(
-                        mOriginalItemInfo.getTargetComponent().getPackageName(),
-                        mOriginalItemInfo.user));
+        final WidgetPickerData data = mActivityContext.getWidgetPickerDataProvider().get();
+        final PackageUserKey packageUserKey = PackageUserKey.fromItemInfo(mOriginalItemInfo);
+        List<WidgetItem> widgets = packageUserKey != null ? findAllWidgetsForPackageUser(data,
+                packageUserKey) : List.of();
 
         TableLayout widgetsTable = findViewById(R.id.widgets_table);
         widgetsTable.removeAllViews();
@@ -247,4 +249,7 @@
             }
         }
     }
+
+    @Override
+    public void onRecommendedWidgetsBound() {} // no op
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategory.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategory.java
index 072d1d5..a68effd 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategory.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategory.java
@@ -19,6 +19,8 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 
+import com.android.launcher3.R;
+
 import java.util.Objects;
 
 /**
@@ -26,6 +28,10 @@
  * option in the pop-up opened on long press of launcher workspace).
  */
 public class WidgetRecommendationCategory implements Comparable<WidgetRecommendationCategory> {
+    public static WidgetRecommendationCategory DEFAULT_WIDGET_RECOMMENDATION_CATEGORY =
+            new WidgetRecommendationCategory(
+                    R.string.others_widget_recommendation_category_label, /*order=*/0);
+
     /** Resource id that holds the user friendly label for the category. */
     @StringRes
     public final int categoryTitleRes;
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 21b7be4..2af8e6f 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -74,6 +74,7 @@
 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -471,7 +472,7 @@
      * Returns all displayable widgets.
      */
     protected List<WidgetsListBaseEntry> getWidgetsToDisplay() {
-        return mActivityContext.getPopupDataProvider().getAllWidgets();
+        return mActivityContext.getWidgetPickerDataProvider().get().getAllWidgets();
     }
 
     @Override
@@ -572,12 +573,11 @@
         if (mIsInSearchMode) {
             return;
         }
-
         if (enableCategorizedWidgetSuggestions()) {
             // We avoid applying new recommendations when some are already displayed.
             if (mRecommendedWidgetsMap.isEmpty()) {
                 mRecommendedWidgetsMap =
-                        mActivityContext.getPopupDataProvider().getCategorizedRecommendedWidgets();
+                        mActivityContext.getWidgetPickerDataProvider().get().getRecommendations();
             }
             mRecommendedWidgetsCount = mWidgetRecommendationsView.setRecommendations(
                     mRecommendedWidgetsMap,
@@ -589,17 +589,20 @@
             );
         } else {
             if (mRecommendedWidgets.isEmpty()) {
-                mRecommendedWidgets =
-                        mActivityContext.getPopupDataProvider().getRecommendedWidgets();
+                mRecommendedWidgets = mActivityContext.getWidgetPickerDataProvider().get()
+                        .getRecommendations()
+                        .values().stream()
+                        .flatMap(Collection::stream).toList();
+                mRecommendedWidgetsCount = mWidgetRecommendationsView.setRecommendations(
+                        mRecommendedWidgets,
+                        mDeviceProfile,
+                        /* availableHeight= */ getMaxAvailableHeightForRecommendations(),
+                        /* availableWidth= */ mMaxSpanPerRow,
+                        /* cellPadding= */ mWidgetCellHorizontalPadding
+                );
             }
-            mRecommendedWidgetsCount = mWidgetRecommendationsView.setRecommendations(
-                    mRecommendedWidgets,
-                    mDeviceProfile,
-                    /* availableHeight= */ getMaxAvailableHeightForRecommendations(),
-                    /* availableWidth= */ mMaxSpanPerRow,
-                    /* cellPadding= */ mWidgetCellHorizontalPadding
-            );
         }
+
         mWidgetRecommendationsContainer.setVisibility(
                 mRecommendedWidgetsCount > 0 ? VISIBLE : GONE);
     }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index c4c755a..c2cd903 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree;
 import static com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree;
 import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.WIDGET_LIST_ITEM_APPEARANCE_DELAY;
+import static com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.findContentEntryForPackageUser;
 
 import android.content.Context;
 import android.graphics.Rect;
@@ -287,9 +288,9 @@
     @Override
     protected List<WidgetsListBaseEntry> getWidgetsToDisplay() {
         List<WidgetsListBaseEntry> allWidgets =
-                mActivityContext.getPopupDataProvider().getAllWidgets();
+                mActivityContext.getWidgetPickerDataProvider().get().getAllWidgets();
         List<WidgetsListBaseEntry> defaultWidgets =
-                mActivityContext.getPopupDataProvider().getDefaultWidgets();
+                mActivityContext.getWidgetPickerDataProvider().get().getDefaultWidgets();
 
         if (allWidgets.isEmpty() || defaultWidgets.isEmpty()) {
             // no menu if there are no default widgets to show
@@ -359,7 +360,7 @@
         WidgetsListHeaderEntry widgetsListHeaderEntry = WidgetsListHeaderEntry.create(
                         packageItemInfo,
                         /*titleSectionName=*/ suggestionsHeaderTitle,
-                        /*items=*/ mActivityContext.getPopupDataProvider().getRecommendedWidgets(),
+                        /*items=*/ List.of(), // not necessary
                         /*visibleWidgetsCount=*/ 0)
                 .withWidgetListShown();
 
@@ -509,11 +510,11 @@
                 final boolean isUserClick = mSelectedHeader != null
                         && !getAccessibilityInitialFocusView().isAccessibilityFocused();
                 mSelectedHeader = selectedHeader;
-                WidgetsListContentEntry contentEntry =
-                        mActivityContext.getPopupDataProvider().getSelectedAppWidgets(
-                                selectedHeader, /*useDefault=*/
-                                (mWidgetOptionsMenuState != null
-                                        && !mWidgetOptionsMenuState.showAllWidgets));
+                final boolean showDefaultWidgets = mWidgetOptionsMenuState != null
+                        && !mWidgetOptionsMenuState.showAllWidgets;
+                WidgetsListContentEntry contentEntry = findContentEntryForPackageUser(
+                        mActivityContext.getWidgetPickerDataProvider().get(),
+                        selectedHeader, showDefaultWidgets);
 
                 if (contentEntry == null || mRightPane == null) {
                     return;
diff --git a/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProvider.kt b/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProvider.kt
new file mode 100644
index 0000000..46d3e7a
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProvider.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 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 com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.widget.model.WidgetsListBaseEntry
+import com.android.launcher3.widget.picker.model.data.WidgetPickerData
+import com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.withRecommendedWidgets
+import com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.withWidgets
+import java.io.PrintWriter
+
+/**
+ * Provides [WidgetPickerData] to various views such as widget picker, app-specific widget picker,
+ * widgets shortcut.
+ */
+class WidgetPickerDataProvider {
+    /** All the widgets data provided for the views */
+    private var mWidgetPickerData: WidgetPickerData = WidgetPickerData()
+
+    private var changeListener: WidgetPickerDataChangeListener? = null
+
+    /** Sets a listener to be called back when widget data is updated. */
+    fun setChangeListener(changeListener: WidgetPickerDataChangeListener?) {
+        this.changeListener = changeListener
+    }
+
+    /** Returns the current snapshot of [WidgetPickerData]. */
+    fun get(): WidgetPickerData {
+        return mWidgetPickerData
+    }
+
+    /**
+     * Updates the widgets available to the widget picker.
+     *
+     * Generally called when the widgets model has new data.
+     */
+    @JvmOverloads
+    fun setWidgets(
+        allWidgets: List<WidgetsListBaseEntry>,
+        defaultWidgets: List<WidgetsListBaseEntry> = listOf()
+    ) {
+        mWidgetPickerData =
+            mWidgetPickerData.withWidgets(allWidgets = allWidgets, defaultWidgets = defaultWidgets)
+        changeListener?.onWidgetsBound()
+    }
+
+    /**
+     * Makes the widget recommendations available to the widget picker
+     *
+     * Generally called when new widget predictions are available.
+     */
+    fun setWidgetRecommendations(recommendations: List<ItemInfo>) {
+        mWidgetPickerData = mWidgetPickerData.withRecommendedWidgets(recommendations)
+        changeListener?.onRecommendedWidgetsBound()
+    }
+
+    /** Writes the current state to the provided writer. */
+    fun dump(prefix: String, writer: PrintWriter) {
+        writer.println(prefix + "WidgetPickerDataProvider:")
+        writer.println("$prefix\twidgetPickerData:$mWidgetPickerData")
+    }
+
+    interface WidgetPickerDataChangeListener {
+        /** A callback to get notified when widgets are bound. */
+        fun onWidgetsBound()
+
+        /** A callback to get notified when recommended widgets are bound. */
+        fun onRecommendedWidgetsBound()
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/model/data/WidgetPickerData.kt b/src/com/android/launcher3/widget/picker/model/data/WidgetPickerData.kt
new file mode 100644
index 0000000..3332ef0
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/model/data/WidgetPickerData.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 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.data
+
+import com.android.launcher3.model.WidgetItem
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.widget.PendingAddWidgetInfo
+import com.android.launcher3.widget.model.WidgetsListBaseEntry
+import com.android.launcher3.widget.model.WidgetsListContentEntry
+import com.android.launcher3.widget.picker.WidgetRecommendationCategory
+import com.android.launcher3.widget.picker.WidgetRecommendationCategory.DEFAULT_WIDGET_RECOMMENDATION_CATEGORY
+
+// This file contains WidgetPickerData and utility functions to operate on it.
+
+/** Widget data for display in the widget picker. */
+data class WidgetPickerData(
+    val allWidgets: List<WidgetsListBaseEntry> = listOf(),
+    val defaultWidgets: List<WidgetsListBaseEntry> = listOf(),
+    val recommendations: Map<WidgetRecommendationCategory, List<WidgetItem>> = mapOf(),
+)
+
+/** Provides utility methods to work with a [WidgetPickerData] object. */
+object WidgetPickerDataUtils {
+    /**
+     * Returns a [WidgetPickerData] with the provided widgets.
+     *
+     * When [defaultWidgets] is not passed, defaults from previous object are not copied over.
+     * Defaults (if supported) should be updated when all widgets are updated.
+     */
+    fun WidgetPickerData.withWidgets(
+        allWidgets: List<WidgetsListBaseEntry>,
+        defaultWidgets: List<WidgetsListBaseEntry> = listOf()
+    ): WidgetPickerData {
+        return copy(allWidgets = allWidgets, defaultWidgets = defaultWidgets)
+    }
+
+    /** Returns a [WidgetPickerData] with the given recommendations set. */
+    fun WidgetPickerData.withRecommendedWidgets(recommendations: List<ItemInfo>): WidgetPickerData {
+        val allWidgetsMap: Map<ComponentKey, WidgetItem> =
+            allWidgets
+                .filterIsInstance<WidgetsListContentEntry>()
+                .flatMap { it.mWidgets }
+                .filterNotNull()
+                .distinct()
+                .associateBy { it } // as ComponentKey
+
+        val categoriesMap =
+            recommendations
+                .filterIsInstance<PendingAddWidgetInfo>()
+                .filter { allWidgetsMap.containsKey(ComponentKey(it.targetComponent, it.user)) }
+                .groupBy { it.recommendationCategory ?: DEFAULT_WIDGET_RECOMMENDATION_CATEGORY }
+                .mapValues { (_, pendingAddWidgetInfos) ->
+                    pendingAddWidgetInfos.map {
+                        allWidgetsMap[ComponentKey(it.targetComponent, it.user)] as WidgetItem
+                    }
+                }
+
+        return copy(recommendations = categoriesMap)
+    }
+
+    /** Finds all [WidgetItem]s available for the provided package user. */
+    @JvmStatic
+    fun findAllWidgetsForPackageUser(
+        widgetPickerData: WidgetPickerData,
+        packageUserKey: PackageUserKey
+    ): List<WidgetItem> {
+        return findContentEntryForPackageUser(widgetPickerData, packageUserKey)?.mWidgets
+            ?: emptyList()
+    }
+
+    /**
+     * Finds and returns the [WidgetsListContentEntry] for the given package user.
+     *
+     * Set [fromDefaultWidgets] to true to limit the content entry to default widgets.
+     */
+    @JvmOverloads
+    @JvmStatic
+    fun findContentEntryForPackageUser(
+        widgetPickerData: WidgetPickerData,
+        packageUserKey: PackageUserKey,
+        fromDefaultWidgets: Boolean = false
+    ): WidgetsListContentEntry? {
+        val widgetsListBaseEntries =
+            if (fromDefaultWidgets) {
+                widgetPickerData.defaultWidgets
+            } else {
+                widgetPickerData.allWidgets
+            }
+
+        return widgetsListBaseEntries.filterIsInstance<WidgetsListContentEntry>().firstOrNull {
+            PackageUserKey.fromPackageItemInfo(it.mPkgItem) == packageUserKey
+        }
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/allapps/FloatingHeaderViewTests.kt b/tests/multivalentTests/src/com/android/launcher3/allapps/FloatingHeaderViewTests.kt
new file mode 100644
index 0000000..ac2c553
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/allapps/FloatingHeaderViewTests.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.allapps
+
+import android.content.Context
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.Flags
+import com.android.launcher3.util.ActivityContextWrapper
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FloatingHeaderViewTests {
+
+    @get:Rule val mSetFlagsRule = SetFlagsRule()
+
+    private lateinit var context: Context
+    private lateinit var vut: FloatingHeaderView
+
+    @Before
+    fun setUp() {
+        context = ActivityContextWrapper(getApplicationContext())
+        // TODO(b/352161553): Inflate FloatingHeaderView or R.layout.all_apps_content with proper
+        // FloatingHeaderView#setup
+        vut = FloatingHeaderView(context)
+        vut.onFinishInflate()
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_FLOATING_SEARCH_BAR, Flags.FLAG_MULTILINE_SEARCH_BAR)
+    fun onHeightUpdated_whenNotMultiline_thenZeroHeight() {
+        vut.setFloatingRowsCollapsed(true)
+        val beforeHeight = vut.maxTranslation
+        vut.updateSearchBarOffset(HEADER_HEIGHT_OFFSET)
+
+        vut.onHeightUpdated()
+
+        assertThat(vut.maxTranslation).isEqualTo(beforeHeight)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MULTILINE_SEARCH_BAR)
+    @DisableFlags(Flags.FLAG_FLOATING_SEARCH_BAR)
+    fun onHeightUpdated_whenMultiline_thenHeightIsOffset() {
+        vut.setFloatingRowsCollapsed(true)
+        vut.updateSearchBarOffset(HEADER_HEIGHT_OFFSET)
+
+        vut.onHeightUpdated()
+
+        assertThat(vut.maxTranslation).isEqualTo(HEADER_HEIGHT_OFFSET)
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MULTILINE_SEARCH_BAR)
+    @EnableFlags(Flags.FLAG_FLOATING_SEARCH_BAR)
+    fun onHeightUpdated_whenFloatingRowsShownAndNotMultiline_thenAddsOnlyFloatingRow() {
+        // Collapse floating rows and expand to trigger header height calculation
+        vut.setFloatingRowsCollapsed(true)
+        vut.setFloatingRowsCollapsed(false)
+        val defaultHeight = vut.maxTranslation
+        vut.updateSearchBarOffset(HEADER_HEIGHT_OFFSET)
+
+        vut.onHeightUpdated()
+
+        assertThat(vut.maxTranslation).isEqualTo(defaultHeight)
+    }
+
+    companion object {
+        private const val HEADER_HEIGHT_OFFSET = 50
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java b/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
index 3f37563..71637f1 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
@@ -32,6 +32,7 @@
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
 
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
@@ -57,6 +58,8 @@
     protected ActivityAllAppsContainerView<ActivityContextWrapper> mAppsView;
 
     private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
+    private final WidgetPickerDataProvider mWidgetPickerDataProvider =
+            new WidgetPickerDataProvider();
     protected final UserCache mUserCache;
 
     public TestSandboxModelContextWrapper(SandboxContext base) {
@@ -76,12 +79,19 @@
         mAppsList = mAppsView.getPersonalAppList();
         mAllAppsStore = mAppsView.getAppsStore();
     }
+
     @Nullable
     @Override
     public PopupDataProvider getPopupDataProvider() {
         return mPopupDataProvider;
     }
 
+    @Nullable
+    @Override
+    public WidgetPickerDataProvider getWidgetPickerDataProvider() {
+        return mWidgetPickerDataProvider;
+    }
+
     @Override
     public ActivityAllAppsContainerView<ActivityContextWrapper> getAppsView() {
         return mAppsView;
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProviderTest.kt
new file mode 100644
index 0000000..1822639
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProviderTest.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2024 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 android.content.ComponentName
+import android.content.Context
+import android.os.UserHandle
+import android.platform.test.rule.AllowedDevices
+import android.platform.test.rule.DeviceProduct
+import android.platform.test.rule.LimitDevicesRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings
+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.util.ActivityContextWrapper
+import com.android.launcher3.util.WidgetUtils
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
+import com.android.launcher3.widget.PendingAddWidgetInfo
+import com.android.launcher3.widget.model.WidgetsListBaseEntry
+import com.android.launcher3.widget.model.WidgetsListContentEntry
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry
+import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider.WidgetPickerDataChangeListener
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoMoreInteractions
+
+// Tests for the WidgetPickerDataProvider class
+
+@RunWith(AndroidJUnit4::class)
+@AllowedDevices(allowed = [DeviceProduct.ROBOLECTRIC])
+class WidgetPickerDataProviderTest {
+    @Rule @JvmField val limitDevicesRule = LimitDevicesRule()
+    @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    @Mock private lateinit var changeListener: WidgetPickerDataChangeListener
+
+    @Mock private lateinit var iconCache: IconCache
+
+    private lateinit var userHandle: UserHandle
+    private lateinit var context: Context
+    private lateinit var testInvariantProfile: InvariantDeviceProfile
+
+    private lateinit var appWidgetItem: WidgetItem
+
+    private var underTest = WidgetPickerDataProvider()
+
+    @Before
+    fun setUp() {
+        userHandle = UserHandle.CURRENT
+        context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+        testInvariantProfile = LauncherAppState.getIDP(context)
+
+        doAnswer { invocation: InvocationOnMock ->
+                val componentWithLabel = invocation.getArgument<Any>(0) as ComponentWithLabel
+                componentWithLabel.getComponent().shortClassName
+            }
+            .`when`(iconCache)
+            .getTitleNoCache(any<ComponentWithLabel>())
+
+        appWidgetItem = createWidgetItem()
+    }
+
+    @Test
+    fun setWidgets_invokesTheListener_andUpdatedWidgetsAvailable() {
+        assertThat(underTest.get().allWidgets).isEmpty()
+
+        underTest.setChangeListener(changeListener)
+        val allWidgets = appWidgetListBaseEntries()
+        underTest.setWidgets(allWidgets = allWidgets)
+
+        assertThat(underTest.get().allWidgets).containsExactlyElementsIn(allWidgets)
+        verify(changeListener, times(1)).onWidgetsBound()
+        verifyNoMoreInteractions(changeListener)
+    }
+
+    @Test
+    fun setWidgetRecommendations_callsBackTheListener_andUpdatedRecommendationsAvailable() {
+        underTest.setWidgets(allWidgets = appWidgetListBaseEntries())
+        assertThat(underTest.get().recommendations).isEmpty()
+
+        underTest.setChangeListener(changeListener)
+        val recommendations =
+            listOf(
+                PendingAddWidgetInfo(
+                    appWidgetItem.widgetInfo,
+                    LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION
+                ),
+            )
+        underTest.setWidgetRecommendations(recommendations)
+
+        assertThat(underTest.get().recommendations).hasSize(1)
+        verify(changeListener, times(1)).onRecommendedWidgetsBound()
+        verifyNoMoreInteractions(changeListener)
+    }
+
+    @Test
+    fun setChangeListener_null_noCallback() {
+        underTest.setChangeListener(changeListener)
+        underTest.setChangeListener(null) // reset
+
+        underTest.setWidgets(allWidgets = appWidgetListBaseEntries())
+        val recommendations =
+            listOf(
+                PendingAddWidgetInfo(
+                    appWidgetItem.widgetInfo,
+                    LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION
+                ),
+            )
+        underTest.setWidgetRecommendations(recommendations)
+
+        verifyNoMoreInteractions(changeListener)
+    }
+
+    private fun createWidgetItem(): WidgetItem {
+        val providerInfo =
+            WidgetUtils.createAppWidgetProviderInfo(
+                ComponentName.createRelative(APP_PACKAGE_NAME, APP_PROVIDER_1_CLASS_NAME)
+            )
+        val widgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(context, providerInfo)
+        return WidgetItem(widgetInfo, testInvariantProfile, iconCache, context)
+    }
+
+    private fun appWidgetListBaseEntries(): List<WidgetsListBaseEntry> {
+        val packageItemInfo = PackageItemInfo(APP_PACKAGE_NAME, userHandle)
+        packageItemInfo.title = APP_PACKAGE_TITLE
+        val widgets = listOf(appWidgetItem)
+
+        return buildList {
+            add(WidgetsListHeaderEntry.create(packageItemInfo, APP_SECTION_NAME, widgets))
+            add(WidgetsListContentEntry(packageItemInfo, APP_SECTION_NAME, widgets))
+        }
+    }
+
+    companion object {
+        const val APP_PACKAGE_NAME = "com.example.app"
+        const val APP_PACKAGE_TITLE = "SomeApp"
+        const val APP_SECTION_NAME = "S" // for fast popup
+        const val APP_PROVIDER_1_CLASS_NAME = "appProvider1"
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/data/WidgetPickerDataTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/data/WidgetPickerDataTest.kt
new file mode 100644
index 0000000..e59e211
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/data/WidgetPickerDataTest.kt
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2024 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.data
+
+import android.content.ComponentName
+import android.content.Context
+import android.os.UserHandle
+import android.platform.test.rule.AllowedDevices
+import android.platform.test.rule.DeviceProduct
+import android.platform.test.rule.LimitDevicesRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION
+import com.android.launcher3.icons.ComponentWithLabel
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.WidgetItem
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.PackageItemInfo
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.util.WidgetUtils
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
+import com.android.launcher3.widget.PendingAddWidgetInfo
+import com.android.launcher3.widget.model.WidgetsListBaseEntry
+import com.android.launcher3.widget.model.WidgetsListContentEntry
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry
+import com.android.launcher3.widget.picker.WidgetRecommendationCategory
+import com.android.launcher3.widget.picker.WidgetRecommendationCategory.DEFAULT_WIDGET_RECOMMENDATION_CATEGORY
+import com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.findAllWidgetsForPackageUser
+import com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.findContentEntryForPackageUser
+import com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.withRecommendedWidgets
+import com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.withWidgets
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+
+// Tests for code / classes in WidgetPickerData file.
+
+@RunWith(AndroidJUnit4::class)
+@AllowedDevices(allowed = [DeviceProduct.ROBOLECTRIC])
+class WidgetPickerDataTest {
+    @Rule @JvmField val limitDevicesRule = LimitDevicesRule()
+    @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    @Mock private lateinit var iconCache: IconCache
+
+    private lateinit var userHandle: UserHandle
+    private lateinit var context: Context
+    private lateinit var testInvariantProfile: InvariantDeviceProfile
+
+    private lateinit var app1PackageItemInfo: PackageItemInfo
+    private lateinit var app2PackageItemInfo: PackageItemInfo
+
+    private lateinit var app1WidgetItem1: WidgetItem
+    private lateinit var app1WidgetItem2: WidgetItem
+    private lateinit var app2WidgetItem1: WidgetItem
+
+    @Before
+    fun setUp() {
+        userHandle = UserHandle.CURRENT
+        context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+        testInvariantProfile = LauncherAppState.getIDP(context)
+
+        doAnswer { invocation: InvocationOnMock ->
+                val componentWithLabel = invocation.getArgument<Any>(0) as ComponentWithLabel
+                componentWithLabel.getComponent().shortClassName
+            }
+            .`when`(iconCache)
+            .getTitleNoCache(any<ComponentWithLabel>())
+
+        app1PackageItemInfo = packageItemInfoWithTitle(APP_1_PACKAGE_NAME, APP_1_PACKAGE_TITLE)
+        app2PackageItemInfo = packageItemInfoWithTitle(APP_2_PACKAGE_NAME, APP_2_PACKAGE_TITLE)
+
+        app1WidgetItem1 = createWidgetItem(APP_1_PACKAGE_NAME, APP_1_PROVIDER_1_CLASS_NAME)
+        app1WidgetItem2 = createWidgetItem(APP_1_PACKAGE_NAME, APP_1_PROVIDER_2_CLASS_NAME)
+        app2WidgetItem1 = createWidgetItem(APP_2_PACKAGE_NAME, APP_2_PROVIDER_1_CLASS_NAME)
+    }
+
+    @Test
+    fun withWidgets_returnsACopyWithProvidedWidgets() {
+        // only app two
+        val widgetPickerData = WidgetPickerData(allWidgets = appTwoWidgetsListBaseEntries())
+
+        // update: only app 1 and default list set
+        val newAllWidgets: List<WidgetsListBaseEntry> =
+            appOneWidgetsListBaseEntries(includeWidgetTwo = true)
+        val newDefaultWidgets: List<WidgetsListBaseEntry> =
+            appOneWidgetsListBaseEntries(includeWidgetTwo = false)
+
+        val newWidgetData = widgetPickerData.withWidgets(newAllWidgets, newDefaultWidgets)
+
+        assertThat(newWidgetData.allWidgets).containsExactlyElementsIn(newAllWidgets)
+        assertThat(newWidgetData.defaultWidgets).containsExactlyElementsIn(newDefaultWidgets)
+    }
+
+    @Test
+    fun withWidgets_noExplicitDefaults_unsetsOld() {
+        // only app two
+        val widgetPickerData =
+            WidgetPickerData(
+                allWidgets = appTwoWidgetsListBaseEntries(),
+                defaultWidgets = appTwoWidgetsListBaseEntries()
+            )
+
+        val newWidgetData =
+            widgetPickerData.withWidgets(allWidgets = appOneWidgetsListBaseEntries())
+
+        assertThat(newWidgetData.allWidgets)
+            .containsExactlyElementsIn(appOneWidgetsListBaseEntries())
+        assertThat(newWidgetData.defaultWidgets).isEmpty() // previous values cleared.
+    }
+
+    @Test
+    fun withRecommendedWidgets_returnsACopyWithProvidedRecommendedWidgets() {
+        val widgetPickerData =
+            WidgetPickerData(
+                allWidgets =
+                    buildList {
+                        addAll(appOneWidgetsListBaseEntries())
+                        addAll(appTwoWidgetsListBaseEntries())
+                    },
+                defaultWidgets = buildList { appTwoWidgetsListBaseEntries() }
+            )
+        val recommendations: List<ItemInfo> =
+            listOf(
+                PendingAddWidgetInfo(
+                    app1WidgetItem1.widgetInfo,
+                    CONTAINER_WIDGETS_PREDICTION,
+                    CATEGORY_1
+                ),
+                PendingAddWidgetInfo(
+                    app2WidgetItem1.widgetInfo,
+                    CONTAINER_WIDGETS_PREDICTION,
+                    CATEGORY_2
+                ),
+            )
+
+        val updatedData = widgetPickerData.withRecommendedWidgets(recommendations)
+
+        assertThat(updatedData.recommendations.keys).containsExactly(CATEGORY_1, CATEGORY_2)
+        assertThat(updatedData.recommendations[CATEGORY_1]).containsExactly(app1WidgetItem1)
+        assertThat(updatedData.recommendations[CATEGORY_2]).containsExactly(app2WidgetItem1)
+    }
+
+    @Test
+    fun withRecommendedWidgets_noCategory_usesDefault() {
+        val widgetPickerData =
+            WidgetPickerData(
+                allWidgets =
+                    buildList {
+                        addAll(appOneWidgetsListBaseEntries())
+                        addAll(appTwoWidgetsListBaseEntries())
+                    },
+                defaultWidgets = buildList { appTwoWidgetsListBaseEntries() }
+            )
+        val recommendations: List<ItemInfo> =
+            listOf(
+                PendingAddWidgetInfo(app1WidgetItem1.widgetInfo, CONTAINER_WIDGETS_PREDICTION),
+                PendingAddWidgetInfo(app2WidgetItem1.widgetInfo, CONTAINER_WIDGETS_PREDICTION),
+            )
+
+        val updatedData = widgetPickerData.withRecommendedWidgets(recommendations)
+
+        assertThat(updatedData.recommendations.keys)
+            .containsExactly(DEFAULT_WIDGET_RECOMMENDATION_CATEGORY)
+        assertThat(updatedData.recommendations[DEFAULT_WIDGET_RECOMMENDATION_CATEGORY])
+            .containsExactly(app1WidgetItem1, app2WidgetItem1)
+    }
+
+    @Test
+    fun withRecommendedWidgets_emptyRecommendations_clearsOld() {
+        val widgetPickerData =
+            WidgetPickerData(
+                allWidgets =
+                    buildList {
+                        addAll(appOneWidgetsListBaseEntries())
+                        addAll(appTwoWidgetsListBaseEntries())
+                    },
+                defaultWidgets = buildList { appTwoWidgetsListBaseEntries() },
+                recommendations = mapOf(CATEGORY_1 to listOf(app1WidgetItem1))
+            )
+
+        val updatedData = widgetPickerData.withRecommendedWidgets(listOf())
+
+        assertThat(updatedData.recommendations).isEmpty()
+    }
+
+    @Test
+    fun withRecommendedWidgets_widgetNotInAllWidgets_filteredOut() {
+        val widgetPickerData =
+            WidgetPickerData(
+                allWidgets =
+                    buildList {
+                        addAll(appOneWidgetsListBaseEntries(includeWidgetTwo = false))
+                        addAll(appTwoWidgetsListBaseEntries())
+                    },
+                defaultWidgets = buildList { appTwoWidgetsListBaseEntries() },
+            )
+
+        val recommendations: List<ItemInfo> =
+            listOf(
+                PendingAddWidgetInfo(app1WidgetItem2.widgetInfo, CONTAINER_WIDGETS_PREDICTION),
+                PendingAddWidgetInfo(app2WidgetItem1.widgetInfo, CONTAINER_WIDGETS_PREDICTION),
+            )
+        val updatedData = widgetPickerData.withRecommendedWidgets(recommendations)
+
+        assertThat(updatedData.recommendations).hasSize(1)
+        // no app1widget2
+        assertThat(updatedData.recommendations.values.first()).containsExactly(app2WidgetItem1)
+    }
+
+    @Test
+    fun findContentEntryForPackageUser_returnsCorrectEntry() {
+        val widgetPickerData =
+            WidgetPickerData(
+                allWidgets =
+                    buildList {
+                        addAll(appOneWidgetsListBaseEntries())
+                        addAll(appTwoWidgetsListBaseEntries())
+                    },
+                defaultWidgets = buildList { addAll(appTwoWidgetsListBaseEntries()) }
+            )
+        val app1PackageUserKey = PackageUserKey.fromPackageItemInfo(app1PackageItemInfo)
+
+        val contentEntry = findContentEntryForPackageUser(widgetPickerData, app1PackageUserKey)
+
+        assertThat(contentEntry).isNotNull()
+        assertThat(contentEntry?.mPkgItem).isEqualTo(app1PackageItemInfo)
+        assertThat(contentEntry?.mWidgets).hasSize(2)
+    }
+
+    @Test
+    fun findContentEntryForPackageUser_fromDefaults_returnsEntryFromDefaultWidgets() {
+        val widgetPickerData =
+            WidgetPickerData(
+                allWidgets =
+                    buildList {
+                        addAll(appOneWidgetsListBaseEntries())
+                        addAll(appTwoWidgetsListBaseEntries())
+                    },
+                defaultWidgets =
+                    buildList { addAll(appOneWidgetsListBaseEntries(includeWidgetTwo = false)) }
+            )
+        val app1PackageUserKey = PackageUserKey.fromPackageItemInfo(app1PackageItemInfo)
+
+        val contentEntry =
+            findContentEntryForPackageUser(
+                widgetPickerData = widgetPickerData,
+                packageUserKey = app1PackageUserKey,
+                fromDefaultWidgets = true
+            )
+
+        assertThat(contentEntry).isNotNull()
+        assertThat(contentEntry?.mPkgItem).isEqualTo(app1PackageItemInfo)
+        // only one widget (since default widgets had only one widget for app A
+        assertThat(contentEntry?.mWidgets).hasSize(1)
+    }
+
+    @Test
+    fun findContentEntryForPackageUser_noMatch_returnsNull() {
+        val app2PackageUserKey = PackageUserKey.fromPackageItemInfo(app2PackageItemInfo)
+        val widgetPickerData =
+            WidgetPickerData(allWidgets = buildList { addAll(appOneWidgetsListBaseEntries()) })
+
+        val contentEntry = findContentEntryForPackageUser(widgetPickerData, app2PackageUserKey)
+
+        assertThat(contentEntry).isNull()
+    }
+
+    @Test
+    fun findAllWidgetsForPackageUser_returnsListOfWidgets() {
+        val app1PackageUserKey = PackageUserKey.fromPackageItemInfo(app1PackageItemInfo)
+        val widgetPickerData =
+            WidgetPickerData(
+                allWidgets =
+                    buildList {
+                        addAll(appOneWidgetsListBaseEntries())
+                        addAll(appTwoWidgetsListBaseEntries())
+                    },
+                defaultWidgets =
+                    buildList { addAll(appOneWidgetsListBaseEntries(includeWidgetTwo = false)) }
+            )
+
+        val widgets = findAllWidgetsForPackageUser(widgetPickerData, app1PackageUserKey)
+
+        // both widgets returned irrespective of default widgets list
+        assertThat(widgets).hasSize(2)
+    }
+
+    @Test
+    fun findAllWidgetsForPackageUser_noMatch_returnsEmptyList() {
+        val widgetPickerData =
+            WidgetPickerData(
+                allWidgets = buildList { addAll(appTwoWidgetsListBaseEntries()) },
+            )
+        val app1PackageUserKey = PackageUserKey.fromPackageItemInfo(app1PackageItemInfo)
+
+        val widgets = findAllWidgetsForPackageUser(widgetPickerData, app1PackageUserKey)
+
+        assertThat(widgets).isEmpty()
+    }
+
+    private fun packageItemInfoWithTitle(packageName: String, title: String): PackageItemInfo {
+        val packageItemInfo = PackageItemInfo(packageName, userHandle)
+        packageItemInfo.title = title
+        return packageItemInfo
+    }
+
+    private fun createWidgetItem(packageName: String, widgetProviderName: String): WidgetItem {
+        val providerInfo =
+            WidgetUtils.createAppWidgetProviderInfo(
+                ComponentName.createRelative(packageName, widgetProviderName)
+            )
+        val widgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(context, providerInfo)
+        return WidgetItem(widgetInfo, testInvariantProfile, iconCache, context)
+    }
+
+    private fun appTwoWidgetsListBaseEntries(): List<WidgetsListBaseEntry> = buildList {
+        val widgets = listOf(app2WidgetItem1)
+        add(WidgetsListHeaderEntry.create(app2PackageItemInfo, APP_2_SECTION_NAME, widgets))
+        add(WidgetsListContentEntry(app2PackageItemInfo, APP_2_SECTION_NAME, widgets))
+    }
+
+    private fun appOneWidgetsListBaseEntries(
+        includeWidgetTwo: Boolean = true
+    ): List<WidgetsListBaseEntry> = buildList {
+        val widgets =
+            if (includeWidgetTwo) {
+                listOf(app1WidgetItem1, app1WidgetItem2)
+            } else {
+                listOf(app1WidgetItem1)
+            }
+
+        add(WidgetsListHeaderEntry.create(app1PackageItemInfo, APP_1_SECTION_NAME, widgets))
+        add(WidgetsListContentEntry(app1PackageItemInfo, APP_1_SECTION_NAME, widgets))
+    }
+
+    companion object {
+        private const val APP_1_PACKAGE_NAME = "com.example.app1"
+        private const val APP_1_PACKAGE_TITLE = "App1"
+        private const val APP_1_SECTION_NAME = "A" // for fast popup
+        private const val APP_1_PROVIDER_1_CLASS_NAME = "app1Provider1"
+        private const val APP_1_PROVIDER_2_CLASS_NAME = "app1Provider2"
+
+        private const val APP_2_PACKAGE_NAME = "com.example.app2"
+        private const val APP_2_PACKAGE_TITLE = "SomeApp2"
+        private const val APP_2_SECTION_NAME = "S" // for fast popup
+        private const val APP_2_PROVIDER_1_CLASS_NAME = "app2Provider1"
+
+        private val CATEGORY_1 =
+            WidgetRecommendationCategory(/* categoryTitleRes= */ 0, /* order= */ 0)
+        private val CATEGORY_2 =
+            WidgetRecommendationCategory(/* categoryTitleRes= */ 1, /* order= */ 1)
+    }
+}
diff --git a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
index 98b6b4b..dcfcad5 100644
--- a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
+++ b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
@@ -63,6 +63,8 @@
 import com.android.launcher3.util.TestSandboxModelContextWrapper;
 import com.android.launcher3.util.UserIconInfo;
 import com.android.launcher3.views.BaseDragLayer;
+import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
+import com.android.launcher3.widget.picker.model.data.WidgetPickerData;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -73,8 +75,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.ArrayList;
-
 @SmallTest
 @RunWith(LauncherMultivalentJUnit.class)
 public class SystemShortcutTest {
@@ -86,7 +86,7 @@
     private TestSandboxModelContextWrapper mTestContext;
     private final SandboxModelContext mSandboxContext = new SandboxModelContext();
     private PrivateProfileManager mPrivateProfileManager;
-    private PopupDataProvider mPopupDataProvider;
+    private WidgetPickerDataProvider mWidgetPickerDataProvider;
     private AppInfo mAppInfo;
     @Mock UserCache mUserCache;
     @Mock ApiWrapper mApiWrapper;
@@ -119,8 +119,8 @@
         spyOn(mPrivateProfileManager);
         when(mPrivateProfileManager.getProfileUser()).thenReturn(PRIVATE_HANDLE);
 
-        mPopupDataProvider = mTestContext.getPopupDataProvider();
-        spyOn(mPopupDataProvider);
+        mWidgetPickerDataProvider = mTestContext.getWidgetPickerDataProvider();
+        spyOn(mWidgetPickerDataProvider);
     }
 
     @After
@@ -141,7 +141,7 @@
         mAppInfo = new AppInfo();
         mAppInfo.componentName = new ComponentName(mTestContext, getClass());
         assertNotNull(mAppInfo.getTargetComponent());
-        doReturn(new ArrayList<>()).when(mPopupDataProvider).getWidgetsForPackageUser(any());
+        doReturn(new WidgetPickerData()).when(mWidgetPickerDataProvider).get();
         spyOn(mAppInfo);
         SystemShortcut systemShortcut = SystemShortcut.WIDGETS
                 .getShortcut(mTestContext, mAppInfo, mView);