Move color picking code into AOSP

Add color picking related code, resources and libraries.

Bug: 218396282
Test: manual
Change-Id: I6aa244c8dac69f4663f97e19e91cfb6cf3eb0990
diff --git a/Android.bp b/Android.bp
index 57d30ce..a8d561e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -69,6 +69,8 @@
     name: "ThemePicker",
 
     static_libs: [
+        "guava",
+        "monet",
         "wallpaper-common-deps",
         "SettingsLibSettingsTheme",
         "SystemUI-statsd",
diff --git a/res/drawable/color_chip_seed_filled0.xml b/res/drawable/color_chip_seed_filled0.xml
new file mode 100644
index 0000000..a93bc6b
--- /dev/null
+++ b/res/drawable/color_chip_seed_filled0.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+     Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="rectangle">
+            <corners
+                android:topLeftRadius="@dimen/component_color_chip_small_size_default" />
+            <size
+                android:width="@dimen/component_color_chip_small_size_default"
+                android:height="@dimen/component_color_chip_small_size_default" />
+            <solid android:color="@android:color/black" />
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/res/drawable/color_chip_seed_filled1.xml b/res/drawable/color_chip_seed_filled1.xml
new file mode 100644
index 0000000..a0672bc
--- /dev/null
+++ b/res/drawable/color_chip_seed_filled1.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+     Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+         <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/component_color_chip_small_size_default" />
+            <size
+                android:width="@dimen/component_color_chip_small_size_default"
+                android:height="@dimen/component_color_chip_small_size_default" />
+            <solid android:color="@android:color/black" />
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/res/drawable/color_chip_seed_filled2.xml b/res/drawable/color_chip_seed_filled2.xml
new file mode 100644
index 0000000..545fbdd
--- /dev/null
+++ b/res/drawable/color_chip_seed_filled2.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+     Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="rectangle">
+            <corners
+                android:topRightRadius="@dimen/component_color_chip_small_size_default" />
+            <size
+                android:width="@dimen/component_color_chip_small_size_default"
+                android:height="@dimen/component_color_chip_small_size_default" />
+            <solid android:color="@android:color/black" />
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/res/drawable/color_chip_seed_filled3.xml b/res/drawable/color_chip_seed_filled3.xml
new file mode 100644
index 0000000..0e286a5
--- /dev/null
+++ b/res/drawable/color_chip_seed_filled3.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+     Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomRightRadius="@dimen/component_color_chip_small_size_default" />
+            <size
+                android:width="@dimen/component_color_chip_small_size_default"
+                android:height="@dimen/component_color_chip_small_size_default" />
+            <solid android:color="@android:color/black" />
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/res/layout/color_option.xml b/res/layout/color_option.xml
new file mode 100644
index 0000000..9bbfd7f
--- /dev/null
+++ b/res/layout/color_option.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:orientation="vertical">
+
+    <FrameLayout
+        android:id="@+id/option_tile"
+        android:layout_width="@dimen/option_tile_width"
+        android:layout_height="@dimen/option_tile_width"
+        android:layout_gravity="center_horizontal"
+        android:background="@drawable/option_border_color">
+        <ImageView
+            android:id="@+id/color_preview_0"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginRight="@dimen/color_seed_chip_margin"
+            android:layout_marginBottom="@dimen/color_seed_chip_margin"
+            android:src="@drawable/color_chip_seed_filled0"/>
+        <ImageView
+            android:id="@+id/color_preview_1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginLeft="@dimen/color_seed_chip_margin"
+            android:layout_marginBottom="@dimen/color_seed_chip_margin"
+            android:src="@drawable/color_chip_seed_filled2"/>
+        <ImageView
+            android:id="@+id/color_preview_2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginRight="@dimen/color_seed_chip_margin"
+            android:layout_marginTop="@dimen/color_seed_chip_margin"
+            android:src="@drawable/color_chip_seed_filled1"/>
+        <ImageView
+            android:id="@+id/color_preview_3"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginLeft="@dimen/color_seed_chip_margin"
+            android:layout_marginTop="@dimen/color_seed_chip_margin"
+            android:src="@drawable/color_chip_seed_filled3"/>
+    </FrameLayout>
+</FrameLayout>
diff --git a/res/layout/color_options_view.xml b/res/layout/color_options_view.xml
new file mode 100644
index 0000000..65028a6
--- /dev/null
+++ b/res/layout/color_options_view.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/color_option_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:orientation="horizontal" />
+</FrameLayout>
diff --git a/res/layout/color_pages_view.xml b/res/layout/color_pages_view.xml
new file mode 100644
index 0000000..3ccbd41
--- /dev/null
+++ b/res/layout/color_pages_view.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <androidx.viewpager2.widget.ViewPager2
+        android:id="@+id/color_page_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"/>
+    <com.android.wallpaper.widget.PageIndicator
+        android:id="@+id/color_page_indicator"
+        android:layout_marginTop="@dimen/color_page_indicator_margin_top"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        android:layout_gravity="center"/>
+</LinearLayout>
diff --git a/res/layout/color_section_view.xml b/res/layout/color_section_view.xml
new file mode 100644
index 0000000..bd892f7
--- /dev/null
+++ b/res/layout/color_section_view.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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.
+-->
+<com.android.customization.picker.color.ColorSectionView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingBottom="@dimen/section_bottom_padding"
+    android:orientation="vertical">
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingHorizontal="@dimen/separated_tabs_horizontal_padding">
+        <include layout="@layout/separated_tabs" />
+    </FrameLayout>
+
+    <androidx.viewpager2.widget.ViewPager2
+        android:id="@+id/color_section_view_pager"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/color_options_container_top_margin"
+        android:paddingHorizontal="@dimen/section_horizontal_padding"
+        android:clipChildren="false"
+        android:clipToPadding="false"/>
+</com.android.customization.picker.color.ColorSectionView>
diff --git a/res/values-w600dp-port/dimens.xml b/res/values-w600dp-port/dimens.xml
index 1be52bb..ba92746 100644
--- a/res/values-w600dp-port/dimens.xml
+++ b/res/values-w600dp-port/dimens.xml
@@ -18,4 +18,7 @@
     <!-- Dimensions for the customization option tiles -->
     <dimen name="option_tile_width">79dp</dimen>
     <dimen name="option_tile_grid_padding_horizontal">8dp</dimen>
-</resources>
\ No newline at end of file
+
+    <dimen name="component_color_chip_small_size_default">30dp</dimen>
+    <dimen name="color_seed_chip_margin">15dp</dimen>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 4aa43b4..0ad221e 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -124,4 +124,15 @@
 
     <dimen name="beta_tag_background_width">46dp</dimen>
     <dimen name="beta_tag_background_height">24dp</dimen>
+
+    <!-- For the color option section -->
+    <dimen name="color_options_container_top_margin">24dp</dimen>
+
+    <!-- For the color page. -->
+    <dimen name="color_page_indicator_margin_top">16dp</dimen>
+
+    <dimen name="component_color_chip_small_size_default">29dp</dimen>
+    <dimen name="color_seed_option_tile_padding">10dp</dimen>
+    <dimen name="color_seed_option_tile_padding_selected">6dp</dimen>
+    <dimen name="color_seed_chip_margin">14dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f2d0f21..1a15fd0 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -247,4 +247,17 @@
 
     <!-- The content description of grid picker entry. [CHAR LIMIT=NONE] -->
     <string name="gird_picker_entry_content_description" msgid="7538418512525897691">Change app grid</string>
+
+    <!-- The title for the tab with colors from wallpaper. [CHAR_LIMIT=20]-->
+    <string name="wallpaper_color_tab">Wallpaper colors</string>
+    <!-- The description on an item that shows a color obtained from the wallpaper
+        (used mainly for accessibility). [CHAR_LIMIT=NONE] -->
+    <string name="wallpaper_color_title">Wallpaper color</string>
+    <!-- The title for the tab with a default set of color options. [CHAR_LIMIT=20]-->
+    <string name="preset_color_tab">Basic colors</string>
+    <!-- The text for A11y announcement when color changes. -->
+    <string name="color_changed">Color changed</string>
+    <!-- Title of a section of color selection option that obtains colors automatically from the
+        wallpaper instead of a set color [CHAR LIMIT=15] -->
+    <string name="adaptive_color_title">Dynamic</string>
 </resources>
diff --git a/src/com/android/customization/model/ResourceConstants.java b/src/com/android/customization/model/ResourceConstants.java
index 293feb8..aaee935 100644
--- a/src/com/android/customization/model/ResourceConstants.java
+++ b/src/com/android/customization/model/ResourceConstants.java
@@ -90,6 +90,14 @@
             "ic_battery_80_24dp"
     };
 
+    /**
+     * Color bundle strings used to reference system resources.
+     */
+    String COLOR_BUNDLES_ARRAY_NAME = "color_bundles";
+    String COLOR_BUNDLE_NAME_PREFIX = "bundle_name_";
+    String COLOR_BUNDLE_MAIN_COLOR_PREFIX = "color_secondary_";
+    String COLOR_BUNDLE_STYLE_PREFIX = "color_style_";
+
     ArrayList<String> sTargetPackages = new ArrayList<>();
     String ACCENT_COLOR_LIGHT_NAME = "accent_device_default_light";
     String ACCENT_COLOR_DARK_NAME = "accent_device_default_dark";
diff --git a/src/com/android/customization/model/color/ColorBundle.java b/src/com/android/customization/model/color/ColorBundle.java
new file mode 100644
index 0000000..dc5a367
--- /dev/null
+++ b/src/com/android/customization/model/color/ColorBundle.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2022 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.customization.model.color;
+
+import static com.android.customization.model.ResourceConstants.PATH_SIZE;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.PathShape;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.Dimension;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.core.graphics.PathParser;
+
+import com.android.customization.model.ResourceConstants;
+import com.android.systemui.monet.Style;
+import com.android.wallpaper.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents a preset color available for the user to chose as their theming option.
+ */
+public class ColorBundle extends ColorOption {
+
+    private final PreviewInfo mPreviewInfo;
+
+    @VisibleForTesting ColorBundle(String title,
+            Map<String, String> overlayPackages, boolean isDefault, Style style, int index,
+            PreviewInfo previewInfo) {
+        super(title, overlayPackages, isDefault, style, index);
+        mPreviewInfo = previewInfo;
+    }
+
+    @Override
+    public void bindThumbnailTile(View view) {
+        Resources res = view.getContext().getResources();
+        int primaryColor = mPreviewInfo.resolvePrimaryColor(res);
+        int secondaryColor = mPreviewInfo.resolveSecondaryColor(res);
+        int padding = view.isActivated()
+                ? res.getDimensionPixelSize(R.dimen.color_seed_option_tile_padding_selected)
+                : res.getDimensionPixelSize(R.dimen.color_seed_option_tile_padding);
+
+        for (int i = 0; i < mPreviewColorIds.length; i++) {
+            ImageView colorPreviewImageView = view.findViewById(mPreviewColorIds[i]);
+            int color = i % 2 == 0 ? primaryColor : secondaryColor;
+            colorPreviewImageView.getDrawable().setColorFilter(color, PorterDuff.Mode.SRC);
+            colorPreviewImageView.setPadding(padding, padding, padding, padding);
+        }
+        view.setContentDescription(getContentDescription(view.getContext()));
+    }
+
+    @Override
+    public PreviewInfo getPreviewInfo() {
+        return mPreviewInfo;
+    }
+
+    @Override
+    public int getLayoutResId() {
+        return R.layout.color_option;
+    }
+
+    @Override
+    public String getSource() {
+        return ColorOptionsProvider.COLOR_SOURCE_PRESET;
+    }
+
+    /**
+     * The preview information of {@link ColorBundle}
+     */
+    public static class PreviewInfo implements ColorOption.PreviewInfo {
+        @ColorInt
+        public final int secondaryColorLight;
+        @ColorInt public final int secondaryColorDark;
+        // Monet system palette and accent colors
+        @ColorInt public final int primaryColorLight;
+        @ColorInt public final int primaryColorDark;
+        public final List<Drawable> icons;
+        public final Drawable shapeDrawable;
+        @Dimension
+        public final int bottomSheetCornerRadius;
+
+        @ColorInt private int mOverrideSecondaryColorLight = Color.TRANSPARENT;
+        @ColorInt private int mOverrideSecondaryColorDark = Color.TRANSPARENT;
+        @ColorInt private int mOverridePrimaryColorLight = Color.TRANSPARENT;
+        @ColorInt private int mOverridePrimaryColorDark = Color.TRANSPARENT;
+
+        private PreviewInfo(
+                int secondaryColorLight, int secondaryColorDark, int colorSystemPaletteLight,
+                int primaryColorDark, List<Drawable> icons, Drawable shapeDrawable,
+                @Dimension int cornerRadius) {
+            this.secondaryColorLight = secondaryColorLight;
+            this.secondaryColorDark = secondaryColorDark;
+            this.primaryColorLight = colorSystemPaletteLight;
+            this.primaryColorDark = primaryColorDark;
+            this.icons = icons;
+            this.shapeDrawable = shapeDrawable;
+            this.bottomSheetCornerRadius = cornerRadius;
+        }
+
+        /**
+         * Returns the accent color to be applied corresponding with the current configuration's
+         * UI mode.
+         * @return one of {@link #secondaryColorDark} or {@link #secondaryColorLight}
+         */
+        @ColorInt
+        public int resolveSecondaryColor(Resources res) {
+            boolean night = (res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                    == Configuration.UI_MODE_NIGHT_YES;
+            if (mOverrideSecondaryColorDark != Color.TRANSPARENT
+                    || mOverrideSecondaryColorLight != Color.TRANSPARENT) {
+                return night ? mOverrideSecondaryColorDark : mOverrideSecondaryColorLight;
+            }
+            return night ? secondaryColorDark : secondaryColorLight;
+        }
+
+        /**
+         * Returns the palette (main) color to be applied corresponding with the current
+         * configuration's UI mode.
+         * @return one of {@link #secondaryColorDark} or {@link #secondaryColorLight}
+         */
+        @ColorInt
+        public int resolvePrimaryColor(Resources res) {
+            boolean night = (res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                    == Configuration.UI_MODE_NIGHT_YES;
+            if (mOverridePrimaryColorDark != Color.TRANSPARENT
+                    || mOverridePrimaryColorLight != Color.TRANSPARENT) {
+                return night ? mOverridePrimaryColorDark : mOverridePrimaryColorLight;
+            }
+            return night ? primaryColorDark
+                    : primaryColorLight;
+        }
+
+        /**
+         * Sets accent colors to override the ones in this bundle
+         */
+        public void setOverrideAccentColors(int overrideColorAccentLight,
+                int overrideColorAccentDark) {
+            mOverrideSecondaryColorLight = overrideColorAccentLight;
+            mOverrideSecondaryColorDark = overrideColorAccentDark;
+        }
+
+        /**
+         * Sets palette colors to override the ones in this bundle
+         */
+        public void setOverridePaletteColors(int overrideColorPaletteLight,
+                int overrideColorPaletteDark) {
+            mOverridePrimaryColorLight = overrideColorPaletteLight;
+            mOverridePrimaryColorDark = overrideColorPaletteDark;
+        }
+    }
+
+    /**
+     * The builder of ColorBundle
+     */
+    public static class Builder {
+        protected String mTitle;
+        @ColorInt private int mSecondaryColorLight = Color.TRANSPARENT;
+        @ColorInt private int mSecondaryColorDark = Color.TRANSPARENT;
+        // System and Monet colors
+        @ColorInt private int mPrimaryColorLight = Color.TRANSPARENT;
+        @ColorInt private int mPrimaryColorDark = Color.TRANSPARENT;
+        private List<Drawable> mIcons = new ArrayList<>();
+        private boolean mIsDefault;
+        private Style mStyle = Style.TONAL_SPOT;
+        private int mIndex;
+        protected Map<String, String> mPackages = new HashMap<>();
+
+        /**
+         * Builds the ColorBundle
+         * @param context {@link Context}
+         * @return new {@link ColorBundle} object
+         */
+        public ColorBundle build(Context context) {
+            if (mTitle == null) {
+                mTitle = context.getString(R.string.adaptive_color_title);
+            }
+            return new ColorBundle(mTitle, mPackages, mIsDefault, mStyle, mIndex,
+                    createPreviewInfo(context));
+        }
+
+        /**
+         * Creates preview information
+         * @param context the {@link Context}
+         * @return the {@link PreviewInfo} object
+         */
+        public PreviewInfo createPreviewInfo(@NonNull Context context) {
+            ShapeDrawable shapeDrawable = null;
+            Resources system = context.getResources().getSystem();
+            String pathString = system.getString(
+                    system.getIdentifier(ResourceConstants.CONFIG_ICON_MASK,
+                            "string", ResourceConstants.ANDROID_PACKAGE));
+            Path path = null;
+            if (!TextUtils.isEmpty(pathString)) {
+                path = PathParser.createPathFromPathData(pathString);
+            }
+            if (path != null) {
+                PathShape shape = new PathShape(path, PATH_SIZE, PATH_SIZE);
+                shapeDrawable = new ShapeDrawable(shape);
+                shapeDrawable.setIntrinsicHeight((int) PATH_SIZE);
+                shapeDrawable.setIntrinsicWidth((int) PATH_SIZE);
+            }
+            return new PreviewInfo(mSecondaryColorLight,
+                    mSecondaryColorDark, mPrimaryColorLight, mPrimaryColorDark, mIcons,
+                    shapeDrawable, system.getDimensionPixelOffset(
+                    system.getIdentifier(ResourceConstants.CONFIG_CORNERRADIUS,
+                            "dimen", ResourceConstants.ANDROID_PACKAGE)));
+        }
+
+        public Map<String, String> getPackages() {
+            return Collections.unmodifiableMap(mPackages);
+        }
+
+        /**
+         * Gets title of this {@link ColorBundle} object
+         * @return title string
+         */
+        public String getTitle() {
+            return mTitle;
+        }
+
+        /**
+         * Sets title of bundle
+         * @param title specified title
+         * @return this of {@link Builder}
+         */
+        public Builder setTitle(String title) {
+            mTitle = title;
+            return this;
+        }
+
+        /**
+         * Sets color accent (light)
+         * @param colorSecondaryLight color accent light in {@link ColorInt}
+         * @return this of {@link Builder}
+         */
+        public Builder setColorSecondaryLight(@ColorInt int colorSecondaryLight) {
+            mSecondaryColorLight = colorSecondaryLight;
+            return this;
+        }
+
+        /**
+         * Sets color accent (dark)
+         * @param colorSecondaryDark color accent dark in {@link ColorInt}
+         * @return this of {@link Builder}
+         */
+        public Builder setColorSecondaryDark(@ColorInt int colorSecondaryDark) {
+            mSecondaryColorDark = colorSecondaryDark;
+            return this;
+        }
+
+        /**
+         * Sets color system palette (light)
+         * @param colorPrimaryLight color system palette in {@link ColorInt}
+         * @return this of {@link Builder}
+         */
+        public Builder setColorPrimaryLight(@ColorInt int colorPrimaryLight) {
+            mPrimaryColorLight = colorPrimaryLight;
+            return this;
+        }
+
+        /**
+         * Sets color system palette (dark)
+         * @param colorPrimaryDark color system palette in {@link ColorInt}
+         * @return this of {@link Builder}
+         */
+        public Builder setColorPrimaryDark(@ColorInt int colorPrimaryDark) {
+            mPrimaryColorDark = colorPrimaryDark;
+            return this;
+        }
+
+        /**
+         * Sets icon for bundle
+         * @param icon icon in {@link Drawable}
+         * @return this of {@link Builder}
+         */
+        public Builder addIcon(Drawable icon) {
+            mIcons.add(icon);
+            return this;
+        }
+
+        /**
+         * Sets overlay package for bundle
+         * @param category the category of bundle
+         * @param packageName tha name of package in the category
+         * @return this of {@link Builder}
+         */
+        public Builder addOverlayPackage(String category, String packageName) {
+            mPackages.put(category, packageName);
+            return this;
+        }
+
+        /**
+         * Sets the style of this color seed
+         * @param style color style of {@link Style}
+         * @return this of {@link Builder}
+         */
+        public Builder setStyle(Style style) {
+            mStyle = style;
+            return this;
+        }
+
+        /**
+         * Sets color option index of bundle
+         * @param index color option index
+         * @return this of {@link Builder}
+         */
+        public Builder setIndex(int index) {
+            mIndex = index;
+            return this;
+        }
+
+        /**
+         * Sets as default bundle
+         * @return this of {@link Builder}
+         */
+        public Builder asDefault() {
+            mIsDefault = true;
+            return this;
+        }
+    }
+}
diff --git a/src/com/android/customization/model/color/ColorBundlePreviewExtractor.java b/src/com/android/customization/model/color/ColorBundlePreviewExtractor.java
new file mode 100644
index 0000000..b67eec8
--- /dev/null
+++ b/src/com/android/customization/model/color/ColorBundlePreviewExtractor.java
@@ -0,0 +1,105 @@
+/**
+ * Copyright (C) 2022 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.customization.model.color;
+
+import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
+import static com.android.customization.model.ResourceConstants.ICONS_FOR_PREVIEW;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SYSTEM_PALETTE;
+import static com.android.customization.model.color.ColorUtils.toColorString;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.ColorInt;
+
+import com.android.systemui.monet.ColorScheme;
+import com.android.systemui.monet.Style;
+
+/**
+ * Utility class to read all the details of a color bundle for previewing it
+ * (eg, actual color values)
+ */
+class ColorBundlePreviewExtractor {
+
+    private static final String TAG = "ColorBundlePreviewExtractor";
+
+    private final PackageManager mPackageManager;
+
+    ColorBundlePreviewExtractor(Context context) {
+        mPackageManager = context.getPackageManager();
+    }
+
+    void addSecondaryColor(ColorBundle.Builder builder, @ColorInt int color) {
+        ColorScheme darkColorScheme = new ColorScheme(color, true);
+        ColorScheme lightColorScheme = new ColorScheme(color, false);
+        int lightSecondary = lightColorScheme.getAccentColor();
+        int darkSecondary = darkColorScheme.getAccentColor();
+        builder.addOverlayPackage(OVERLAY_CATEGORY_COLOR, toColorString(color))
+                .setColorSecondaryLight(lightSecondary)
+                .setColorSecondaryDark(darkSecondary);
+    }
+
+    void addPrimaryColor(ColorBundle.Builder builder, @ColorInt int color) {
+        ColorScheme darkColorScheme = new ColorScheme(color, true);
+        ColorScheme lightColorScheme = new ColorScheme(color, false);
+        int lightPrimary = lightColorScheme.getAccentColor();
+        int darkPrimary = darkColorScheme.getAccentColor();
+        builder.addOverlayPackage(OVERLAY_CATEGORY_SYSTEM_PALETTE, toColorString(color))
+                .setColorPrimaryLight(lightPrimary)
+                .setColorPrimaryDark(darkPrimary);
+    }
+
+    void addColorStyle(ColorBundle.Builder builder, String styleName) {
+        Style s = Style.TONAL_SPOT;
+        if (!TextUtils.isEmpty(styleName)) {
+            try {
+                s = Style.valueOf(styleName);
+            } catch (IllegalArgumentException e) {
+                Log.i(TAG, "Unknown style : " + styleName + ". Will default to TONAL_SPOT.");
+            }
+        }
+        builder.setStyle(s);
+    }
+
+    void addAndroidIconOverlay(ColorBundle.Builder builder) throws NameNotFoundException {
+        addSystemDefaultIcons(builder, ICONS_FOR_PREVIEW);
+    }
+
+    void addSystemDefaultIcons(ColorBundle.Builder builder, String... previewIcons) {
+        try {
+            for (String iconName : previewIcons) {
+                builder.addIcon(loadIconPreviewDrawable(iconName));
+            }
+        } catch (NameNotFoundException | NotFoundException e) {
+            Log.w(TAG, "Didn't find android package icons, will skip preview", e);
+        }
+    }
+
+    Drawable loadIconPreviewDrawable(String drawableName)
+            throws NameNotFoundException, NotFoundException {
+        Resources packageRes = mPackageManager.getResourcesForApplication(ANDROID_PACKAGE);
+        Resources res = Resources.getSystem();
+        return res.getDrawable(packageRes.getIdentifier(drawableName, "drawable",
+                        ANDROID_PACKAGE), null);
+    }
+}
diff --git a/src/com/android/customization/model/color/ColorCustomizationManager.java b/src/com/android/customization/model/color/ColorCustomizationManager.java
new file mode 100644
index 0000000..f1f1e53
--- /dev/null
+++ b/src/com/android/customization/model/color/ColorCustomizationManager.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2022 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.customization.model.color;
+
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SYSTEM_PALETTE;
+import static com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_PRESET;
+import static com.android.customization.model.color.ColorOptionsProvider.OVERLAY_COLOR_BOTH;
+import static com.android.customization.model.color.ColorOptionsProvider.OVERLAY_COLOR_INDEX;
+import static com.android.customization.model.color.ColorOptionsProvider.OVERLAY_COLOR_SOURCE;
+import static com.android.customization.model.color.ColorOptionsProvider.OVERLAY_THEME_STYLE;
+
+import android.app.WallpaperColors;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.customization.model.CustomizationManager;
+import com.android.customization.model.ResourceConstants;
+import com.android.customization.model.color.ColorOptionsProvider.ColorSource;
+import com.android.customization.model.theme.OverlayManagerCompat;
+import com.android.wallpaper.R;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/** The Color manager to manage Color bundle related operations. */
+public class ColorCustomizationManager implements CustomizationManager<ColorOption> {
+
+    private static final String TAG = "ColorCustomizationManager";
+    private static final ExecutorService sExecutorService = Executors.newSingleThreadExecutor();
+
+    private static final Set<String> COLOR_OVERLAY_SETTINGS = new HashSet<>();
+    static {
+        COLOR_OVERLAY_SETTINGS.add(OVERLAY_CATEGORY_SYSTEM_PALETTE);
+        COLOR_OVERLAY_SETTINGS.add(OVERLAY_CATEGORY_COLOR);
+        COLOR_OVERLAY_SETTINGS.add(OVERLAY_COLOR_SOURCE);
+        COLOR_OVERLAY_SETTINGS.add(OVERLAY_THEME_STYLE);
+    }
+
+    private static ColorCustomizationManager sColorCustomizationManager;
+
+    private final ColorOptionsProvider mProvider;
+    private final OverlayManagerCompat mOverlayManagerCompat;
+    private final ContentResolver mContentResolver;
+    private final ContentObserver mObserver;
+
+    private Map<String, String> mCurrentOverlays;
+    @ColorSource private String mCurrentSource;
+    private String mCurrentStyle;
+    private WallpaperColors mHomeWallpaperColors;
+    private WallpaperColors mLockWallpaperColors;
+
+    /** Returns the {@link ColorCustomizationManager} instance. */
+    public static ColorCustomizationManager getInstance(Context context,
+            OverlayManagerCompat overlayManagerCompat) {
+        if (sColorCustomizationManager == null) {
+            Context appContext = context.getApplicationContext();
+            sColorCustomizationManager = new ColorCustomizationManager(
+                    new ColorProvider(appContext,
+                            appContext.getString(R.string.themes_stub_package)),
+                    appContext.getContentResolver(), overlayManagerCompat);
+        }
+        return sColorCustomizationManager;
+    }
+
+    @VisibleForTesting
+    ColorCustomizationManager(ColorOptionsProvider provider, ContentResolver contentResolver,
+            OverlayManagerCompat overlayManagerCompat) {
+        mProvider = provider;
+        mContentResolver = contentResolver;
+        mObserver = new ContentObserver(/* handler= */ null) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                super.onChange(selfChange, uri);
+                // Resets current overlays when system's theme setting is changed.
+                if (TextUtils.equals(uri.getLastPathSegment(), ResourceConstants.THEME_SETTING)) {
+                    Log.i(TAG, "Resetting " + mCurrentOverlays + " to null");
+                    mCurrentOverlays = null;
+                }
+            }
+        };
+        mContentResolver.registerContentObserver(
+                Settings.Secure.CONTENT_URI, /* notifyForDescendants= */ true, mObserver);
+        mOverlayManagerCompat = overlayManagerCompat;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mOverlayManagerCompat.isAvailable() && mProvider.isAvailable();
+    }
+
+    @Override
+    public void apply(ColorOption theme, Callback callback) {
+        applyOverlays(theme, callback);
+    }
+
+    private void applyOverlays(ColorOption colorOption, Callback callback) {
+        sExecutorService.submit(() -> {
+            String currentStoredOverlays = getStoredOverlays();
+            if (TextUtils.isEmpty(currentStoredOverlays)) {
+                currentStoredOverlays = "{}";
+            }
+            JSONObject overlaysJson = null;
+            try {
+                overlaysJson = new JSONObject(currentStoredOverlays);
+                JSONObject colorJson = colorOption.getJsonPackages(true);
+                for (String setting : COLOR_OVERLAY_SETTINGS) {
+                    overlaysJson.remove(setting);
+                }
+                for (Iterator<String> it = colorJson.keys(); it.hasNext(); ) {
+                    String key = it.next();
+                    overlaysJson.put(key, colorJson.get(key));
+                }
+                overlaysJson.put(OVERLAY_COLOR_SOURCE, colorOption.getSource());
+                overlaysJson.put(OVERLAY_COLOR_INDEX, String.valueOf(colorOption.getIndex()));
+                overlaysJson.put(OVERLAY_THEME_STYLE,
+                        String.valueOf(colorOption.getStyle().toString()));
+
+                // OVERLAY_COLOR_BOTH is only for wallpaper color case, not preset.
+                if (!COLOR_SOURCE_PRESET.equals(colorOption.getSource())) {
+                    boolean isForBoth =
+                            (mLockWallpaperColors == null || mLockWallpaperColors.equals(
+                                    mHomeWallpaperColors));
+                    overlaysJson.put(OVERLAY_COLOR_BOTH, isForBoth ? "1" : "0");
+                } else {
+                    overlaysJson.remove(OVERLAY_COLOR_BOTH);
+                }
+            } catch (JSONException e) {
+                e.printStackTrace();
+            }
+            boolean allApplied = overlaysJson != null && Settings.Secure.putString(
+                    mContentResolver, ResourceConstants.THEME_SETTING, overlaysJson.toString());
+            new Handler(Looper.getMainLooper()).post(() -> {
+                if (allApplied) {
+                    callback.onSuccess();
+                } else {
+                    callback.onError(null);
+                }
+            });
+        });
+    }
+
+    @Override
+    public void fetchOptions(OptionsFetchedListener<ColorOption> callback, boolean reload) {
+        WallpaperColors lockWallpaperColors = mLockWallpaperColors;
+        if (lockWallpaperColors != null && mLockWallpaperColors.equals(mHomeWallpaperColors)) {
+            lockWallpaperColors = null;
+        }
+        mProvider.fetch(callback, reload, mHomeWallpaperColors, lockWallpaperColors);
+    }
+
+    /**
+     * Sets the current wallpaper colors to extract seeds from
+     */
+    public void setWallpaperColors(WallpaperColors homeColors,
+            @Nullable WallpaperColors lockColors) {
+        mHomeWallpaperColors = homeColors;
+        mLockWallpaperColors = lockColors;
+    }
+
+    /**
+     * Gets current overlays mapping
+     * @return the {@link Map} of overlays
+     */
+    public Map<String, String> getCurrentOverlays() {
+        if (mCurrentOverlays == null) {
+            parseSettings(getStoredOverlays());
+        }
+        return mCurrentOverlays;
+    }
+
+    /**
+     * @return The source of the currently applied color. One of
+     * {@link ColorOptionsProvider#COLOR_SOURCE_HOME},{@link ColorOptionsProvider#COLOR_SOURCE_LOCK}
+     * or {@link ColorOptionsProvider#COLOR_SOURCE_PRESET}.
+     */
+    @ColorSource
+    public String getCurrentColorSource() {
+        if (mCurrentSource == null) {
+            parseSettings(getStoredOverlays());
+        }
+        return mCurrentSource;
+    }
+
+    /**
+     * @return The style of the currently applied color. One of enum values in
+     * {@link com.android.systemui.monet.Style}.
+     */
+    public String getCurrentStyle() {
+        if (mCurrentStyle == null) {
+            parseSettings(getStoredOverlays());
+        }
+        return mCurrentStyle;
+    }
+
+    public String getStoredOverlays() {
+        return Settings.Secure.getString(mContentResolver, ResourceConstants.THEME_SETTING);
+    }
+
+    @VisibleForTesting
+    void parseSettings(String serializedJson) {
+        Map<String, String> allSettings = parseColorSettings(serializedJson);
+        mCurrentSource = allSettings.remove(OVERLAY_COLOR_SOURCE);
+        mCurrentStyle = allSettings.remove(OVERLAY_THEME_STYLE);
+        mCurrentOverlays = allSettings;
+    }
+
+    private Map<String, String> parseColorSettings(String serializedJsonSettings) {
+        Map<String, String> overlayPackages = new HashMap<>();
+        if (serializedJsonSettings != null) {
+            try {
+                final JSONObject jsonPackages = new JSONObject(serializedJsonSettings);
+
+                JSONArray names = jsonPackages.names();
+                if (names != null) {
+                    for (int i = 0; i < names.length(); i++) {
+                        String category = names.getString(i);
+                        if (COLOR_OVERLAY_SETTINGS.contains(category)) {
+                            try {
+                                overlayPackages.put(category, jsonPackages.getString(category));
+                            } catch (JSONException e) {
+                                Log.e(TAG, "parseColorOverlays: " + e.getLocalizedMessage(), e);
+                            }
+                        }
+                    }
+                }
+            } catch (JSONException e) {
+                Log.e(TAG, "parseColorOverlays: " + e.getLocalizedMessage(), e);
+            }
+        }
+        return overlayPackages;
+    }
+}
diff --git a/src/com/android/customization/model/color/ColorOption.java b/src/com/android/customization/model/color/ColorOption.java
new file mode 100644
index 0000000..c8b28c2
--- /dev/null
+++ b/src/com/android/customization/model/color/ColorOption.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2022 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.customization.model.color;
+
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SYSTEM_PALETTE;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.customization.model.CustomizationManager;
+import com.android.customization.model.CustomizationOption;
+import com.android.customization.model.color.ColorOptionsProvider.ColorSource;
+import com.android.systemui.monet.Style;
+import com.android.wallpaper.R;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Represents a color choice for the user.
+ * This could be a preset color or those obtained from a wallpaper.
+ */
+public abstract class ColorOption implements CustomizationOption<ColorOption> {
+
+    private static final String TAG = "ColorOption";
+    private static final String EMPTY_JSON = "{}";
+    @VisibleForTesting
+    static final String TIMESTAMP_FIELD = "_applied_timestamp";
+
+    protected final Map<String, String> mPackagesByCategory;
+    protected final int[] mPreviewColorIds = {R.id.color_preview_0, R.id.color_preview_1,
+            R.id.color_preview_2, R.id.color_preview_3};
+    private final String mTitle;
+    private final boolean mIsDefault;
+    private final Style mStyle;
+    private final int mIndex;
+    private CharSequence mContentDescription;
+
+    protected ColorOption(String title, Map<String, String> overlayPackages, boolean isDefault,
+            Style style, int index) {
+        mTitle = title;
+        mIsDefault = isDefault;
+        mStyle = style;
+        mIndex = index;
+        mPackagesByCategory = Collections.unmodifiableMap(removeNullValues(overlayPackages));
+    }
+
+    @Override
+    public String getTitle() {
+        return mTitle;
+    }
+
+    @Override
+    public boolean isActive(CustomizationManager<ColorOption> manager) {
+        ColorCustomizationManager colorManager = (ColorCustomizationManager) manager;
+
+        String currentStyle = colorManager.getCurrentStyle();
+        if (TextUtils.isEmpty(currentStyle)) {
+            currentStyle = Style.TONAL_SPOT.toString();
+        }
+        boolean isCurrentStyle = TextUtils.equals(getStyle().toString(), currentStyle);
+
+        if (mIsDefault) {
+            String serializedOverlays = colorManager.getStoredOverlays();
+            return (TextUtils.isEmpty(serializedOverlays) || EMPTY_JSON.equals(serializedOverlays)
+                    || colorManager.getCurrentOverlays().isEmpty() || !(serializedOverlays.contains(
+                    OVERLAY_CATEGORY_SYSTEM_PALETTE) || serializedOverlays.contains(
+                    OVERLAY_CATEGORY_COLOR))) && isCurrentStyle;
+        } else {
+            Map<String, String> currentOverlays = colorManager.getCurrentOverlays();
+            String currentSource = colorManager.getCurrentColorSource();
+            boolean isCurrentSource = TextUtils.isEmpty(currentSource) || getSource().equals(
+                    currentSource);
+            return isCurrentSource && isCurrentStyle && mPackagesByCategory.equals(currentOverlays);
+        }
+    }
+
+    /**
+     * This is similar to #equals() but it only compares this theme's packages with the other, that
+     * is, it will return true if applying this theme has the same effect of applying the given one.
+     */
+    public boolean isEquivalent(ColorOption other) {
+        if (other == null) {
+            return false;
+        }
+        if (mIsDefault) {
+            return other.isDefault() || TextUtils.isEmpty(other.getSerializedPackages())
+                    || EMPTY_JSON.equals(other.getSerializedPackages());
+        }
+        // Map#equals ensures keys and values are compared.
+        return mPackagesByCategory.equals(other.mPackagesByCategory);
+    }
+
+    /**
+     * Returns the {@link PreviewInfo} object for this ColorOption
+     */
+    public abstract PreviewInfo getPreviewInfo();
+
+    boolean isDefault() {
+        return mIsDefault;
+    }
+
+    public Map<String, String> getPackagesByCategory() {
+        return mPackagesByCategory;
+    }
+
+    public String getSerializedPackages() {
+        return getJsonPackages(false).toString();
+    }
+
+    public String getSerializedPackagesWithTimestamp() {
+        return getJsonPackages(true).toString();
+    }
+
+    /**
+     * Get a JSONObject representation of this color option, with the current values for each
+     * field, and optionally a {@link TIMESTAMP_FIELD} field.
+     * @param insertTimestamp whether to add a field with the current timestamp
+     * @return the JSONObject for this color option
+     */
+    public JSONObject getJsonPackages(boolean insertTimestamp) {
+        JSONObject json;
+        if (isDefault()) {
+            json = new JSONObject();
+        } else {
+            json = new JSONObject(mPackagesByCategory);
+            // Remove items with null values to avoid deserialization issues.
+            removeNullValues(json);
+        }
+        if (insertTimestamp) {
+            try {
+                json.put(TIMESTAMP_FIELD, System.currentTimeMillis());
+            } catch (JSONException e) {
+                Log.e(TAG, "Couldn't add timestamp to serialized themebundle");
+            }
+        }
+        return json;
+    }
+
+    private void removeNullValues(JSONObject json) {
+        Iterator<String> keys = json.keys();
+        Set<String> keysToRemove = new HashSet<>();
+        while (keys.hasNext()) {
+            String key = keys.next();
+            if (json.isNull(key)) {
+                keysToRemove.add(key);
+            }
+        }
+        for (String key : keysToRemove) {
+            json.remove(key);
+        }
+    }
+
+    private Map<String, String> removeNullValues(Map<String, String> map) {
+        return map.entrySet()
+                .stream()
+                .filter(entry -> entry.getValue() != null)
+                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+    }
+
+    protected CharSequence getContentDescription(Context context) {
+        if (mContentDescription == null) {
+            CharSequence defaultName = context.getString(R.string.default_theme_title);
+            if (isDefault()) {
+                mContentDescription = defaultName;
+            } else {
+                mContentDescription = mTitle;
+            }
+        }
+        return mContentDescription;
+    }
+
+    /**
+     * @return the source of this color option
+     */
+    @ColorSource
+    public abstract String getSource();
+
+    /**
+     * @return the style of this color option
+     */
+    public Style getStyle() {
+        return mStyle;
+    }
+
+    /**
+     * @return the index of this color option
+     */
+    public int getIndex() {
+        return mIndex;
+    }
+
+    /**
+     * The preview information of {@link ColorOption}
+     */
+    public interface PreviewInfo {
+    }
+
+}
diff --git a/src/com/android/customization/model/color/ColorOptionsProvider.java b/src/com/android/customization/model/color/ColorOptionsProvider.java
new file mode 100644
index 0000000..2803c7b
--- /dev/null
+++ b/src/com/android/customization/model/color/ColorOptionsProvider.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 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.customization.model.color;
+
+import android.app.WallpaperColors;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.StringDef;
+
+import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
+
+/**
+ * Interface for a class that can retrieve Colors from the system.
+ */
+public interface ColorOptionsProvider {
+
+    /**
+     * Extra setting indicating the source of the color overlays (it can be one of
+     * COLOR_SOURCE_PRESET, COLOR_SOURCE_HOME or COLOR_SOURCE_LOCK)
+     */
+    String OVERLAY_COLOR_SOURCE = "android.theme.customization.color_source";
+
+    /**
+     * Extra setting indicating the style of the color overlays (it can be one of
+     * {@link com.android.systemui.monet.Style}).
+     */
+    String OVERLAY_THEME_STYLE = "android.theme.customization.theme_style";
+
+    /**
+     * Users selected color option, its value starts from 1 (which means first option).
+     */
+    String OVERLAY_COLOR_INDEX = "android.theme.customization.color_index";
+
+    /**
+     * Users selected color from both home and lock screen.
+     * Example value: 0 means home or lock screen, 1 means both.
+     */
+    String OVERLAY_COLOR_BOTH = "android.theme.customization.color_both";
+
+    String COLOR_SOURCE_PRESET = "preset";
+    String COLOR_SOURCE_HOME = "home_wallpaper";
+    String COLOR_SOURCE_LOCK = "lock_wallpaper";
+
+    @StringDef({COLOR_SOURCE_PRESET, COLOR_SOURCE_HOME, COLOR_SOURCE_LOCK})
+    @interface ColorSource{}
+
+
+    /**
+     * Returns whether themes are available in the current setup.
+     */
+    boolean isAvailable();
+
+    /**
+     * Retrieve the available themes.
+     * @param callback called when the themes have been retrieved (or immediately if cached)
+     * @param reload whether to reload themes if they're cached.
+     * @param homeWallpaperColors to get seed colors from
+     * @param lockWallpaperColors WallpaperColors from the lockscreen wallpaper to get seeds from,
+     *                            if different than homeWallpaperColors
+     */
+    void fetch(OptionsFetchedListener<ColorOption> callback, boolean reload,
+            @Nullable WallpaperColors homeWallpaperColors,
+            @Nullable WallpaperColors lockWallpaperColors);
+}
diff --git a/src/com/android/customization/model/color/ColorProvider.kt b/src/com/android/customization/model/color/ColorProvider.kt
new file mode 100644
index 0000000..0ff3837
--- /dev/null
+++ b/src/com/android/customization/model/color/ColorProvider.kt
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2022 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.customization.model.color
+
+import android.app.WallpaperColors
+import android.content.Context
+import android.content.res.ColorStateList
+import android.content.res.Resources
+import android.text.TextUtils
+import androidx.annotation.ColorInt
+import androidx.core.graphics.ColorUtils.setAlphaComponent
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.android.customization.model.CustomizationManager.OptionsFetchedListener
+import com.android.customization.model.ResourceConstants.COLOR_BUNDLES_ARRAY_NAME
+import com.android.customization.model.ResourceConstants.COLOR_BUNDLE_MAIN_COLOR_PREFIX
+import com.android.customization.model.ResourceConstants.COLOR_BUNDLE_NAME_PREFIX
+import com.android.customization.model.ResourceConstants.COLOR_BUNDLE_STYLE_PREFIX
+import com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SYSTEM_PALETTE
+import com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR
+import com.android.customization.model.ResourcesApkProvider
+import com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_HOME
+import com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_LOCK
+import com.android.customization.model.color.ColorUtils.toColorString
+import com.android.systemui.monet.ColorScheme
+import com.android.systemui.monet.Style
+import com.android.wallpaper.compat.WallpaperManagerCompat
+import com.android.wallpaper.module.InjectorProvider
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import java.util.*
+
+/**
+ * Default implementation of {@link ColorOptionsProvider} that reads preset colors from
+ * a stub APK.
+ */
+class ColorProvider(context: Context, stubPackageName: String) :
+        ResourcesApkProvider(context, stubPackageName), ColorOptionsProvider {
+
+    companion object {
+        const val themeStyleEnabled = true
+        val styleSize = if (themeStyleEnabled) Style.values().size else 1
+        private const val TAG = "ColorProvider"
+        private const val MAX_SEED_COLORS = 4
+        private const val MAX_PRESET_COLORS = 4
+        private const val ALPHA_MASK = 0xFF
+    }
+
+    private val monetEnabled = ColorUtils.isMonetEnabled(context)
+    // TODO(b/202145216): Use style method to fetch the list of style.
+    private var styleList = if (themeStyleEnabled) arrayOf(
+        Style.TONAL_SPOT, Style.SPRITZ, Style.VIBRANT, Style.EXPRESSIVE
+    ) else arrayOf(Style.TONAL_SPOT)
+
+    private val scope = if (mContext is LifecycleOwner) {
+        mContext.lifecycleScope
+    } else {
+        CoroutineScope(Dispatchers.Default + SupervisorJob())
+    }
+
+    private var colorsAvailable = true
+    private var colorBundles: List<ColorOption>? = null
+    private var homeWallpaperColors: WallpaperColors? = null
+    private var lockWallpaperColors: WallpaperColors? = null
+
+
+    override fun isAvailable(): Boolean {
+        return monetEnabled && super.isAvailable() && colorsAvailable
+    }
+
+    override fun fetch(callback: OptionsFetchedListener<ColorOption>?, reload: Boolean,
+                       homeWallpaperColors: WallpaperColors?,
+                       lockWallpaperColors: WallpaperColors?) {
+        val wallpaperColorsChanged = this.homeWallpaperColors != homeWallpaperColors
+                || this.lockWallpaperColors != lockWallpaperColors
+        if (wallpaperColorsChanged) {
+            this.homeWallpaperColors = homeWallpaperColors
+            this.lockWallpaperColors = lockWallpaperColors
+        }
+        if(colorBundles == null || reload || wallpaperColorsChanged) {
+            scope.launch {
+                try {
+                    if (colorBundles == null || reload) {
+                        loadPreset()
+                    }
+                    if (wallpaperColorsChanged || reload) {
+                        loadSeedColors(homeWallpaperColors, lockWallpaperColors)
+                    }
+                } catch (e: Throwable) {
+                    colorsAvailable = false
+                    callback?.onError(e)
+                    return@launch
+                }
+                callback?.onOptionsLoaded(colorBundles)
+            }
+        } else {
+            callback?.onOptionsLoaded(colorBundles)
+        }
+    }
+
+    private fun isLockScreenWallpaperLastApplied(): Boolean {
+        // The WallpaperId increases every time a new wallpaper is set, so the larger wallpaper id
+        // is the most recently set wallpaper
+        val manager = InjectorProvider.getInjector().getWallpaperManagerCompat(mContext)
+        return manager.getWallpaperId(WallpaperManagerCompat.FLAG_LOCK) >
+                manager.getWallpaperId(WallpaperManagerCompat.FLAG_SYSTEM)
+    }
+
+    private fun loadSeedColors(homeWallpaperColors: WallpaperColors?,
+                               lockWallpaperColors: WallpaperColors?) {
+        if (homeWallpaperColors == null) return
+
+        val bundles: MutableList<ColorOption> = ArrayList()
+        val colorsPerSource = if (lockWallpaperColors == null) {
+            MAX_SEED_COLORS
+        } else {
+            MAX_SEED_COLORS / 2
+        }
+
+        if (lockWallpaperColors != null) {
+            val shouldLockColorsGoFirst = isLockScreenWallpaperLastApplied()
+            // First half of the colors
+            buildColorSeeds(
+                    if (shouldLockColorsGoFirst) lockWallpaperColors else homeWallpaperColors,
+                    colorsPerSource,
+                    if (shouldLockColorsGoFirst) COLOR_SOURCE_LOCK else COLOR_SOURCE_HOME,
+                    true,
+                    bundles)
+            // Second half of the colors
+            buildColorSeeds(
+                    if (shouldLockColorsGoFirst) homeWallpaperColors else lockWallpaperColors,
+                    MAX_SEED_COLORS - bundles.size / styleSize,
+                    if (shouldLockColorsGoFirst) COLOR_SOURCE_HOME else COLOR_SOURCE_LOCK,
+                    false,
+                    bundles)
+        } else {
+            buildColorSeeds(homeWallpaperColors, colorsPerSource, COLOR_SOURCE_HOME, true, bundles)
+        }
+
+        bundles.addAll(colorBundles?.filterNot{it is ColorSeedOption} ?: emptyList())
+        colorBundles = bundles
+    }
+
+    private fun buildColorSeeds(wallpaperColors: WallpaperColors, maxColors: Int, source: String,
+                                containsDefault: Boolean, bundles: MutableList<ColorOption>) {
+        val seedColors = ColorScheme.getSeedColors(wallpaperColors)
+        val defaultSeed = seedColors.first()
+        buildBundle(defaultSeed, 0, containsDefault, source, bundles)
+        for ((i, colorInt) in seedColors.drop(1).take(maxColors - 1).withIndex()) {
+            buildBundle(colorInt, i + 1, false, source, bundles)
+        }
+    }
+
+    private fun buildBundle(colorInt: Int, i: Int, isDefault: Boolean, source: String,
+                            bundles: MutableList<ColorOption>) {
+        // TODO(b/202145216): Measure time cost in the loop.
+        for (style in styleList) {
+            val builder = ColorSeedOption.Builder()
+            val lightColorScheme = ColorScheme(colorInt, /* darkTheme= */ false, style)
+            val darkColorScheme = ColorScheme(colorInt, /* darkTheme= */ true, style)
+            builder.setLightColors(lightColorScheme.getLightColorPreview())
+                .setDarkColors(darkColorScheme.getDarkColorPreview())
+                .addOverlayPackage(OVERLAY_CATEGORY_SYSTEM_PALETTE,
+                    if (isDefault) "" else toColorString(colorInt)
+                )
+                .addOverlayPackage(OVERLAY_CATEGORY_COLOR,
+                    if (isDefault) "" else toColorString(colorInt)
+                )
+                .setSource(source)
+                .setStyle(style)
+                // Color option index value starts from 1.
+                .setIndex(i + 1)
+
+            if (isDefault) builder.asDefault()
+
+            bundles.add(builder.build())
+        }
+    }
+
+    /**
+     * Returns the colors for the light theme version of the preview of a ColorScheme
+     * based on this order:
+     * |-------|
+     * | 0 | 1 |
+     * |---+---|
+     * | 2 | 3 |
+     * |-------|
+     */
+    @ColorInt
+    private fun ColorScheme.getLightColorPreview(): IntArray {
+        return intArrayOf(setAlphaComponent(this.accent1[2], ALPHA_MASK),
+                setAlphaComponent(this.accent1[2], ALPHA_MASK),
+                ColorStateList.valueOf(this.accent3[6]).withLStar(85f).colors[0],
+                setAlphaComponent(this.accent1[6], ALPHA_MASK))
+    }
+
+    /**
+     * Returns the color for the dark theme version of the preview of a ColorScheme
+     * based on this order:
+     * |-------|
+     * | 0 | 1 |
+     * |---+---|
+     * | 2 | 3 |
+     * |-------|
+     */
+    @ColorInt
+    private fun ColorScheme.getDarkColorPreview(): IntArray {
+        return intArrayOf(setAlphaComponent(this.accent1[2], ALPHA_MASK),
+                setAlphaComponent(this.accent1[2], ALPHA_MASK),
+                ColorStateList.valueOf(this.accent3[6]).withLStar(85f).colors[0],
+                setAlphaComponent(this.accent1[6], ALPHA_MASK))
+    }
+
+    private fun ColorScheme.getPresetColorPreview(seed: Int): IntArray {
+        return when(this.style) {
+            Style.FRUIT_SALAD -> intArrayOf(seed, this.accent1[2])
+            Style.TONAL_SPOT -> intArrayOf(this.accentColor, this.accentColor)
+            else -> intArrayOf(this.accent1[2], this.accent1[2])
+        }
+    }
+
+    private suspend fun loadPreset() = withContext(Dispatchers.IO) {
+        val extractor = ColorBundlePreviewExtractor(mContext)
+        val bundles: MutableList<ColorOption> = ArrayList()
+
+        val bundleNames = getItemsFromStub(COLOR_BUNDLES_ARRAY_NAME)
+        // Color option index value starts from 1.
+        var index = 1
+        val maxPresetColors = if (themeStyleEnabled) bundleNames.size else MAX_PRESET_COLORS
+        for (bundleName in bundleNames.take(maxPresetColors)) {
+            val builder = ColorBundle.Builder()
+            builder.title = getItemStringFromStub(COLOR_BUNDLE_NAME_PREFIX, bundleName)
+            builder.setIndex(index)
+            val colorFromStub = getItemColorFromStub(COLOR_BUNDLE_MAIN_COLOR_PREFIX, bundleName)
+            extractor.addPrimaryColor(builder, colorFromStub)
+            extractor.addSecondaryColor(builder, colorFromStub)
+            if (themeStyleEnabled) {
+                val styleName = try {
+                    getItemStringFromStub(COLOR_BUNDLE_STYLE_PREFIX, bundleName)
+                } catch (e: Resources.NotFoundException) {
+                    null
+                }
+                extractor.addColorStyle(builder, styleName)
+                val style = try {
+                    if (styleName != null) Style.valueOf(styleName) else Style.TONAL_SPOT
+                } catch (e: IllegalArgumentException) {
+                    Style.TONAL_SPOT
+                }
+
+                val darkColors = ColorScheme(colorFromStub, true, style)
+                        .getPresetColorPreview(colorFromStub)
+                val lightColors = ColorScheme(colorFromStub, false, style)
+                        .getPresetColorPreview(colorFromStub)
+                builder.setColorPrimaryDark(darkColors[0]).setColorSecondaryDark(darkColors[1])
+                builder.setColorPrimaryLight(lightColors[0]).setColorPrimaryLight(lightColors[1])
+            }
+
+            extractor.addAndroidIconOverlay(builder)
+            bundles.add(builder.build(mContext))
+            index++
+        }
+
+        colorBundles = bundles
+    }
+}
diff --git a/src/com/android/customization/model/color/ColorSectionController.java b/src/com/android/customization/model/color/ColorSectionController.java
new file mode 100644
index 0000000..c3b6825
--- /dev/null
+++ b/src/com/android/customization/model/color/ColorSectionController.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright (C) 2022 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.customization.model.color;
+
+import static android.view.View.VISIBLE;
+
+import static com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_HOME;
+import static com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_LOCK;
+import static com.android.customization.model.color.ColorOptionsProvider.COLOR_SOURCE_PRESET;
+import static com.android.customization.widget.OptionSelectorController.CheckmarkStyle.CENTER;
+
+import android.app.Activity;
+import android.app.WallpaperColors;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.stats.style.StyleEnums;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.android.customization.model.CustomizationManager;
+import com.android.customization.model.theme.OverlayManagerCompat;
+import com.android.customization.module.CustomizationInjector;
+import com.android.customization.module.ThemesUserEventLogger;
+import com.android.customization.picker.color.ColorSectionView;
+import com.android.customization.widget.OptionSelectorController;
+import com.android.wallpaper.R;
+import com.android.wallpaper.model.CustomizationSectionController;
+import com.android.wallpaper.model.WallpaperColorsViewModel;
+import com.android.wallpaper.module.InjectorProvider;
+import com.android.wallpaper.widget.PageIndicator;
+import com.android.wallpaper.widget.SeparatedTabLayout;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Color section view's controller for the logic of color customization.
+ */
+public class ColorSectionController implements CustomizationSectionController<ColorSectionView> {
+
+    private static final String TAG = "ColorSectionController";
+    private static final String KEY_COLOR_TAB_POSITION = "COLOR_TAB_POSITION";
+    private static final long MIN_COLOR_APPLY_PERIOD = 500L;
+
+    private static final int WALLPAPER_TAB_INDEX = 0;
+    private static final int PRESET_TAB_INDEX = 1;
+
+    private final ThemesUserEventLogger mEventLogger;
+    private final ColorCustomizationManager mColorManager;
+    private final WallpaperColorsViewModel mWallpaperColorsViewModel;
+    private final LifecycleOwner mLifecycleOwner;
+    private final ColorSectionAdapter mColorSectionAdapter = new ColorSectionAdapter();
+    private final List<ColorOption> mWallpaperColorOptions = new ArrayList<>();
+    private final List<ColorOption> mPresetColorOptions = new ArrayList<>();
+
+    private ViewPager2 mColorSectionViewPager;
+    private ColorOption mSelectedColor;
+    private SeparatedTabLayout mTabLayout;
+    @Nullable private WallpaperColors mHomeWallpaperColors;
+    @Nullable private WallpaperColors mLockWallpaperColors;
+    // Uses a boolean value to indicate whether wallpaper color is ready because WallpaperColors
+    // maybe be null when it's ready.
+    private boolean mHomeWallpaperColorsReady;
+    private boolean mLockWallpaperColorsReady;
+    private Optional<Integer> mTabPositionToRestore = Optional.empty();
+    private long mLastColorApplyingTime = 0L;
+    private ColorSectionView mColorSectionView;
+
+    private static int getNumPages(int optionsPerPage, int totalOptions) {
+        return (int) Math.ceil((float) totalOptions / optionsPerPage);
+    }
+
+    public ColorSectionController(Activity activity, WallpaperColorsViewModel viewModel,
+            LifecycleOwner lifecycleOwner, @Nullable Bundle savedInstanceState) {
+        CustomizationInjector injector = (CustomizationInjector) InjectorProvider.getInjector();
+        mEventLogger = (ThemesUserEventLogger) injector.getUserEventLogger(activity);
+        mColorManager = ColorCustomizationManager.getInstance(activity,
+                new OverlayManagerCompat(activity));
+        mWallpaperColorsViewModel = viewModel;
+        mLifecycleOwner = lifecycleOwner;
+
+        if (savedInstanceState != null && savedInstanceState.containsKey(KEY_COLOR_TAB_POSITION)) {
+            mTabPositionToRestore = Optional.of(savedInstanceState.getInt(KEY_COLOR_TAB_POSITION));
+        }
+    }
+
+    @Override
+    public boolean isAvailable(@Nullable Context context) {
+        return context != null && ColorUtils.isMonetEnabled(context) && mColorManager.isAvailable();
+    }
+
+    @Override
+    public ColorSectionView createView(Context context) {
+        mColorSectionView = (ColorSectionView) LayoutInflater.from(context).inflate(
+                R.layout.color_section_view, /* root= */ null);
+        mColorSectionViewPager = mColorSectionView.findViewById(R.id.color_section_view_pager);
+        mColorSectionViewPager.setAdapter(mColorSectionAdapter);
+        mColorSectionViewPager.setUserInputEnabled(false);
+        mTabLayout = mColorSectionView.findViewById(R.id.separated_tabs);
+        mColorSectionAdapter.setNumColors(context.getResources().getInteger(
+                R.integer.options_grid_num_columns));
+        // TODO(b/202145216): Use just 2 views when tapping either button on top.
+        mTabLayout.setViewPager(mColorSectionViewPager);
+
+        mWallpaperColorsViewModel.getHomeWallpaperColors().observe(mLifecycleOwner,
+                homeColors -> {
+                    mHomeWallpaperColors = homeColors;
+                    mHomeWallpaperColorsReady = true;
+                    maybeLoadColors();
+                });
+        mWallpaperColorsViewModel.getLockWallpaperColors().observe(mLifecycleOwner,
+                lockColors -> {
+                    mLockWallpaperColors = lockColors;
+                    mLockWallpaperColorsReady = true;
+                    maybeLoadColors();
+                });
+        return mColorSectionView;
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle savedInstanceState) {
+        if (mColorSectionViewPager != null) {
+            savedInstanceState.putInt(KEY_COLOR_TAB_POSITION,
+                    mColorSectionViewPager.getCurrentItem());
+        }
+    }
+
+    private void maybeLoadColors() {
+        if (mHomeWallpaperColorsReady && mLockWallpaperColorsReady) {
+            mColorManager.setWallpaperColors(mHomeWallpaperColors, mLockWallpaperColors);
+            loadColorOptions(/* reload= */ false);
+        }
+    }
+
+    private void loadColorOptions(boolean reload) {
+        mColorManager.fetchOptions(new CustomizationManager.OptionsFetchedListener<ColorOption>() {
+            @Override
+            public void onOptionsLoaded(List<ColorOption> options) {
+                mWallpaperColorOptions.clear();
+                mPresetColorOptions.clear();
+
+                for (ColorOption option : options) {
+                    if (option instanceof ColorSeedOption) {
+                        mWallpaperColorOptions.add(option);
+                    } else if (option instanceof ColorBundle) {
+                        mPresetColorOptions.add(option);
+                    }
+                }
+                mSelectedColor = findActiveColorOption(mWallpaperColorOptions,
+                        mPresetColorOptions);
+                mTabLayout.post(()-> setUpColorViewPager());
+            }
+
+            @Override
+            public void onError(@Nullable Throwable throwable) {
+                if (throwable != null) {
+                    Log.e(TAG, "Error loading theme bundles", throwable);
+                }
+            }
+        }, reload);
+    }
+
+    private void setUpColorViewPager() {
+        mColorSectionAdapter.notifyDataSetChanged();
+
+        if (mTabLayout != null && mTabLayout.getTabCount() == 0) {
+            mTabLayout.addTab(mTabLayout.newTab().setText(R.string.wallpaper_color_tab),
+                    WALLPAPER_TAB_INDEX);
+            mTabLayout.addTab(mTabLayout.newTab().setText(R.string.preset_color_tab),
+                    PRESET_TAB_INDEX);
+        }
+
+        if (mWallpaperColorOptions.isEmpty()) {
+            // Select preset tab and disable wallpaper tab.
+            mTabLayout.getTabAt(WALLPAPER_TAB_INDEX).view.setEnabled(false);
+            mColorSectionViewPager.setCurrentItem(PRESET_TAB_INDEX, /* smoothScroll= */ false);
+            return;
+        }
+
+        mColorSectionViewPager.setCurrentItem(
+                mTabPositionToRestore.orElseGet(
+                        () -> COLOR_SOURCE_PRESET.equals(mColorManager.getCurrentColorSource())
+                                ? PRESET_TAB_INDEX
+                                : WALLPAPER_TAB_INDEX),
+                /* smoothScroll= */ false);
+
+        // Disable "wallpaper colors" and "basic colors" swiping for new color style.
+        mColorSectionViewPager.setUserInputEnabled(!ColorProvider.themeStyleEnabled);
+    }
+
+    private void setupWallpaperColorPages(ViewPager2 container, int colorsPerPage,
+            PageIndicator pageIndicator) {
+        container.setAdapter(new ColorPageAdapter(mWallpaperColorOptions, /* pageEnabled= */ true,
+                colorsPerPage));
+        if (ColorProvider.themeStyleEnabled) {
+            // Update page index to show selected items.
+            int selectedIndex = mWallpaperColorOptions.indexOf(mSelectedColor);
+            if (selectedIndex >= 0 && colorsPerPage != 0) {
+                int pageIndex = selectedIndex / colorsPerPage;
+                container.setCurrentItem(pageIndex, /* smoothScroll= */ false);
+            }
+            pageIndicator.setNumPages(getNumPages(colorsPerPage, mWallpaperColorOptions.size()));
+            registerOnPageChangeCallback(container, pageIndicator);
+        }
+    }
+
+    private void setupPresetColorPages(ViewPager2 container, int colorsPerPage,
+            PageIndicator pageIndicator) {
+        container.setAdapter(new ColorPageAdapter(mPresetColorOptions, /* pageEnabled= */ true,
+                colorsPerPage));
+        if (ColorProvider.themeStyleEnabled) {
+            // Update page index to show selected items.
+            int selectedIndex = mPresetColorOptions.indexOf(mSelectedColor);
+            if (selectedIndex >= 0 && colorsPerPage != 0) {
+                int pageIndex = selectedIndex / colorsPerPage;
+                container.setCurrentItem(pageIndex, /* smoothScroll= */ false);
+            }
+            pageIndicator.setNumPages(getNumPages(colorsPerPage, mPresetColorOptions.size()));
+            registerOnPageChangeCallback(container, pageIndicator);
+        }
+    }
+
+    private void registerOnPageChangeCallback(ViewPager2 container, PageIndicator pageIndicator) {
+        container.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
+            @Override
+            public void onPageSelected(int position) {
+                super.onPageSelected(position);
+                pageIndicator.setLocation(position);
+            }
+
+            @Override
+            public void onPageScrolled(int position, float positionOffset,
+                    int positionOffsetPixels) {
+                super.onPageScrolled(position, positionOffset, positionOffsetPixels);
+                pageIndicator.setLocation(position);
+            }
+        });
+    }
+
+    private void setupColorOptions(RecyclerView container, List<ColorOption> colorOptions,
+            boolean pageEnabled, int index, int colorsPerPage) {
+        int totalSize = colorOptions.size();
+        if (totalSize == 0) {
+            return;
+        }
+
+        List<ColorOption> subOptions;
+        if (pageEnabled && ColorProvider.themeStyleEnabled) {
+            subOptions = colorOptions.subList(colorsPerPage * index,
+                    Math.min(colorsPerPage * (index + 1), totalSize));
+        } else {
+            subOptions = colorOptions;
+        }
+
+        OptionSelectorController<ColorOption> adaptiveController = new OptionSelectorController<>(
+                container, subOptions, /* useGrid= */ true, CENTER);
+        adaptiveController.initOptions(mColorManager);
+        setUpColorOptionsController(adaptiveController);
+    }
+
+    private ColorOption findActiveColorOption(List<ColorOption> wallpaperColorOptions,
+            List<ColorOption> presetColorOptions) {
+        ColorOption activeColorOption = null;
+        for (ColorOption colorOption : Lists.newArrayList(
+                Iterables.concat(wallpaperColorOptions, presetColorOptions))) {
+            if (colorOption.isActive(mColorManager)) {
+                activeColorOption = colorOption;
+                break;
+            }
+        }
+        // Use the first one option by default. This should not happen as above should have an
+        // active option found.
+        if (activeColorOption == null) {
+            activeColorOption = wallpaperColorOptions.isEmpty()
+                    ? presetColorOptions.get(0)
+                    : wallpaperColorOptions.get(0);
+        }
+        return activeColorOption;
+    }
+
+    private void setUpColorOptionsController(
+            OptionSelectorController<ColorOption> optionSelectorController) {
+        if (mSelectedColor != null && optionSelectorController.containsOption(mSelectedColor)) {
+            optionSelectorController.setSelectedOption(mSelectedColor);
+        }
+
+        optionSelectorController.addListener(selectedOption -> {
+            ColorOption selectedColor = (ColorOption) selectedOption;
+            if (mSelectedColor.equals(selectedColor)) {
+                return;
+            }
+            mSelectedColor = (ColorOption) selectedOption;
+            // Post with delay for color option to run ripple.
+            new Handler().postDelayed(()-> applyColor(mSelectedColor), /* delayMillis= */ 100);
+        });
+    }
+
+    private void applyColor(ColorOption colorOption) {
+        if (SystemClock.elapsedRealtime() - mLastColorApplyingTime < MIN_COLOR_APPLY_PERIOD) {
+            return;
+        }
+        mLastColorApplyingTime = SystemClock.elapsedRealtime();
+        mColorManager.apply(colorOption, new CustomizationManager.Callback() {
+            @Override
+            public void onSuccess() {
+                mColorSectionView.announceForAccessibility(
+                        mColorSectionView.getContext().getString(R.string.color_changed));
+                mEventLogger.logColorApplied(getColorAction(colorOption), colorOption.getIndex());
+            }
+
+            @Override
+            public void onError(@Nullable Throwable throwable) {
+                Log.w(TAG, "Apply theme with error: " + throwable);
+            }
+        });
+    }
+
+    private int getColorAction(ColorOption colorOption) {
+        int action = StyleEnums.DEFAULT_ACTION;
+        boolean isForBoth = mLockWallpaperColors == null || mLockWallpaperColors.equals(
+                mHomeWallpaperColors);
+
+        if (TextUtils.equals(colorOption.getSource(), COLOR_SOURCE_PRESET)) {
+            action = StyleEnums.COLOR_PRESET_APPLIED;
+        } else if (isForBoth) {
+            action = StyleEnums.COLOR_WALLPAPER_HOME_LOCK_APPLIED;
+        } else {
+            switch (colorOption.getSource()) {
+                case COLOR_SOURCE_HOME:
+                    action = StyleEnums.COLOR_WALLPAPER_HOME_APPLIED;
+                    break;
+                case COLOR_SOURCE_LOCK:
+                    action = StyleEnums.COLOR_WALLPAPER_LOCK_APPLIED;
+                    break;
+            }
+        }
+        return action;
+    }
+
+    private class ColorSectionAdapter extends
+            RecyclerView.Adapter<ColorSectionAdapter.ColorPageViewHolder> {
+
+        private final int mItemCounts = new int[]{WALLPAPER_TAB_INDEX, PRESET_TAB_INDEX}.length;
+        private int mNumColors;
+
+        @Override
+        public int getItemCount() {
+            return mItemCounts;
+        }
+
+        @Override
+        public void onBindViewHolder(ColorPageViewHolder viewHolder, int position) {
+            switch (position) {
+                case WALLPAPER_TAB_INDEX:
+                    setupWallpaperColorPages(viewHolder.mContainer, mNumColors,
+                            viewHolder.mPageIndicator);
+                    break;
+                case PRESET_TAB_INDEX:
+                    setupPresetColorPages(viewHolder.mContainer, mNumColors,
+                            viewHolder.mPageIndicator);
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        @Override
+        public ColorPageViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
+            return new ColorPageViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(
+                    viewType, viewGroup, false));
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            return R.layout.color_pages_view;
+        }
+
+        public void setNumColors(int numColors) {
+            mNumColors = numColors;
+        }
+
+        private class ColorPageViewHolder extends RecyclerView.ViewHolder {
+            private ViewPager2 mContainer;
+            private PageIndicator mPageIndicator;
+
+            ColorPageViewHolder(View itemView) {
+                super(itemView);
+                mContainer = itemView.findViewById(R.id.color_page_container);
+                mPageIndicator = itemView.findViewById(R.id.color_page_indicator);
+                if (ColorProvider.themeStyleEnabled) {
+                    mPageIndicator.setVisibility(VISIBLE);
+                }
+            }
+        }
+    }
+
+    private class ColorPageAdapter extends
+            RecyclerView.Adapter<ColorPageAdapter.ColorOptionViewHolder> {
+
+        private final boolean mPageEnabled;
+        private final List<ColorOption> mColorOptions;
+        private final int mColorsPerPage;
+
+        private ColorPageAdapter(List<ColorOption> colorOptions, boolean pageEnabled,
+                int colorsPerPage) {
+            mPageEnabled = pageEnabled;
+            mColorOptions = colorOptions;
+            mColorsPerPage = colorsPerPage;
+        }
+
+        @Override
+        public int getItemCount() {
+            if (!mPageEnabled || !ColorProvider.themeStyleEnabled) {
+                return 1;
+            }
+            // Color page size.
+            return getNumPages(mColorsPerPage, mColorOptions.size());
+        }
+
+        @Override
+        public void onBindViewHolder(ColorOptionViewHolder viewHolder, int position) {
+            setupColorOptions(viewHolder.mContainer, mColorOptions, mPageEnabled, position,
+                    mColorsPerPage);
+        }
+
+        @Override
+        public ColorOptionViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
+            return new ColorOptionViewHolder(
+                    LayoutInflater.from(viewGroup.getContext()).inflate(viewType, viewGroup,
+                            false));
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            return R.layout.color_options_view;
+        }
+
+        private class ColorOptionViewHolder extends RecyclerView.ViewHolder {
+            private RecyclerView mContainer;
+
+            ColorOptionViewHolder(View itemView) {
+                super(itemView);
+                mContainer = itemView.findViewById(R.id.color_option_container);
+            }
+        }
+    }
+}
diff --git a/src/com/android/customization/model/color/ColorSeedOption.java b/src/com/android/customization/model/color/ColorSeedOption.java
new file mode 100644
index 0000000..7bddcb0
--- /dev/null
+++ b/src/com/android/customization/model/color/ColorSeedOption.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2022 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.customization.model.color;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PorterDuff.Mode;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.customization.model.color.ColorOptionsProvider.ColorSource;
+import com.android.systemui.monet.Style;
+import com.android.wallpaper.R;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Represents a seed color obtained from WallpaperColors, for the user to chose as their theming
+ * option.
+ */
+public class ColorSeedOption extends ColorOption {
+
+    private final PreviewInfo mPreviewInfo;
+    @ColorSource
+    private final String mSource;
+
+    @VisibleForTesting
+    ColorSeedOption(String title, Map<String, String> overlayPackages, boolean isDefault,
+            @ColorSource String source, Style style, int index, PreviewInfo previewInfo) {
+        super(title, overlayPackages, isDefault, style, index);
+        mSource = source;
+        mPreviewInfo = previewInfo;
+    }
+
+    @Override
+    public PreviewInfo getPreviewInfo() {
+        return mPreviewInfo;
+    }
+
+    @Override
+    public String getSource() {
+        return mSource;
+    }
+
+    @Override
+    public int getLayoutResId() {
+        return R.layout.color_option;
+    }
+
+    @Override
+    public void bindThumbnailTile(View view) {
+        Resources res = view.getContext().getResources();
+        @ColorInt int[] colors = mPreviewInfo.resolveColors(res);
+
+        int padding = view.isActivated()
+                ? res.getDimensionPixelSize(R.dimen.color_seed_option_tile_padding_selected)
+                : res.getDimensionPixelSize(R.dimen.color_seed_option_tile_padding);
+        for (int i = 0; i < mPreviewColorIds.length; i++) {
+            ImageView colorPreviewImageView = view.findViewById(mPreviewColorIds[i]);
+            colorPreviewImageView.getDrawable().setColorFilter(colors[i], Mode.SRC);
+            colorPreviewImageView.setPadding(padding, padding, padding, padding);
+        }
+
+        view.setContentDescription(getContentDescription(view.getContext()));
+    }
+
+    @Override
+    protected CharSequence getContentDescription(Context context) {
+        // Override because we want all options with the same description.
+        return context.getString(R.string.wallpaper_color_title);
+    }
+
+    /**
+     * The preview information of {@link ColorOption}
+     */
+    public static class PreviewInfo implements ColorOption.PreviewInfo {
+        @ColorInt public int[] lightColors;
+        @ColorInt public int[] darkColors;
+
+        private PreviewInfo(@ColorInt int[] lightColors, @ColorInt int[] darkColors) {
+            this.lightColors = lightColors;
+            this.darkColors = darkColors;
+        }
+
+        /**
+         * Returns the colors to be applied corresponding with the current
+         * configuration's UI mode.
+         * @return one of {@link #lightColors} or {@link #darkColors}
+         */
+        @ColorInt
+        public int[] resolveColors(Resources res) {
+            boolean night = (res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                    == Configuration.UI_MODE_NIGHT_YES;
+            return night ? darkColors : lightColors;
+        }
+    }
+
+    /**
+     * The builder of ColorSeedOption
+     */
+    public static class Builder {
+        protected String mTitle;
+        @ColorInt
+        private int[] mLightColors;
+        @ColorInt
+        private int[] mDarkColors;
+        @ColorSource
+        private String mSource;
+        private boolean mIsDefault;
+        private Style mStyle = Style.TONAL_SPOT;
+        private int mIndex;
+        protected Map<String, String> mPackages = new HashMap<>();
+
+        /**
+         * Builds the ColorSeedOption
+         * @return new {@link ColorOption} object
+         */
+        public ColorSeedOption build() {
+            return new ColorSeedOption(mTitle, mPackages, mIsDefault, mSource, mStyle, mIndex,
+                    createPreviewInfo());
+        }
+
+        /**
+         * Creates preview information
+         * @return the {@link PreviewInfo} object
+         */
+        public PreviewInfo createPreviewInfo() {
+            return new PreviewInfo(mLightColors, mDarkColors);
+        }
+
+        public Map<String, String> getPackages() {
+            return Collections.unmodifiableMap(mPackages);
+        }
+
+        /**
+         * Gets title of {@link ColorOption} object
+         * @return title string
+         */
+        public String getTitle() {
+            return mTitle;
+        }
+
+        /**
+         * Sets title of bundle
+         * @param title specified title
+         * @return this of {@link ColorBundle.Builder}
+         */
+        public Builder setTitle(String title) {
+            mTitle = title;
+            return this;
+        }
+
+        /**
+         * Sets the colors for preview in light mode
+         * @param lightColors  {@link ColorInt} colors for light mode
+         * @return this of {@link Builder}
+         */
+        public Builder setLightColors(@ColorInt int[] lightColors) {
+            mLightColors = lightColors;
+            return this;
+        }
+
+        /**
+         * Sets the colors for preview in light mode
+         * @param darkColors  {@link ColorInt} colors for light mode
+         * @return this of {@link Builder}
+         */
+        public Builder setDarkColors(@ColorInt int[] darkColors) {
+            mDarkColors = darkColors;
+            return this;
+        }
+
+
+        /**
+         * Sets overlay package for bundle
+         * @param category the category of bundle
+         * @param packageName tha name of package in the category
+         * @return this of {@link Builder}
+         */
+        public Builder addOverlayPackage(String category, String packageName) {
+            mPackages.put(category, packageName);
+            return this;
+        }
+
+        /**
+         * Sets the source of this color seed
+         * @param source typically either {@link ColorOptionsProvider#COLOR_SOURCE_HOME} or
+         *              {@link ColorOptionsProvider#COLOR_SOURCE_LOCK}
+         * @return this of {@link Builder}
+         */
+        public Builder setSource(@ColorSource String source) {
+            mSource = source;
+            return this;
+        }
+
+        /**
+         * Sets the source of this color seed
+         * @param style color style of {@link Style}
+         * @return this of {@link Builder}
+         */
+        public Builder setStyle(Style style) {
+            mStyle = style;
+            return this;
+        }
+
+        /**
+         * Sets color option index of seed
+         * @param index color option index
+         * @return this of {@link ColorBundle.Builder}
+         */
+        public Builder setIndex(int index) {
+            mIndex = index;
+            return this;
+        }
+
+        /**
+         * Sets as default bundle
+         * @return this of {@link Builder}
+         */
+        public Builder asDefault() {
+            mIsDefault = true;
+            return this;
+        }
+    }
+}
diff --git a/src/com/android/customization/model/color/ColorUtils.kt b/src/com/android/customization/model/color/ColorUtils.kt
new file mode 100644
index 0000000..f07ff31
--- /dev/null
+++ b/src/com/android/customization/model/color/ColorUtils.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 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.customization.model.color
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.os.SystemProperties
+import android.util.Log
+import androidx.annotation.ColorInt
+
+/**
+ * Utility to wrap Monet's color extraction
+ */
+object ColorUtils {
+    private const val TAG = "ColorUtils"
+    private const val MONET_FLAG = "flag_monet"
+    private var sSysuiRes: Resources? = null
+    private var sFlagId = 0
+
+    /**
+     * Returns true if color extraction is enabled in systemui.
+     */
+    @JvmStatic
+    fun isMonetEnabled(context: Context): Boolean {
+        var monetEnabled = SystemProperties.getBoolean("persist.systemui.flag_monet", false)
+        if (!monetEnabled) {
+            if (sSysuiRes == null) {
+                try {
+                    val pm = context.packageManager
+                    val sysUIInfo = pm.getApplicationInfo("com.android.systemui",
+                            PackageManager.GET_META_DATA or PackageManager.MATCH_SYSTEM_ONLY)
+                    if (sysUIInfo != null) {
+                        sSysuiRes = pm.getResourcesForApplication(sysUIInfo)
+                    }
+                } catch (e: PackageManager.NameNotFoundException) {
+                    Log.w(TAG, "Couldn't read color flag, skipping section", e)
+                }
+            }
+            if (sFlagId == 0) {
+                sFlagId = if (sSysuiRes == null) 0 else sSysuiRes!!.getIdentifier(
+                        MONET_FLAG, "bool", "com.android.systemui")
+            }
+            if (sFlagId > 0) {
+                monetEnabled = sSysuiRes!!.getBoolean(sFlagId)
+            }
+        }
+        return monetEnabled
+    }
+
+    @JvmStatic
+    fun toColorString(@ColorInt color: Int): String {
+        return String.format("%06X", 0xFFFFFF and color)
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/customization/model/color/WallpaperColorResources.java b/src/com/android/customization/model/color/WallpaperColorResources.java
new file mode 100644
index 0000000..eb8b39b
--- /dev/null
+++ b/src/com/android/customization/model/color/WallpaperColorResources.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 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.customization.model.color;
+
+import android.app.WallpaperColors;
+import android.content.Context;
+import android.util.SparseIntArray;
+import android.widget.RemoteViews.ColorResources;
+
+import com.android.systemui.monet.ColorScheme;
+
+import java.util.List;
+
+/** A class to override colors in a {@link Context} with wallpaper colors. */
+public class WallpaperColorResources {
+
+    private final SparseIntArray mColorOverlay = new SparseIntArray();
+
+    public WallpaperColorResources(WallpaperColors wallpaperColors) {
+        ColorScheme wallpaperColorScheme = new ColorScheme(wallpaperColors, /* darkTheme= */ false);
+        addOverlayColor(wallpaperColorScheme.getNeutral1(), android.R.color.system_neutral1_10);
+        addOverlayColor(wallpaperColorScheme.getNeutral2(), android.R.color.system_neutral2_10);
+        addOverlayColor(wallpaperColorScheme.getAccent1(), android.R.color.system_accent1_10);
+        addOverlayColor(wallpaperColorScheme.getAccent2(), android.R.color.system_accent2_10);
+        addOverlayColor(wallpaperColorScheme.getAccent3(), android.R.color.system_accent3_10);
+    }
+
+    /** Applies the wallpaper color resources to the {@code context}. */
+    public void apply(Context context) {
+        ColorResources.create(context, mColorOverlay).apply(context);
+    }
+
+    private void addOverlayColor(List<Integer> colors, int firstResourceColorId) {
+        int resourceColorId = firstResourceColorId;
+        for (int color : colors) {
+            mColorOverlay.put(resourceColorId, color);
+            resourceColorId++;
+        }
+    }
+}
diff --git a/src/com/android/customization/picker/color/ColorSectionView.java b/src/com/android/customization/picker/color/ColorSectionView.java
new file mode 100644
index 0000000..b8ba2e4
--- /dev/null
+++ b/src/com/android/customization/picker/color/ColorSectionView.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.customization.picker.color;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.annotation.Nullable;
+
+import com.android.wallpaper.picker.SectionView;
+
+/**
+ * The class inherits from {@link SectionView} as the view representing the color section of the
+ * customization picker.
+ */
+public final class ColorSectionView extends SectionView {
+    public ColorSectionView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+}