Show widget previews in their expected cell sizes

1. Show widget previews in their expected cell sizes.
2. Update the bottom picker to use a table layout rather than
a horizontal scrollbar.
3. Move the title and cell size description to the bottom of
the preview.

Test: Run AddConfigWidgetTest
      Manually open full and bottom widgets picker and observe
      widget previews.
      Video: https://drive.google.com/file/d/1K8A8hK5B4FgPTsX3cb-Yt4a_VjI0pk-w/view?usp=sharing
Bug: 179797520
Change-Id: I938612cb2e2477ce4a3a2d6c8891a0a36fc0978c
diff --git a/res/layout/live_preview_widget_cell.xml b/res/layout/live_preview_widget_cell.xml
index 7a42d19..1e1ce6e 100644
--- a/res/layout/live_preview_widget_cell.xml
+++ b/res/layout/live_preview_widget_cell.xml
@@ -15,14 +15,16 @@
 -->
 <com.android.launcher3.dragndrop.LivePreviewWidgetCell
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
+    android:layout_width="0dp"
     android:layout_height="wrap_content"
+    android:paddingHorizontal="@dimen/widget_cell_horizontal_padding"
+    android:paddingVertical="@dimen/widget_cell_vertical_padding"
     android:layout_weight="1"
     android:orientation="vertical"
     android:focusable="true"
     android:background="?android:attr/colorPrimaryDark"
     android:gravity="center_horizontal">
 
-    <include layout="@layout/widget_cell_content"  />
+    <include layout="@layout/widget_cell_content" />
 
 </com.android.launcher3.dragndrop.LivePreviewWidgetCell>
\ No newline at end of file
diff --git a/res/layout/widget_cell.xml b/res/layout/widget_cell.xml
index 148a99b..73a5737 100644
--- a/res/layout/widget_cell.xml
+++ b/res/layout/widget_cell.xml
@@ -15,8 +15,10 @@
 -->
 <com.android.launcher3.widget.WidgetCell
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
+    android:layout_width="0dp"
     android:layout_height="wrap_content"
+    android:paddingHorizontal="@dimen/widget_cell_horizontal_padding"
+    android:paddingVertical="@dimen/widget_cell_vertical_padding"
     android:layout_weight="1"
     android:orientation="vertical"
     android:focusable="true"
diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml
index 64f2362..c3e37e9 100644
--- a/res/layout/widget_cell_content.xml
+++ b/res/layout/widget_cell_content.xml
@@ -17,47 +17,37 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content">
 
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:paddingTop="@dimen/widget_preview_label_vertical_padding"
-        android:paddingBottom="@dimen/widget_preview_label_vertical_padding"
-        android:paddingLeft="@dimen/widget_preview_label_horizontal_padding"
-        android:paddingRight="@dimen/widget_preview_label_horizontal_padding"
-        android:orientation="horizontal">
-
-        <!-- The name of the widget. -->
-        <TextView
-            android:id="@+id/widget_name"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:ellipsize="end"
-            android:fadingEdge="horizontal"
-            android:gravity="start"
-            android:singleLine="true"
-            android:maxLines="1"
-            android:textColor="?android:attr/textColorSecondary"
-            android:textSize="14sp" />
-
-        <!-- The original dimensions of the widget (can't be the same text as above due to different
-             style. -->
-        <TextView
-            android:id="@+id/widget_dims"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="5dp"
-            android:layout_marginLeft="5dp"
-            android:textColor="?android:attr/textColorSecondary"
-            android:textSize="14sp"
-            android:alpha="0.8" />
-    </LinearLayout>
-
     <!-- The image of the widget. This view does not support padding. Any placement adjustment
          should be done using margins. -->
     <com.android.launcher3.widget.WidgetImageView
         android:id="@+id/widget_preview"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="0dp"
-        android:layout_weight="1" />
+        android:layout_weight="1"
+        android:layout_marginVertical="8dp" />
+
+    <!-- The name of the widget. -->
+    <TextView
+        android:id="@+id/widget_name"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:ellipsize="end"
+        android:fadingEdge="horizontal"
+        android:gravity="center_horizontal"
+        android:singleLine="true"
+        android:maxLines="1"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textSize="14sp" />
+
+    <!-- The original dimensions of the widget (can't be the same text as above due to different
+         style. -->
+    <TextView
+        android:id="@+id/widget_dims"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textSize="14sp"
+        android:alpha="0.8" />
+
 </merge>
\ No newline at end of file
diff --git a/res/layout/widgets_bottom_sheet.xml b/res/layout/widgets_bottom_sheet.xml
index 3fdfc96..c1b2cbf 100644
--- a/res/layout/widgets_bottom_sheet.xml
+++ b/res/layout/widgets_bottom_sheet.xml
@@ -44,11 +44,16 @@
         android:textSize="14sp"
         android:text="@string/long_press_widget_to_add"/>
 
-    <include layout="@layout/widgets_scroll_container"
-         android:id="@+id/widgets"
-         android:layout_width="wrap_content"
-         android:layout_height="wrap_content"
-         android:layout_marginTop="45dp"
-         android:layout_marginBottom="40dp"/>
+    <ScrollView
+        android:id="@+id/widgets_table_scroll_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="45dp"
+        android:layout_marginBottom="40dp">
+        <include layout="@layout/widgets_table_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal" />
+    </ScrollView>
 
 </com.android.launcher3.widget.WidgetsBottomSheet>
\ No newline at end of file
diff --git a/res/layout/widgets_table_container.xml b/res/layout/widgets_table_container.xml
index ffa239a..c4dfe7e 100644
--- a/res/layout/widgets_table_container.xml
+++ b/res/layout/widgets_table_container.xml
@@ -18,4 +18,5 @@
     android:id="@+id/widgets_table"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:layout_marginHorizontal="8dp"
     android:background="?android:attr/colorPrimaryDark" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index acc6466..ee91799 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -102,8 +102,8 @@
     <dimen name="work_profile_footer_text_size">16sp</dimen>
 
 <!-- Widget tray -->
-    <dimen name="widget_preview_label_vertical_padding">8dp</dimen>
-    <dimen name="widget_preview_label_horizontal_padding">16dp</dimen>
+    <dimen name="widget_cell_vertical_padding">8dp</dimen>
+    <dimen name="widget_cell_horizontal_padding">16dp</dimen>
 
     <dimen name="widget_preview_shadow_blur">0.5dp</dimen>
     <dimen name="widget_preview_key_shadow_distance">1dp</dimen>
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index bef91d2..c6593f8 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -24,7 +24,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnLayoutChangeListener;
-import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.LinearLayout;
@@ -60,6 +59,8 @@
     /** Widget preview width is calculated by multiplying this factor to the widget cell width. */
     private static final float PREVIEW_SCALE = 0.8f;
 
+    private int mPreviewWidth;
+    private int mPreviewHeight;
     protected int mPresetPreviewSize;
     private int mCellSize;
 
@@ -106,6 +107,7 @@
     private void setContainerWidth() {
         mCellSize = (int) (mDeviceProfile.allAppsIconSizePx * WIDTH_SCALE);
         mPresetPreviewSize = (int) (mCellSize * PREVIEW_SCALE);
+        mPreviewWidth = mPreviewHeight = mPresetPreviewSize;
     }
 
     @Override
@@ -128,6 +130,7 @@
         mWidgetImage.setBitmap(null, null);
         mWidgetName.setText(null);
         mWidgetDims.setText(null);
+        mPreviewWidth = mPreviewHeight = mPresetPreviewSize;
 
         if (mActiveRequest != null) {
             mActiveRequest.cancel();
@@ -181,6 +184,11 @@
             return;
         }
         if (bitmap != null) {
+            LayoutParams layoutParams = (LayoutParams) mWidgetImage.getLayoutParams();
+            layoutParams.width = bitmap.getWidth();
+            layoutParams.height = bitmap.getHeight();
+            mWidgetImage.setLayoutParams(layoutParams);
+
             mWidgetImage.setBitmap(bitmap, mWidgetPreviewLoader.getBadgeForUser(mItem.user,
                     BaseIconFactory.getBadgeSizeForIconSize(mDeviceProfile.allAppsIconSizePx)));
             if (mAnimatePreview) {
@@ -197,8 +205,16 @@
         if (mActiveRequest != null) {
             return;
         }
-        mActiveRequest = mWidgetPreviewLoader.getPreview(
-                mItem, mPresetPreviewSize, mPresetPreviewSize, this);
+        mActiveRequest = mWidgetPreviewLoader.getPreview(mItem, mPreviewWidth, mPreviewHeight,
+                this);
+    }
+
+    /** Sets the widget preview image size in number of cells. */
+    public void setPreviewSize(int spanX, int spanY) {
+        int padding = 2 * getResources()
+                .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
+        mPreviewWidth = mDeviceProfile.cellWidthPx * spanX + padding;
+        mPreviewHeight = mDeviceProfile.cellHeightPx * spanY + padding;
     }
 
     @Override
@@ -233,12 +249,6 @@
     }
 
     @Override
-    public void setLayoutParams(ViewGroup.LayoutParams params) {
-        params.width = params.height = mCellSize;
-        super.setLayoutParams(params);
-    }
-
-    @Override
     public CharSequence getAccessibilityClassName() {
         return WidgetCell.class.getName();
     }
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 223cda2..6abbf21 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -24,22 +24,25 @@
 import android.util.AttributeSet;
 import android.util.IntProperty;
 import android.util.Pair;
-import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.Interpolator;
+import android.widget.ScrollView;
+import android.widget.TableLayout;
+import android.widget.TableRow;
 import android.widget.TextView;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.util.WidgetsTableUtils;
 
 import java.util.List;
 
@@ -65,6 +68,8 @@
     private static final int DEFAULT_CLOSE_DURATION = 200;
     private ItemInfo mOriginalItemInfo;
     private Rect mInsets;
+    private final int mMaxTableHeight;
+    private int mMaxHorizontalSpan = 4;
 
     public WidgetsBottomSheet(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -75,12 +80,41 @@
         setWillNotDraw(false);
         mInsets = new Rect();
         mContent = this;
+        DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
+        // Set the max table height to 2 / 3 of the grid height so that the bottom picker won't
+        // take over the entire view vertically.
+        mMaxTableHeight = deviceProfile.inv.numRows * 2 / 3  * deviceProfile.cellHeightPx;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        int paddingPx = 2 * getResources().getDimensionPixelOffset(
+                R.dimen.widget_cell_horizontal_padding);
+        int maxHorizontalSpan = findViewById(R.id.widgets_table).getMeasuredWidth()
+                / (mLauncher.getDeviceProfile().cellWidthPx + paddingPx);
+
+        if (mMaxHorizontalSpan != maxHorizontalSpan) {
+            // Ensure the table layout is showing widgets in the right column after measure.
+            mMaxHorizontalSpan = maxHorizontalSpan;
+            onWidgetsBound();
+        }
     }
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
         setTranslationShift(mTranslationShift);
+
+        // Ensure the scroll view height is not larger than mMaxTableHeight, which is a value
+        // smaller than the entire screen height.
+        ScrollView widgetsTableScrollView = findViewById(R.id.widgets_table_scroll_view);
+        if (widgetsTableScrollView.getMeasuredHeight() > mMaxTableHeight) {
+            ViewGroup.LayoutParams layoutParams = widgetsTableScrollView.getLayoutParams();
+            layoutParams.height = mMaxTableHeight;
+            widgetsTableScrollView.setLayoutParams(layoutParams);
+        }
     }
 
     public void populateAndShow(ItemInfo itemInfo) {
@@ -101,39 +135,21 @@
                         mOriginalItemInfo.getTargetComponent().getPackageName(),
                         mOriginalItemInfo.user));
 
-        ViewGroup widgetRow = findViewById(R.id.widgets);
-        ViewGroup widgetCells = widgetRow.findViewById(R.id.widgets_cell_list);
+        TableLayout widgetsTable = findViewById(R.id.widgets_table);
+        widgetsTable.removeAllViews();
 
-        widgetCells.removeAllViews();
-
-        for (int i = 0; i < widgets.size(); i++) {
-            WidgetCell widget = addItemCell(widgetCells);
-            widget.applyFromCellItem(widgets.get(i), LauncherAppState.getInstance(mLauncher)
-                    .getWidgetCache());
-            widget.ensurePreview();
-            widget.setVisibility(View.VISIBLE);
-            if (i < widgets.size() - 1) {
-                addDivider(widgetCells);
-            }
-        }
-
-        if (widgets.size() == 1) {
-            // If there is only one widget, we want to center it instead of left-align.
-            WidgetsBottomSheet.LayoutParams params = (WidgetsBottomSheet.LayoutParams)
-                    widgetRow.getLayoutParams();
-            params.gravity = Gravity.CENTER_HORIZONTAL;
-        } else {
-            // Otherwise, add an empty view to the start as padding (but still scroll edge to edge).
-            View leftPaddingView = LayoutInflater.from(getContext()).inflate(
-                    R.layout.widget_list_divider, widgetRow, false);
-            leftPaddingView.getLayoutParams().width = ResourceUtils.pxFromDp(
-                    16, getResources().getDisplayMetrics());
-            widgetCells.addView(leftPaddingView, 0);
-        }
-    }
-
-    private void addDivider(ViewGroup parent) {
-        LayoutInflater.from(getContext()).inflate(R.layout.widget_list_divider, parent, true);
+        WidgetsTableUtils.groupWidgetItemsIntoTable(widgets, mMaxHorizontalSpan).forEach(row -> {
+            TableRow tableRow = new TableRow(getContext());
+            row.forEach(widgetItem -> {
+                WidgetCell widget = addItemCell(tableRow);
+                widget.setPreviewSize(widgetItem.spanX, widgetItem.spanY);
+                widget.applyFromCellItem(widgetItem, LauncherAppState.getInstance(mLauncher)
+                        .getWidgetCache());
+                widget.ensurePreview();
+                widget.setVisibility(View.VISIBLE);
+            });
+            widgetsTable.addView(tableRow);
+        });
     }
 
     protected WidgetCell addItemCell(ViewGroup parent) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 39953b1..fddb931 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -198,8 +198,9 @@
         setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
                 MeasureSpec.getSize(heightMeasureSpec));
 
-        int maxSpansPerRow = getMeasuredWidth() / (deviceProfile.cellWidthPx
-                + deviceProfile.workspaceCellPaddingXPx);
+        int paddingPx = 2 * getResources().getDimensionPixelOffset(
+                R.dimen.widget_cell_horizontal_padding);
+        int maxSpansPerRow = getMeasuredWidth() / (deviceProfile.cellWidthPx + paddingPx);
         mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
                 maxSpansPerRow);
         if (mHasWorkProfile) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index 2355700..9c8684a 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -112,7 +112,9 @@
                 TableRow row = (TableRow) table.getChildAt(i);
                 row.setVisibility(View.VISIBLE);
                 WidgetCell widget = (WidgetCell) row.getChildAt(j);
+                widget.clear();
                 WidgetItem widgetItem = widgetItemsPerRow.get(j);
+                widget.setPreviewSize(widgetItem.spanX, widgetItem.spanY);
                 widget.applyFromCellItem(widgetItem, mWidgetPreviewLoader);
                 widget.setApplyBitmapDeferred(mApplyBitmapDeferred);
                 widget.ensurePreview();