Merge "Fix state restoration ordering for ThemePicker." into ub-launcher3-qt-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0046cad..4857768 100755
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -7,6 +7,7 @@
 
     <uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.SET_WALLPAPER_COMPONENT" />
 
     <application
         android:extractNativeLibs="false"
diff --git a/res/layout/theme_color_option.xml b/res/layout/theme_color_option.xml
index 0b7ca9e..72acd8b 100644
--- a/res/layout/theme_color_option.xml
+++ b/res/layout/theme_color_option.xml
@@ -15,10 +15,10 @@
      limitations under the License.
 -->
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="match_parent"
-    android:layout_marginHorizontal="6dp"
-    android:paddingHorizontal="4dp">
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:padding="4dp">
 
     <ImageView
         android:id="@+id/option_tile"
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 465a926..30123ed 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -57,7 +57,6 @@
     <dimen name="theme_option_title_font_text_size">12sp</dimen>
 
     <dimen name="option_tile_margin_horizontal">4dp</dimen>
-    <dimen name="option_tile_margin_horizontal_ends">10dp</dimen>
     <dimen name="theme_option_label_margin">4dp</dimen>
 
     <dimen name="preview_card_corner_radius">8dp</dimen>
diff --git a/res/values/integers.xml b/res/values/integers.xml
new file mode 100644
index 0000000..6e2db17
--- /dev/null
+++ b/res/values/integers.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+    <integer name="options_grid_num_columns">4</integer>
+</resources>
diff --git a/src/com/android/customization/model/theme/DefaultThemeProvider.java b/src/com/android/customization/model/theme/DefaultThemeProvider.java
index a2b9ab5..e282cbd 100644
--- a/src/com/android/customization/model/theme/DefaultThemeProvider.java
+++ b/src/com/android/customization/model/theme/DefaultThemeProvider.java
@@ -31,15 +31,20 @@
 import static com.android.customization.model.ResourceConstants.SETTINGS_PACKAGE;
 import static com.android.customization.model.ResourceConstants.SYSUI_PACKAGE;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayManager;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
+import android.service.wallpaper.WallpaperService;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -54,12 +59,15 @@
 import com.android.customization.module.CustomizationPreferences;
 import com.android.wallpaper.R;
 import com.android.wallpaper.asset.ResourceAsset;
+import com.android.wallpaper.model.LiveWallpaperInfo;
 
 import com.bumptech.glide.request.RequestOptions;
 
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -169,26 +177,7 @@
 
                 addNoPreviewIconOverlay(builder, iconSettingsOverlayPackage);
 
-                try {
-                    String wallpaperResName = WALLPAPER_PREFIX + themeName;
-                    int wallpaperResId = mStubApkResources.getIdentifier(wallpaperResName,
-                            "drawable", mStubPackageName);
-                    if (wallpaperResId > 0) {
-                        builder.setWallpaperInfo(mStubPackageName, wallpaperResName,
-                                themeName, wallpaperResId,
-                                mStubApkResources.getIdentifier(WALLPAPER_TITLE_PREFIX + themeName,
-                                        "string", mStubPackageName),
-                                mStubApkResources.getIdentifier(
-                                        WALLPAPER_ATTRIBUTION_PREFIX + themeName, "string",
-                                        mStubPackageName),
-                                mStubApkResources.getIdentifier(WALLPAPER_ACTION_PREFIX + themeName,
-                                        "string", mStubPackageName))
-                                .setWallpaperAsset(
-                                        getDrawableResourceAsset(WALLPAPER_PREFIX, themeName));
-                    }
-                } catch (NotFoundException e) {
-                    // Nothing to do here, if there's no wallpaper we'll just omit wallpaper
-                }
+                addWallpaper(themeName, builder);
 
                 mThemes.add(builder.build(mContext));
             } catch (NameNotFoundException | NotFoundException e) {
@@ -200,6 +189,58 @@
         addCustomTheme();
     }
 
+    private void addWallpaper(String themeName, Builder builder) {
+        try {
+            String wallpaperResName = WALLPAPER_PREFIX + themeName;
+            int wallpaperResId = mStubApkResources.getIdentifier(wallpaperResName,
+                    "drawable", mStubPackageName);
+            if (wallpaperResId > 0) {
+                builder.setWallpaperInfo(mStubPackageName, wallpaperResName,
+                        themeName, wallpaperResId,
+                        mStubApkResources.getIdentifier(WALLPAPER_TITLE_PREFIX + themeName,
+                                "string", mStubPackageName),
+                        mStubApkResources.getIdentifier(
+                                WALLPAPER_ATTRIBUTION_PREFIX + themeName, "string",
+                                mStubPackageName),
+                        mStubApkResources.getIdentifier(WALLPAPER_ACTION_PREFIX + themeName,
+                                "string", mStubPackageName))
+                        .setWallpaperAsset(
+                                getDrawableResourceAsset(WALLPAPER_PREFIX, themeName));
+            } else {
+                // Try to see if it's a live wallpaper reference
+                wallpaperResId = mStubApkResources.getIdentifier(wallpaperResName,
+                        "string", mStubPackageName);
+                if (wallpaperResId > 0) {
+                    String wpComponent = mStubApkResources.getString(wallpaperResId);
+                    String[] componentParts = wpComponent.split("/");
+                    Intent liveWpIntent =  new Intent(WallpaperService.SERVICE_INTERFACE);
+                    liveWpIntent.setComponent(
+                            new ComponentName(componentParts[0],
+                                    componentParts[0] + componentParts[1]));
+                    Context appContext = mContext.getApplicationContext();
+                    PackageManager pm = appContext.getPackageManager();
+                    ResolveInfo resolveInfo =
+                            pm.resolveService(liveWpIntent, PackageManager.GET_META_DATA);
+                    if (resolveInfo != null) {
+                        android.app.WallpaperInfo wallpaperInfo;
+                        try {
+                            wallpaperInfo = new android.app.WallpaperInfo(appContext, resolveInfo);
+                            LiveWallpaperInfo liveInfo = new LiveWallpaperInfo(wallpaperInfo);
+                            builder.setLiveWallpaperInfo(liveInfo)
+                                    .setWallpaperAsset(liveInfo.getThumbAsset(mContext));
+                        } catch (XmlPullParserException e) {
+                            Log.w(TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
+                        } catch (IOException e) {
+                            Log.w(TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
+                        }
+                    }
+                }
+            }
+        } catch (NotFoundException e) {
+            // Nothing to do here, if there's no wallpaper we'll just omit wallpaper
+        }
+    }
+
     private void addColorOverlay(Builder builder, String colorOverlayPackage)
             throws NameNotFoundException {
         if (!TextUtils.isEmpty(colorOverlayPackage)) {
@@ -373,29 +414,7 @@
             addSystemDefaultIcons(builder, SYSUI_PACKAGE, ICONS_FOR_PREVIEW);
         }
 
-        try {
-            String wallpaperResName = WALLPAPER_PREFIX + DEFAULT_THEME_NAME;
-            int wallpaperResId = mStubApkResources.getIdentifier(wallpaperResName,
-                    "drawable", mStubPackageName);
-            if (wallpaperResId > 0) {
-                builder.setWallpaperInfo(mStubPackageName, wallpaperResName, DEFAULT_THEME_NAME,
-                        mStubApkResources.getIdentifier(
-                                wallpaperResName,
-                                "drawable", mStubPackageName),
-                        mStubApkResources.getIdentifier(WALLPAPER_TITLE_PREFIX + DEFAULT_THEME_NAME,
-                                "string", mStubPackageName),
-                        mStubApkResources.getIdentifier(
-                                WALLPAPER_ATTRIBUTION_PREFIX + DEFAULT_THEME_NAME, "string",
-                                mStubPackageName),
-                        mStubApkResources.getIdentifier(
-                                WALLPAPER_ACTION_PREFIX + DEFAULT_THEME_NAME,
-                                "string", mStubPackageName))
-                        .setWallpaperAsset(
-                                getDrawableResourceAsset(WALLPAPER_PREFIX, DEFAULT_THEME_NAME));
-            }
-        } catch (NotFoundException e) {
-            // Nothing to do here, if there's no wallpaper we'll just omit wallpaper
-        }
+        addWallpaper(DEFAULT_THEME_NAME, builder);
 
         mThemes.add(builder.build(mContext));
     }
diff --git a/src/com/android/customization/model/theme/ThemeBundle.java b/src/com/android/customization/model/theme/ThemeBundle.java
index e13efb5..414719e 100644
--- a/src/com/android/customization/model/theme/ThemeBundle.java
+++ b/src/com/android/customization/model/theme/ThemeBundle.java
@@ -45,12 +45,12 @@
 
 import com.android.customization.model.CustomizationManager;
 import com.android.customization.model.CustomizationOption;
-import com.android.customization.model.theme.custom.CustomTheme;
 import com.android.customization.widget.DynamicAdaptiveIconDrawable;
 import com.android.wallpaper.R;
 import com.android.wallpaper.asset.Asset;
 import com.android.wallpaper.asset.BitmapCachingAsset;
 import com.android.wallpaper.asset.ResourceAsset;
+import com.android.wallpaper.model.LiveWallpaperInfo;
 import com.android.wallpaper.model.WallpaperInfo;
 
 import org.json.JSONObject;
@@ -108,7 +108,7 @@
                     mPreviewInfo.shapeDrawable);
         }
         if (!mPreviewInfo.icons.isEmpty()) {
-            Drawable icon = mPreviewInfo.icons.get(0).mutate();
+            Drawable icon = mPreviewInfo.icons.get(0).getConstantState().newDrawable().mutate();
             icon.setTint(res.getColor(R.color.icon_thumbnail_color, null));
             ((ImageView) view.findViewById(R.id.theme_option_icon)).setImageDrawable(
                     icon);
@@ -256,7 +256,7 @@
         private PreviewInfo(Context context, Typeface bodyFontFamily, Typeface headlineFontFamily,
                 int colorAccentLight, int colorAccentDark, List<Drawable> icons,
                 Drawable shapeDrawable, @Dimension int cornerRadius,
-                @Nullable ResourceAsset wallpaperAsset, List<Drawable> shapeAppIcons) {
+                @Nullable Asset wallpaperAsset, List<Drawable> shapeAppIcons) {
             this.bodyFontFamily = bodyFontFamily;
             this.headlineFontFamily = headlineFontFamily;
             this.colorAccentLight = colorAccentLight;
@@ -291,7 +291,7 @@
         private String mShapePath;
         private boolean mIsDefault;
         @Dimension private int mCornerRadius;
-        private ResourceAsset mWallpaperAsset;
+        private Asset mWallpaperAsset;
         private WallpaperInfo mWallpaperInfo;
         protected Map<String, String> mPackages = new HashMap<>();
         private List<Drawable> mAppIcons = new ArrayList<>();
@@ -373,7 +373,13 @@
             return this;
         }
 
-        public Builder setWallpaperAsset(ResourceAsset wallpaperAsset) {
+        public Builder setLiveWallpaperInfo(LiveWallpaperInfo info) {
+            mWallpaperInfo = info;
+            return this;
+        }
+
+
+        public Builder setWallpaperAsset(Asset wallpaperAsset) {
             mWallpaperAsset = wallpaperAsset;
             return this;
         }
diff --git a/src/com/android/customization/model/theme/ThemeManager.java b/src/com/android/customization/model/theme/ThemeManager.java
index ec859bf..cd33f6b 100644
--- a/src/com/android/customization/model/theme/ThemeManager.java
+++ b/src/com/android/customization/model/theme/ThemeManager.java
@@ -27,9 +27,7 @@
 import static com.android.customization.model.ResourceConstants.SYSUI_PACKAGE;
 
 import android.graphics.Point;
-import android.os.UserHandle;
 import android.provider.Settings;
-import android.text.TextUtils;
 
 import androidx.annotation.Nullable;
 import androidx.fragment.app.FragmentActivity;
@@ -40,8 +38,6 @@
 import com.android.customization.module.ThemesUserEventLogger;
 import com.android.wallpaper.R;
 import com.android.wallpaper.asset.Asset;
-import com.android.wallpaper.module.Injector;
-import com.android.wallpaper.module.InjectorProvider;
 import com.android.wallpaper.module.WallpaperPersister;
 import com.android.wallpaper.module.WallpaperPersister.SetWallpaperCallback;
 import com.android.wallpaper.module.WallpaperSetter;
@@ -95,7 +91,8 @@
         // Set wallpaper
         if (theme.shouldUseThemeWallpaper()) {
             mWallpaperSetter.requestDestination(mActivity, mActivity.getSupportFragmentManager(),
-                    R.string.set_theme_wallpaper_dialog_message, new Listener() {
+                    R.string.set_theme_wallpaper_dialog_message, theme.getWallpaperInfo(),
+                    new Listener() {
                         @Override
                         public void onSetHomeScreen() {
                             applyWallpaper(theme, WallpaperPersister.DEST_HOME_SCREEN,
@@ -140,19 +137,27 @@
                 mActivity.getResources(),
                 mActivity.getWindowManager().getDefaultDisplay());
         Asset wallpaperAsset = theme.getWallpaperInfo().getAsset(mActivity);
-        wallpaperAsset.decodeRawDimensions(mActivity,
-                dimensions -> {
-                    float scale = 1f;
-                    // Calculate scale to fit the screen height
-                    if (dimensions != null && dimensions.y > 0) {
-                        scale = (float) defaultCropSurfaceSize.y / dimensions.y;
-                    }
-                    mWallpaperSetter.setCurrentWallpaper(mActivity,
-                            theme.getWallpaperInfo(),
-                            wallpaperAsset,
-                            destination,
-                            scale, null, callback);
-                });
+        if (wallpaperAsset != null) {
+            wallpaperAsset.decodeRawDimensions(mActivity,
+                    dimensions -> {
+                        float scale = 1f;
+                        // Calculate scale to fit the screen height
+                        if (dimensions != null && dimensions.y > 0) {
+                            scale = (float) defaultCropSurfaceSize.y / dimensions.y;
+                        }
+                        mWallpaperSetter.setCurrentWallpaper(mActivity,
+                                theme.getWallpaperInfo(),
+                                wallpaperAsset,
+                                destination,
+                                scale, null, callback);
+                    });
+        } else {
+            mWallpaperSetter.setCurrentWallpaper(mActivity,
+                    theme.getWallpaperInfo(),
+                    null,
+                    destination,
+                    1f, null, callback);
+        }
     }
 
     private void applyOverlays(ThemeBundle theme, Callback callback) {
diff --git a/src/com/android/customization/picker/theme/CustomThemeActivity.java b/src/com/android/customization/picker/theme/CustomThemeActivity.java
index df14c86..40d1fe8 100644
--- a/src/com/android/customization/picker/theme/CustomThemeActivity.java
+++ b/src/com/android/customization/picker/theme/CustomThemeActivity.java
@@ -339,7 +339,8 @@
             return CustomThemeComponentFragment.newInstance(
                     CustomThemeActivity.this.getString(R.string.custom_theme_fragment_title),
                     position,
-                    titleResId);
+                    titleResId,
+                    true);
         }
     }
 
diff --git a/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java b/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
index f2088ae..cd2c067 100644
--- a/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
+++ b/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
@@ -40,6 +40,7 @@
 public class CustomThemeComponentFragment extends ToolbarFragment {
     private static final String ARG_KEY_POSITION = "CustomThemeComponentFragment.position";
     private static final String ARG_KEY_TITLE_RES_ID = "CustomThemeComponentFragment.title_res";
+    private static final String ARG_USE_GRID_LAYOUT = "CustomThemeComponentFragment.use_grid";;
     private CustomThemeComponentFragmentHost mHost;
 
     public interface CustomThemeComponentFragmentHost {
@@ -55,10 +56,16 @@
 
     public static CustomThemeComponentFragment newInstance(CharSequence toolbarTitle, int position,
             int titleResId) {
+        return newInstance(toolbarTitle, position, titleResId, false);
+    }
+
+    public static CustomThemeComponentFragment newInstance(CharSequence toolbarTitle, int position,
+            int titleResId, boolean allowGridLayout) {
         CustomThemeComponentFragment fragment = new CustomThemeComponentFragment();
         Bundle arguments = ToolbarFragment.createArguments(toolbarTitle);
         arguments.putInt(ARG_KEY_POSITION, position);
         arguments.putInt(ARG_KEY_TITLE_RES_ID, titleResId);
+        arguments.putBoolean(ARG_USE_GRID_LAYOUT, allowGridLayout);
         fragment.setArguments(arguments);
         return fragment;
     }
@@ -67,6 +74,7 @@
     private CustomThemeManager mCustomThemeManager;
     private int mPosition;
     @StringRes private int mTitleResId;
+    private boolean mUseGridLayout;
 
     private RecyclerView mOptionsContainer;
     private OptionSelectorController<ThemeComponentOption> mOptionsController;
@@ -79,6 +87,7 @@
         super.onCreate(savedInstanceState);
         mPosition = getArguments().getInt(ARG_KEY_POSITION);
         mTitleResId = getArguments().getInt(ARG_KEY_TITLE_RES_ID);
+        mUseGridLayout = getArguments().getBoolean(ARG_USE_GRID_LAYOUT);
         mProvider = mHost.getComponentOptionProvider(mPosition);
         mCustomThemeManager = mHost.getCustomThemeManager();
     }
@@ -144,7 +153,8 @@
 
     private void setUpOptions() {
         mProvider.fetch(options -> {
-            mOptionsController = new OptionSelectorController(mOptionsContainer, options);
+            mOptionsController = new OptionSelectorController(
+                    mOptionsContainer, options, mUseGridLayout, false);
 
             mOptionsController.addListener(selected -> {
                 mSelectedOption = (ThemeComponentOption) selected;
diff --git a/src/com/android/customization/picker/theme/ThemeFragment.java b/src/com/android/customization/picker/theme/ThemeFragment.java
index 7e1982a..7f6754f 100644
--- a/src/com/android/customization/picker/theme/ThemeFragment.java
+++ b/src/com/android/customization/picker/theme/ThemeFragment.java
@@ -384,7 +384,8 @@
             ViewGroup iconsContainer = card.findViewById(R.id.theme_preview_top_bar_icons);
 
             for (int i = 0; i < iconsContainer.getChildCount() && i < mIcons.size(); i++) {
-                ((ImageView)iconsContainer.getChildAt(i)).setImageDrawable(mIcons.get(i));
+                ((ImageView) iconsContainer.getChildAt(i))
+                        .setImageDrawable(mIcons.get(i).getConstantState().newDrawable().mutate());
             }
 
             ViewGroup body = card.findViewById(R.id.theme_preview_card_body_container);
@@ -503,7 +504,7 @@
                     }
                     for (int i = 0; i < 3 && i < previewInfo.icons.size(); i++) {
                         Drawable icon =
-                                previewInfo.icons.get(i).getConstantState().newDrawable();
+                                previewInfo.icons.get(i).getConstantState().newDrawable().mutate();
                         Drawable bgShape =
                                 previewInfo.shapeDrawable.getConstantState().newDrawable();
                         bgShape.setTint(accentColor);
@@ -548,8 +549,9 @@
                     @Override
                     protected void bindBody(boolean forceRebind) {
                         for (int i = 0; i < mIconIds.length && i < previewInfo.icons.size(); i++) {
-                            ((ImageView) card.findViewById(mIconIds[i])).setImageDrawable(
-                                    previewInfo.icons.get(i));
+                            ((ImageView) card.findViewById(mIconIds[i]))
+                                    .setImageDrawable(previewInfo.icons.get(i)
+                                            .getConstantState().newDrawable().mutate());
                         }
                     }
                 });
@@ -601,10 +603,10 @@
 
                         for (int i = 0; i < mColorTileIds.length && i < previewInfo.icons.size();
                                 i++) {
-                            Drawable icon =
-                                previewInfo.icons.get(i).getConstantState().newDrawable();
+                            Drawable icon = previewInfo.icons.get(i)
+                                    .getConstantState().newDrawable().mutate();
                             Drawable bgShape =
-                                previewInfo.shapeDrawable.getConstantState().newDrawable();
+                                    previewInfo.shapeDrawable.getConstantState().newDrawable();
                             bgShape.setTint(accentColor);
 
                             ImageView bg = card.findViewById(mColorTileIds[i]);
diff --git a/src/com/android/customization/widget/HorizontalSpacerItemDecoration.java b/src/com/android/customization/widget/HorizontalSpacerItemDecoration.java
index f17d52f..df38e6e 100644
--- a/src/com/android/customization/widget/HorizontalSpacerItemDecoration.java
+++ b/src/com/android/customization/widget/HorizontalSpacerItemDecoration.java
@@ -11,24 +11,22 @@
 
 /**
  * RecyclerView ItemDecorator that adds a horizontal space of the given size between items
- * (except the first and last)
+ * and double that space on the ends.
  */
 public class HorizontalSpacerItemDecoration extends ItemDecoration {
 
     private final int mOffset;
-    private final int mEndOffset;
 
-    public HorizontalSpacerItemDecoration(@Dimension int offset, @Dimension int endOffset) {
+    public HorizontalSpacerItemDecoration(@Dimension int offset) {
         mOffset = offset;
-        mEndOffset = endOffset;
     }
 
     @Override
     public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
             @NonNull RecyclerView parent, @NonNull State state) {
         int position = parent.getChildAdapterPosition(view);
-        int left = position == 0 ? mEndOffset : mOffset;
-        int right = (position == parent.getAdapter().getItemCount() - 1) ? mEndOffset : mOffset;
+        int left = position == 0 ? mOffset * 2: mOffset;
+        int right = (position == parent.getAdapter().getItemCount() - 1) ? mOffset * 2 : mOffset;
         outRect.set(left, 0, right, 0);
     }
 }
diff --git a/src/com/android/customization/widget/OptionSelectorController.java b/src/com/android/customization/widget/OptionSelectorController.java
index 615d7d8..b311abe 100644
--- a/src/com/android/customization/widget/OptionSelectorController.java
+++ b/src/com/android/customization/widget/OptionSelectorController.java
@@ -18,18 +18,22 @@
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
+import android.util.DisplayMetrics;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.customization.model.CustomizationManager;
 import com.android.customization.model.CustomizationOption;
+import com.android.customization.model.theme.custom.ThemeComponentOption;
 import com.android.wallpaper.R;
 
 import java.util.HashSet;
@@ -49,6 +53,7 @@
      * Interface to be notified when an option is selected by the user.
      */
     public interface OptionSelectedListener {
+
         /**
          * Called when an option has been selected (and marked as such in the UI)
          */
@@ -57,6 +62,8 @@
 
     private final RecyclerView mContainer;
     private final List<T> mOptions;
+    private final boolean mUseGrid;
+    private final boolean mShowCheckmark;
 
     private final Set<OptionSelectedListener> mListeners = new HashSet<>();
     private RecyclerView.Adapter<TileViewHolder> mAdapter;
@@ -64,8 +71,15 @@
     private CustomizationOption mAppliedOption;
 
     public OptionSelectorController(RecyclerView container, List<T> options) {
+        this(container, options, false, true);
+    }
+
+    public OptionSelectorController(RecyclerView container, List<T> options,
+            boolean useGrid, boolean showCheckmark) {
         mContainer = container;
         mOptions = options;
+        mUseGrid = useGrid;
+        mShowCheckmark = showCheckmark;
     }
 
     public void addListener(OptionSelectedListener listener) {
@@ -140,7 +154,7 @@
                 holder.itemView.setActivated(option.equals(mSelectedOption));
                 holder.itemView.setOnClickListener(view -> setSelectedOption(option));
 
-                if (option.equals(mAppliedOption)) {
+                if (mShowCheckmark && option.equals(mAppliedOption)) {
                     Resources res = mContainer.getContext().getResources();
                     Drawable checkmark = res.getDrawable(R.drawable.ic_check_circle_filled_24px);
                     Drawable frame = holder.itemView.getForeground();
@@ -171,10 +185,38 @@
         mContainer.setLayoutManager(new LinearLayoutManager(mContainer.getContext(),
                 LinearLayoutManager.HORIZONTAL, false));
         Resources res = mContainer.getContext().getResources();
-        mContainer.addItemDecoration(new HorizontalSpacerItemDecoration(
-                res.getDimensionPixelOffset(R.dimen.option_tile_margin_horizontal),
-                res.getDimensionPixelOffset(R.dimen.option_tile_margin_horizontal_ends)));
         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);
+        DisplayMetrics metrics = new DisplayMetrics();
+        // TODO: retrieve fixed container width for landscape
+        mContainer.getContext().getSystemService(WindowManager.class)
+                .getDefaultDisplay().getMetrics(metrics);
+        int totalWidth = mContainer.getMeasuredWidth();
+
+        if (mUseGrid) {
+            int numColumns = res.getInteger(R.integer.options_grid_num_columns);
+            int widthPerItem = totalWidth / mAdapter.getItemCount();
+            int extraSpace = metrics.widthPixels - widthPerItem * numColumns;
+            int containerSidePadding = extraSpace / (numColumns + 1);
+            mContainer.setLayoutManager(new GridLayoutManager(mContainer.getContext(), numColumns));
+            mContainer.setPaddingRelative(containerSidePadding, 0, containerSidePadding, 0);
+            mContainer.setOverScrollMode(View.OVER_SCROLL_NEVER);
+            return;
+        }
+
+        int extraSpace = metrics.widthPixels - totalWidth;
+        if (extraSpace >= 0) {
+            mContainer.setOverScrollMode(View.OVER_SCROLL_NEVER);
+        }
+        int itemSideMargin =  res.getDimensionPixelOffset(R.dimen.option_tile_margin_horizontal);
+        int defaultTotalPadding = itemSideMargin * (mAdapter.getItemCount() * 2 + 2);
+        if (extraSpace > defaultTotalPadding) {
+            int spaceBetweenItems = extraSpace / (mAdapter.getItemCount() + 1);
+            itemSideMargin = spaceBetweenItems / 2;
+        }
+        mContainer.addItemDecoration(new HorizontalSpacerItemDecoration(itemSideMargin));
     }
 
     public void resetOptions(List<T> options) {