Show full page view if categorized view is low density in large screen

UX prefers full page view over low density categorized view in tablets.

Bug: 347288847
Test: Screenshot (see the tablet screenshots vs foldable)
Flag: EXEMPT BUGFIX
Change-Id: I6c8ad36dc6169c65aea4e655af0542958e555669
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
index 9260af9..d84a219 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3.widget.picker;
 
-import static com.android.launcher3.widget.util.WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering;
+import static com.android.launcher3.widget.util.WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -38,6 +38,7 @@
 import com.android.launcher3.pageindicators.PageIndicatorDots;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -57,6 +58,12 @@
     private static final String INITIALLY_DISPLAYED_WIDGETS_STATE_KEY =
             "widgetRecommendationsView:mDisplayedWidgets";
     private static final int MAX_CATEGORIES = 3;
+
+    // Whether to show all widgets in a full page without any limitation on height
+    private boolean mShowFullPageViewIfLowDensity = false;
+    // Number of items below which a category is considered low density.
+    private static final int IDEAL_ITEMS_PER_CATEGORY = 2;
+
     private TextView mRecommendationPageTitle;
     private final List<String> mCategoryTitles = new ArrayList<>();
 
@@ -88,6 +95,14 @@
     }
 
     /**
+     * When there are less than 3 categories or when at least one category has less than 2 widgets,
+     * all widgets will be shown in a single page without being limited by the available height.
+     */
+    public void enableFullPageViewIfLowDensity() {
+        mShowFullPageViewIfLowDensity = true;
+    }
+
+    /**
      * Saves the necessary state in the provided bundle. To be called in case of orientation /
      * other config changes.
      */
@@ -170,6 +185,22 @@
         return displayedWidgets.size();
     }
 
+    private boolean shouldShowFullPageView(
+            Map<WidgetRecommendationCategory, List<WidgetItem>> recommendations) {
+        if (mShowFullPageViewIfLowDensity) {
+            boolean hasLessCategories = recommendations.size() < MAX_CATEGORIES;
+            long lowDensityCategoriesCount = recommendations.values()
+                    .stream()
+                    .limit(MAX_CATEGORIES)
+                    .filter(items -> items.size() < IDEAL_ITEMS_PER_CATEGORY).count();
+
+            // If there less number of categories or if there are at least 2 categorizes with less
+            // widgets, prefer showing single page view.
+            return hasLessCategories || lowDensityCategoriesCount > 1;
+        }
+        return false;
+    }
+
     /**
      * Displays the recommendations grouped by categories as pages.
      * <p>In case of a single category, no title is displayed for it.</p>
@@ -188,6 +219,14 @@
             Map<WidgetRecommendationCategory, List<WidgetItem>> recommendations,
             DeviceProfile deviceProfile, final @Px float availableHeight,
             final @Px int availableWidth, final @Px int cellPadding, final int requestedPage) {
+        if (shouldShowFullPageView(recommendations)) {
+            // Show all widgets in single page with unlimited available height.
+            return setRecommendations(
+                    recommendations.values().stream().flatMap(Collection::stream).toList(),
+                    deviceProfile, /*availableHeight=*/ Float.MAX_VALUE, availableWidth,
+                    cellPadding);
+
+        }
         this.mAvailableHeight = availableHeight;
         this.mAvailableWidth = availableWidth;
         Context context = getContext();
@@ -325,7 +364,7 @@
 
         // Since we are limited by space, we don't sort recommendations - to show most relevant
         // (if possible).
-        List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering(
+        List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithReordering(
                 filteredRecommendedWidgets,
                 context,
                 deviceProfile,
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 1ed3d88..0bcab60 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -18,7 +18,7 @@
 import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
 import static com.android.launcher3.widget.util.WidgetSizes.getWidgetSizePx;
-import static com.android.launcher3.widget.util.WidgetsTableUtils.WIDGETS_TABLE_ROW_SIZE_COMPARATOR;
+import static com.android.launcher3.widget.util.WidgetsTableUtils.WIDGETS_TABLE_ROW_COUNT_COMPARATOR;
 
 import static java.lang.Math.max;
 
@@ -163,6 +163,6 @@
         }
 
         // Perform re-ordering once we have filtered out recommendations that fit.
-        return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).toList();
+        return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_COUNT_COMPARATOR).toList();
     }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index 5b7bbc2..c84680d 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -119,6 +119,9 @@
         mWidgetRecommendationsView.initParentViews(mWidgetRecommendationsContainer);
         mWidgetRecommendationsView.setWidgetCellLongClickListener(this);
         mWidgetRecommendationsView.setWidgetCellOnClickListener(this);
+        if (!mDeviceProfile.isTwoPanels) {
+            mWidgetRecommendationsView.enableFullPageViewIfLowDensity();
+        }
         // To save the currently displayed page, so that, it can be requested when rebinding
         // recommendations with different size constraints.
         mWidgetRecommendationsView.addPageSwitchListener(
diff --git a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
index edaf474..df72f07 100644
--- a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
+++ b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
@@ -69,6 +69,21 @@
             });
 
     /**
+     * Comparator that enables displaying rows with more number of items at the top, and then
+     * rest of widgets shown in increasing order of their size (totalW * H).
+     */
+    public static final Comparator<ArrayList<WidgetItem>> WIDGETS_TABLE_ROW_COUNT_COMPARATOR =
+            Comparator.comparingInt(row -> {
+                if (row.size() > 1) {
+                    return -row.size();
+                } else {
+                    int rowWidth = row.stream().mapToInt(w -> w.spanX).sum();
+                    int rowHeight = row.get(0).spanY;
+                    return (rowWidth * rowHeight);
+                }
+            });
+
+    /**
      * Groups {@code widgetItems} items into a 2D array which matches their appearance in a UI
      * table. This takes liberty to rearrange widgets to make the table visually appealing.
      */