Fixes spacing of color options in wallpaper picker.

This eliminates overlap and clipping of color option backgrounds, and
also adds vertical spacing between rows if the options spill into a
second row.

Fixes: 227690986
Fixes: 240448797
Fixes: 231414521
Test: Passes TreeHugger
Change-Id: Ia3a2a1c7ae27d9afdf446226c83845be2221f432
diff --git a/src/com/android/customization/widget/OptionSelectorController.java b/src/com/android/customization/widget/OptionSelectorController.java
index a66dd44..33d2198 100644
--- a/src/com/android/customization/widget/OptionSelectorController.java
+++ b/src/com/android/customization/widget/OptionSelectorController.java
@@ -45,6 +45,7 @@
 import com.android.customization.model.CustomizationOption;
 import com.android.wallpaper.R;
 import com.android.wallpaper.widget.GridPaddingDecoration;
+import com.android.wallpaper.widget.GridRowSpacerDecoration;
 
 import java.util.HashSet;
 import java.util.List;
@@ -84,7 +85,8 @@
     private final RecyclerView mContainer;
     private final List<T> mOptions;
     private final boolean mUseGrid;
-    @CheckmarkStyle private final int mCheckmarkStyle;
+    @CheckmarkStyle
+    private final int mCheckmarkStyle;
 
     private final Set<OptionSelectedListener> mListeners = new HashSet<>();
     private RecyclerView.Adapter<TileViewHolder> mAdapter;
@@ -141,7 +143,6 @@
     /**
      * Mark an option as the one which is currently applied on the device. This will result in a
      * check being displayed in the lower-right corner of the corresponding ViewHolder.
-     * @param option
      */
     public void setAppliedOption(T option) {
         if (!mOptions.contains(option)) {
@@ -157,6 +158,7 @@
 
     /**
      * Notify that a given option has changed.
+     *
      * @param option the option that changed
      */
     public void optionChanged(T option) {
@@ -217,7 +219,7 @@
                                     mContainer.getContext().getTheme()),
                             Gravity.CENTER, res.getDimensionPixelSize(R.dimen.center_check_size),
                             0, true);
-                }  else if (mCheckmarkStyle == CheckmarkStyle.CENTER_CHANGE_COLOR_WHEN_NOT_SELECTED
+                } else if (mCheckmarkStyle == CheckmarkStyle.CENTER_CHANGE_COLOR_WHEN_NOT_SELECTED
                         && option.equals(mAppliedOption)) {
                     int drawableRes = option.equals(mSelectedOption)
                             ? R.drawable.check_circle_grey_large
@@ -230,7 +232,7 @@
                 } else if (option.equals(mAppliedOption)) {
                     // Initialize with "previewed" description if we don't show checkmark
                     holder.setContentDescription(mContainer.getContext(), option,
-                        R.string.option_previewed_description);
+                            R.string.option_previewed_description);
                 } else if (mCheckmarkStyle != CheckmarkStyle.NONE) {
                     if (mCheckmarkStyle == CheckmarkStyle.CENTER_CHANGE_COLOR_WHEN_NOT_SELECTED) {
                         if (option.equals(mSelectedOption)) {
@@ -282,72 +284,67 @@
         };
 
         Resources res = mContainer.getContext().getResources();
-        if (mUseGrid) {
-            mContainer.setLayoutManager(new GridLayoutManager(mContainer.getContext(),
-                    res.getInteger(R.integer.options_grid_num_columns)));
-        } else {
-            mContainer.setLayoutManager(new LinearLayoutManager(mContainer.getContext(),
-                    LinearLayoutManager.HORIZONTAL, false));
-        }
-
         mContainer.setAdapter(mAdapter);
-
-        // Measure RecyclerView to get to the total amount of space used by all options.
-        mContainer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
-        int fixWidth = res.getDimensionPixelSize(R.dimen.options_container_width);
-        int availableWidth;
-        if (fixWidth == 0) {
-            DisplayMetrics metrics = new DisplayMetrics();
-            mContainer.getContext().getSystemService(WindowManager.class)
-                    .getDefaultDisplay().getMetrics(metrics);
-            availableWidth = metrics.widthPixels;
-        } else {
-            availableWidth = fixWidth;
-        }
-        int totalWidth = mContainer.getMeasuredWidth();
-        int widthPerItem = res.getDimensionPixelOffset(R.dimen.option_tile_width);
+        final int padding = res.getDimensionPixelSize(
+                R.dimen.option_tile_grid_padding_horizontal);
+        final int fixWidth = res.getDimensionPixelSize(R.dimen.options_container_width);
+        final DisplayMetrics metrics = new DisplayMetrics();
+        mContainer.getContext().getSystemService(WindowManager.class)
+                .getDefaultDisplay().getMetrics(metrics);
+        // This is based on the assumption that the parent view is the same width as the screen.
+        final int availableDynamicWidth = metrics.widthPixels - 2 * res.getDimensionPixelSize(
+                R.dimen.section_horizontal_padding) - 2 * padding;
+        final int availableWidth = (fixWidth != 0) ? fixWidth : availableDynamicWidth;
+        final boolean hasDecoration = mContainer.getItemDecorationCount() != 0;
+        final int widthPerItem = res.getDimensionPixelSize(R.dimen.option_tile_width) + (
+                hasDecoration ? 0 : 2 * padding);
 
         if (mUseGrid) {
             int numColumns = res.getInteger(R.integer.options_grid_num_columns);
-            int extraSpace = availableWidth - widthPerItem * numColumns;
-            while (extraSpace < 0) {
+            GridLayoutManager gridLayoutManager = new GridLayoutManager(mContainer.getContext(),
+                    numColumns);
+            mContainer.setLayoutManager(gridLayoutManager);
+            // Measure RecyclerView to get to the total amount of space used by all options.
+            mContainer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+            while (mContainer.getMeasuredWidth() > availableWidth && numColumns > 1) {
                 numColumns -= 1;
-                extraSpace = availableWidth - widthPerItem * numColumns;
+                gridLayoutManager.setSpanCount(numColumns);
+                mContainer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
             }
 
-            if (mContainer.getLayoutManager() != null) {
-                ((GridLayoutManager) mContainer.getLayoutManager()).setSpanCount(numColumns);
+            if (!hasDecoration) {
+                mContainer.addItemDecoration(new GridPaddingDecoration(padding, 0));
+                if (numColumns > 1) {
+                    mContainer.addItemDecoration(new GridRowSpacerDecoration(2 * padding));
+                }
             }
-            if (mContainer.getItemDecorationCount() == 0) {
-                mContainer.addItemDecoration(new GridPaddingDecoration(
-                        mContainer.getContext().getResources().getDimensionPixelSize(
-                                R.dimen.option_tile_grid_padding_horizontal), 0));
+        } else {
+            mContainer.setLayoutManager(new LinearLayoutManager(mContainer.getContext(),
+                    LinearLayoutManager.HORIZONTAL, false));
+            int extraSpace = availableWidth - mContainer.getMeasuredWidth();
+            if (extraSpace >= 0) {
+                mContainer.setOverScrollMode(View.OVER_SCROLL_NEVER);
             }
-            return;
+
+            if (mAdapter.getItemCount() >= mLinearLayoutHorizontalDisplayOptionsMax) {
+                int spaceBetweenItems = availableWidth
+                        - Math.round(widthPerItem * mLinearLayoutHorizontalDisplayOptionsMax)
+                        - mContainer.getPaddingLeft();
+                int itemEndMargin =
+                        spaceBetweenItems / (int) mLinearLayoutHorizontalDisplayOptionsMax;
+                if (itemEndMargin <= 0) {
+                    itemEndMargin = res.getDimensionPixelOffset(
+                            R.dimen.option_tile_margin_horizontal);
+                }
+                mContainer.addItemDecoration(new ItemEndHorizontalSpaceItemDecoration(
+                        mContainer.getContext(), itemEndMargin));
+                return;
+            }
+
+            int spaceBetweenItems = extraSpace / (mAdapter.getItemCount() + 1);
+            int itemSideMargin = spaceBetweenItems / 2;
+            mContainer.addItemDecoration(new HorizontalSpacerItemDecoration(itemSideMargin));
         }
-
-        int extraSpace = availableWidth - totalWidth;
-        if (extraSpace >= 0) {
-            mContainer.setOverScrollMode(View.OVER_SCROLL_NEVER);
-        }
-
-        if (mAdapter.getItemCount() >= mLinearLayoutHorizontalDisplayOptionsMax) {
-            int spaceBetweenItems = availableWidth
-                    - Math.round(widthPerItem * mLinearLayoutHorizontalDisplayOptionsMax)
-                    - mContainer.getPaddingLeft();
-            int itemEndMargin =
-                    spaceBetweenItems / (int) mLinearLayoutHorizontalDisplayOptionsMax;
-            if (itemEndMargin <= 0) {
-                itemEndMargin = res.getDimensionPixelOffset(R.dimen.option_tile_margin_horizontal);
-            }
-            mContainer.addItemDecoration(new ItemEndHorizontalSpaceItemDecoration(
-                    mContainer.getContext(), itemEndMargin));
-            return;
-        }
-
-        int spaceBetweenItems = extraSpace / (mAdapter.getItemCount() + 1);
-        int itemSideMargin = spaceBetweenItems / 2;
-        mContainer.addItemDecoration(new HorizontalSpacerItemDecoration(itemSideMargin));
     }
 
     public void resetOptions(List<T> options) {
@@ -382,9 +379,10 @@
         /**
          * Set the content description for this holder using the given string id.
          * If the option does not have a label, the description will be set on the tile view.
+         *
          * @param context The view's context
-         * @param option The customization option
-         * @param id Resource ID of the string to use for the content description
+         * @param option  The customization option
+         * @param id      Resource ID of the string to use for the content description
          */
         public void setContentDescription(Context context, CustomizationOption<?> option, int id) {
             title = option.getTitle();