ThemePickerLeaf: Bring back fonts, icon pack, shape customization

 * Based on android 11 impl

Co-Authored-By: Tim Zimmermann <tim@linux4.de>
Change-Id: I60b24f0cd8e898cb6d43ad190cad99a73cb7b2bb
diff --git a/Android.bp b/Android.bp
index 57ddbe3..de35b67 100644
--- a/Android.bp
+++ b/Android.bp
@@ -30,6 +30,18 @@
 }
 
 genrule {
+    name: "ThemePickerLeaf_res",
+    tools: ["soong_zip"],
+    srcs: [
+        "res/**/*",
+    ],
+    out: ["ThemePickerLeaf_res.zip"],
+    cmd: "INPUTS=($(in)) && "
+        + "RES_DIR=$$(dirname $$(dirname $${INPUTS[0]})) && "
+        + "$(location soong_zip) -o $(out) -C $$RES_DIR -D $$RES_DIR"
+}
+
+genrule {
     name: "ThemePickerLeaf_res_overrides",
     tools: ["soong_zip"],
     srcs: [
@@ -68,6 +80,7 @@
         ":WallpaperPicker2_res",
         ":ThemePicker_res",
         ":ThemePicker_res_overrides",
+        ":ThemePickerLeaf_res",
         ":ThemePickerLeaf_res_overrides",
     ],
 
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 589b03d..380b14b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -17,6 +17,8 @@
     xmlns:tools="http://schemas.android.com/tools"
     package="com.android.wallpaper">
     <uses-permission android:name="android.permission.BIND_WALLPAPER" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
 
     <queries>
         <!-- Intent filter with action used to discover launcher -->
diff --git a/res/layout/font_preview_card.xml b/res/layout/font_preview_card.xml
new file mode 100644
index 0000000..aa5c276
--- /dev/null
+++ b/res/layout/font_preview_card.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<androidx.cardview.widget.CardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/FullContentPreviewCard"
+    android:id="@+id/font_preview_card"
+    android:contentDescription="@string/font_preview_content_description"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center">
+
+    <FrameLayout
+        android:id="@+id/theme_preview_card_body_container"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/preview_theme_content_max_height"
+        android:layout_marginTop="@dimen/preview_theme_content_margin"
+        android:clipChildren="false"
+        android:importantForAccessibility="noHideDescendants">
+
+        <include layout="@layout/preview_card_font_content" />
+
+   </FrameLayout>
+</androidx.cardview.widget.CardView>
diff --git a/res/layout/font_section_view.xml b/res/layout/font_section_view.xml
new file mode 100644
index 0000000..12e8687
--- /dev/null
+++ b/res/layout/font_section_view.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<org.leafos.customization.picker.font.FontSectionView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:paddingBottom="@dimen/section_bottom_padding"
+    android:paddingHorizontal="@dimen/section_horizontal_padding"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/preview_name_font"
+            style="@style/SectionTitleTextStyle" />
+
+        <TextView
+            android:id="@+id/font_section_description"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="@style/SectionSubtitleTextStyle"/>
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@+id/font_section_tile"
+        android:layout_width="@dimen/option_tile_width"
+        android:layout_height="@dimen/option_tile_width"
+        android:scaleType="center"
+        android:background="@drawable/option_border_color">
+        <TextView
+            android:id="@+id/thumbnail_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:textSize="@dimen/font_comonent_option_thumbnail_size"
+            android:textAlignment="center"
+            android:textColor="?android:attr/colorForeground"
+            android:text="@string/font_component_option_thumbnail"/>
+    </FrameLayout>
+
+</org.leafos.customization.picker.font.FontSectionView>
diff --git a/res/layout/fragment_font_picker.xml b/res/layout/fragment_font_picker.xml
new file mode 100644
index 0000000..8138462
--- /dev/null
+++ b/res/layout/fragment_font_picker.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <include layout="@layout/section_header"/>
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/content_section"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <FrameLayout
+                android:id="@+id/preview_card_container"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:clipToPadding="false"
+                android:paddingTop="@dimen/preview_page_top_margin"
+                android:paddingBottom="@dimen/preview_page_bottom_margin"
+                app:layout_constrainedHeight="true"
+                app:layout_constraintBottom_toTopOf="@+id/options_container"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent">
+
+                <include layout="@layout/font_preview_card" />
+            </FrameLayout>
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/options_container"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/options_container_height"
+                android:layout_marginBottom="@dimen/grid_options_container_bottom_margin"
+                android:paddingHorizontal="@dimen/grid_options_container_horizontal_margin"
+                android:clipToPadding="false"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <androidx.core.widget.ContentLoadingProgressBar
+            android:id="@+id/loading_indicator"
+            style="@android:style/Widget.DeviceDefault.ProgressBar"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="200dp"
+            android:layout_gravity="center_horizontal|top"
+            android:indeterminate="true"/>
+        <FrameLayout
+            android:id="@+id/error_section"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone">
+            <TextView
+                android:id="@+id/error_message"
+                style="@style/TitleTextAppearance"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:gravity="center"
+                android:text="@string/something_went_wrong"/>
+        </FrameLayout>
+    </FrameLayout>
+</LinearLayout>
diff --git a/res/layout/fragment_icon_pack_picker.xml b/res/layout/fragment_icon_pack_picker.xml
new file mode 100644
index 0000000..d4472a7
--- /dev/null
+++ b/res/layout/fragment_icon_pack_picker.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <include layout="@layout/section_header"/>
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/content_section"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <FrameLayout
+                android:id="@+id/preview_card_container"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:clipToPadding="false"
+                android:paddingTop="@dimen/preview_page_top_margin"
+                android:paddingBottom="@dimen/preview_page_bottom_margin"
+                app:layout_constrainedHeight="true"
+                app:layout_constraintBottom_toTopOf="@+id/options_container"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent">
+
+                <include layout="@layout/icon_preview_card" />
+            </FrameLayout>
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/options_container"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/options_container_height"
+                android:layout_marginBottom="@dimen/grid_options_container_bottom_margin"
+                android:paddingHorizontal="@dimen/grid_options_container_horizontal_margin"
+                android:clipToPadding="false"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <androidx.core.widget.ContentLoadingProgressBar
+            android:id="@+id/loading_indicator"
+            style="@android:style/Widget.DeviceDefault.ProgressBar"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="200dp"
+            android:layout_gravity="center_horizontal|top"
+            android:indeterminate="true"/>
+        <FrameLayout
+            android:id="@+id/error_section"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone">
+            <TextView
+                android:id="@+id/error_message"
+                style="@style/TitleTextAppearance"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:gravity="center"
+                android:text="@string/something_went_wrong"/>
+        </FrameLayout>
+    </FrameLayout>
+</LinearLayout>
diff --git a/res/layout/fragment_icon_shape_picker.xml b/res/layout/fragment_icon_shape_picker.xml
new file mode 100644
index 0000000..a573c84
--- /dev/null
+++ b/res/layout/fragment_icon_shape_picker.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <include layout="@layout/section_header"/>
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/content_section"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <FrameLayout
+                android:id="@+id/preview_card_container"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:clipToPadding="false"
+                android:paddingTop="@dimen/preview_page_top_margin"
+                android:paddingBottom="@dimen/preview_page_bottom_margin"
+                app:layout_constrainedHeight="true"
+                app:layout_constraintBottom_toTopOf="@+id/options_container"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent">
+
+                <include layout="@layout/icon_shape_preview_card" />
+            </FrameLayout>
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/options_container"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/options_container_height"
+                android:layout_marginBottom="@dimen/grid_options_container_bottom_margin"
+                android:paddingHorizontal="@dimen/grid_options_container_horizontal_margin"
+                android:clipToPadding="false"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <androidx.core.widget.ContentLoadingProgressBar
+            android:id="@+id/loading_indicator"
+            style="@android:style/Widget.DeviceDefault.ProgressBar"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="200dp"
+            android:layout_gravity="center_horizontal|top"
+            android:indeterminate="true"/>
+        <FrameLayout
+            android:id="@+id/error_section"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone">
+            <TextView
+                android:id="@+id/error_message"
+                style="@style/TitleTextAppearance"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:gravity="center"
+                android:text="@string/something_went_wrong"/>
+        </FrameLayout>
+    </FrameLayout>
+</LinearLayout>
diff --git a/res/layout/icon_preview_card.xml b/res/layout/icon_preview_card.xml
new file mode 100644
index 0000000..9c0183f
--- /dev/null
+++ b/res/layout/icon_preview_card.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+          http://www.apache.org/licenses/LICENSE-2.0
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<androidx.cardview.widget.CardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/FullContentPreviewCard"
+    android:id="@+id/icon_preview_card"
+    android:contentDescription="@string/icon_preview_content_description"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center">
+
+    <FrameLayout
+        android:id="@+id/theme_preview_card_body_container"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/preview_theme_content_max_height"
+        android:layout_marginTop="@dimen/preview_theme_content_margin"
+        android:clipChildren="false"
+        android:importantForAccessibility="noHideDescendants">
+
+        <include layout="@layout/preview_card_icon_content" />
+
+   </FrameLayout>
+</androidx.cardview.widget.CardView>
diff --git a/res/layout/icon_section_view.xml b/res/layout/icon_section_view.xml
new file mode 100644
index 0000000..3a2c486
--- /dev/null
+++ b/res/layout/icon_section_view.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<org.leafos.customization.picker.iconpack.IconPackSectionView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:paddingBottom="@dimen/section_bottom_padding"
+    android:paddingHorizontal="@dimen/section_horizontal_padding"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/preview_name_icon"
+            style="@style/SectionTitleTextStyle" />
+
+        <TextView
+            android:id="@+id/icon_section_description"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="@style/SectionSubtitleTextStyle"/>
+    </LinearLayout>
+
+    <ImageView
+        android:id="@+id/icon_section_tile"
+        android:layout_width="@dimen/option_tile_width"
+        android:layout_height="@dimen/option_tile_width"
+        android:scaleType="center"
+        android:background="@drawable/option_border_color" />
+
+</org.leafos.customization.picker.iconpack.IconPackSectionView>
diff --git a/res/layout/icon_shape_preview_card.xml b/res/layout/icon_shape_preview_card.xml
new file mode 100644
index 0000000..7c70ec3
--- /dev/null
+++ b/res/layout/icon_shape_preview_card.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+          http://www.apache.org/licenses/LICENSE-2.0
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<androidx.cardview.widget.CardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/FullContentPreviewCard"
+    android:id="@+id/icon_shape_preview_card"
+    android:contentDescription="@string/shape_preview_content_description"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center">
+
+    <FrameLayout
+        android:id="@+id/theme_preview_card_body_container"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/preview_theme_content_max_height"
+        android:layout_marginTop="@dimen/preview_theme_content_margin"
+        android:clipChildren="false"
+        android:importantForAccessibility="noHideDescendants">
+
+        <include layout="@layout/preview_card_shape_content" />
+
+   </FrameLayout>
+</androidx.cardview.widget.CardView>
diff --git a/res/layout/icon_shape_section_view.xml b/res/layout/icon_shape_section_view.xml
new file mode 100644
index 0000000..7f55a06
--- /dev/null
+++ b/res/layout/icon_shape_section_view.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<org.leafos.customization.picker.iconshape.IconShapeSectionView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:paddingBottom="@dimen/section_bottom_padding"
+    android:paddingHorizontal="@dimen/section_horizontal_padding"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/preview_name_shape"
+            style="@style/SectionTitleTextStyle" />
+
+        <TextView
+            android:id="@+id/icon_section_description"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="@style/SectionSubtitleTextStyle"/>
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@+id/icon_option_tile"
+        android:layout_width="@dimen/option_tile_width"
+        android:layout_height="@dimen/option_tile_width"
+        android:layout_gravity="center_horizontal"
+        android:paddingHorizontal="@dimen/option_tile_padding_horizontal"
+        android:paddingVertical="@dimen/option_tile_padding_vertical"
+        android:background="@drawable/option_border_color">
+        <ImageView
+            android:id="@+id/icon_section_tile"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"/>
+    </FrameLayout>
+
+</org.leafos.customization.picker.iconshape.IconShapeSectionView>
diff --git a/res/layout/theme_font_option.xml b/res/layout/theme_font_option.xml
new file mode 100644
index 0000000..df03df6
--- /dev/null
+++ b/res/layout/theme_font_option.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              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:paddingHorizontal="@dimen/option_tile_padding_horizontal"
+        android:paddingVertical="@dimen/option_tile_padding_vertical"
+        android:layout_marginHorizontal="@dimen/component_options_margin_horizontal"
+        android:background="@drawable/option_border">
+        <TextView
+            android:id="@+id/thumbnail_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:textSize="@dimen/font_comonent_option_thumbnail_size"
+            android:textAlignment="center"
+            android:textColor="?android:attr/colorForeground"
+            android:text="@string/font_component_option_thumbnail"/>
+    </FrameLayout>
+
+    <TextView
+        android:id="@+id/option_label"
+        android:layout_width="wrap_content"
+        android:layout_height="24dp"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginTop="@dimen/theme_option_label_margin"
+        android:gravity="center"
+        android:textAppearance="@style/OptionTitleTextAppearance" />
+</LinearLayout>
diff --git a/res/layout/theme_icon_option.xml b/res/layout/theme_icon_option.xml
new file mode 100644
index 0000000..020ebc7
--- /dev/null
+++ b/res/layout/theme_icon_option.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              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:paddingHorizontal="@dimen/option_tile_padding_horizontal"
+        android:paddingVertical="@dimen/option_tile_padding_vertical"
+        android:layout_marginHorizontal="@dimen/component_options_margin_horizontal"
+        android:background="@drawable/option_border">
+        <ImageView
+            android:id="@+id/option_icon"
+            android:layout_width="@dimen/component_icon_thumb_size"
+            android:layout_height="@dimen/component_icon_thumb_size"
+            android:layout_gravity="center"
+            android:tint="?android:colorForeground"/>
+    </FrameLayout>
+
+    <TextView
+        android:id="@+id/option_label"
+        android:layout_width="wrap_content"
+        android:layout_height="24dp"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginTop="@dimen/theme_option_label_margin"
+        android:gravity="center"
+        android:textAppearance="@style/OptionTitleTextAppearance" />
+</LinearLayout>
diff --git a/res/layout/theme_shape_option.xml b/res/layout/theme_shape_option.xml
new file mode 100644
index 0000000..61456f0
--- /dev/null
+++ b/res/layout/theme_shape_option.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              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:paddingHorizontal="@dimen/option_tile_padding_horizontal"
+        android:paddingVertical="@dimen/option_tile_padding_vertical"
+        android:layout_marginHorizontal="@dimen/component_options_margin_horizontal"
+        android:background="@drawable/option_border">
+        <ImageView
+            android:id="@+id/shape_thumbnail"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"/>
+    </FrameLayout>
+
+    <TextView
+        android:id="@+id/option_label"
+        android:layout_width="wrap_content"
+        android:layout_height="24dp"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginTop="@dimen/theme_option_label_margin"
+        android:gravity="center"
+        android:textAppearance="@style/OptionTitleTextAppearance" />
+</LinearLayout>
diff --git a/src/org/leafos/customization/model/font/FontManager.java b/src/org/leafos/customization/model/font/FontManager.java
new file mode 100644
index 0000000..5186913
--- /dev/null
+++ b/src/org/leafos/customization/model/font/FontManager.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.leafos.customization.model.font;
+
+import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import com.android.customization.model.CustomizationManager;
+import com.android.customization.model.theme.OverlayManagerCompat;
+
+import java.util.Map;
+import java.util.List;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class FontManager implements CustomizationManager<FontOption> {
+
+    private static FontManager sFontOptionManager;
+    private Context mContext;
+    private FontOption mActiveOption;
+    private OverlayManagerCompat mOverlayManager;
+    private FontOptionProvider mProvider;
+    private static final String TAG = "FontManager";
+    private static final String KEY_STATE_CURRENT_SELECTION = "FontManager.currentSelection";
+
+    FontManager(Context context, OverlayManagerCompat overlayManager, FontOptionProvider provider) {
+        mContext = context;
+        mProvider = provider;
+        mOverlayManager = overlayManager;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mOverlayManager.isAvailable();
+    }
+
+    @Override
+    public void apply(FontOption option, @Nullable Callback callback) {
+        if (!persistOverlay(option)) {
+            Toast failed = Toast.makeText(mContext, "Failed to apply font, reboot to try again.", Toast.LENGTH_SHORT);
+            failed.show();
+            if (callback != null) {
+                callback.onError(null);
+            }
+            return;
+        }
+        if (option.getPackageName() == null) {
+            if (mActiveOption.getPackageName() == null) return;
+            for (String overlay : mOverlayManager.getOverlayPackagesForCategory(
+                    OVERLAY_CATEGORY_FONT, UserHandle.myUserId(), ANDROID_PACKAGE)) {
+                mOverlayManager.disableOverlay(overlay, UserHandle.myUserId());
+            }
+        } else {
+            mOverlayManager.setEnabledExclusiveInCategory(option.getPackageName(), UserHandle.myUserId());
+        }
+        if (callback != null) {
+            callback.onSuccess();
+        }
+        mActiveOption = option;
+    }
+
+    @Override
+    public void fetchOptions(OptionsFetchedListener<FontOption> callback, boolean reload) {
+        List<FontOption> options = mProvider.getOptions(reload);
+        for (FontOption option : options) {
+            if (isActive(option)) {
+                mActiveOption = option;
+                break;
+            }
+        }
+        callback.onOptionsLoaded(options);
+    }
+
+    public OverlayManagerCompat getOverlayManager() {
+        return mOverlayManager;
+    }
+
+    public boolean isActive(FontOption option) {
+        String enabledPkg = mOverlayManager.getEnabledPackageName(ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT);
+        if (enabledPkg != null) {
+            return enabledPkg.equals(option.getPackageName());
+        } else {
+            return option.getPackageName() == null;
+        }
+    }
+
+    private boolean persistOverlay(FontOption toPersist) {
+        String value = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, UserHandle.USER_CURRENT);
+        JSONObject json;
+        if (value == null) {
+            json = new JSONObject();
+        } else {
+            try {
+                json = new JSONObject(value);
+            } catch (JSONException e) {
+                Log.e(TAG, "Error parsing current settings value:\n" + e.getMessage());
+                return false;
+            }
+        }
+        // removing all currently enabled overlays from the json
+        json.remove(OVERLAY_CATEGORY_FONT);
+        // adding the new ones
+        try {
+            json.put(OVERLAY_CATEGORY_FONT, toPersist.getPackageName());
+        } catch (JSONException e) {
+            Log.e(TAG, "Error adding new settings value:\n" + e.getMessage());
+            return false;
+        }
+        // updating the setting
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+                json.toString(), UserHandle.USER_CURRENT);
+        return true;
+    }
+
+    public static FontManager getInstance(Context context, OverlayManagerCompat overlayManager) {
+        if (sFontOptionManager == null) {
+            Context applicationContext = context.getApplicationContext();
+            sFontOptionManager = new FontManager(context, overlayManager, new FontOptionProvider(applicationContext, overlayManager));
+        }
+        return sFontOptionManager;
+    }
+
+}
diff --git a/src/org/leafos/customization/model/font/FontOption.java b/src/org/leafos/customization/model/font/FontOption.java
new file mode 100644
index 0000000..5736b31
--- /dev/null
+++ b/src/org/leafos/customization/model/font/FontOption.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.leafos.customization.model.font;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.Typeface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.wallpaper.R;
+import com.android.wallpaper.util.ResourceUtils;
+
+import com.android.customization.model.CustomizationManager;
+import com.android.customization.model.CustomizationOption;
+import com.android.customization.model.ResourceConstants;
+import com.android.customization.model.theme.OverlayManagerCompat;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public class FontOption implements CustomizationOption<FontOption> {
+
+    private final Typeface mHeadlineFont;
+    private final Typeface mBodyFont;
+    private String mTitle;
+    private String mOverlayPackage;
+
+    public FontOption(String overlayPackage, String label, Typeface headlineFont, Typeface bodyFont) {
+        mTitle = label;
+        mHeadlineFont = headlineFont;
+        mBodyFont = bodyFont;
+        mOverlayPackage = overlayPackage;
+    }
+
+    @Override
+    public void bindThumbnailTile(View view) {
+        Resources res = view.getContext().getResources();
+        ((TextView) view.findViewById(R.id.thumbnail_text)).setTypeface(
+                mHeadlineFont);
+        int colorFilter = ResourceUtils.getColorAttr(view.getContext(),
+                view.isActivated() || view.getId() == R.id.font_section_tile
+                        ? android.R.attr.textColorPrimary
+                        : android.R.attr.textColorTertiary);
+        ((TextView) view.findViewById(R.id.thumbnail_text)).setTextColor(colorFilter);
+        view.setContentDescription(mTitle);
+    }
+
+    @Override
+    public boolean isActive(CustomizationManager<FontOption> manager) {
+        FontManager fontManager = (FontManager) manager;
+        return fontManager.isActive(this);
+    }
+
+    @Override
+    public int getLayoutResId() {
+        return R.layout.theme_font_option;
+    }
+
+    @Override
+    public String getTitle() {
+        return mTitle;
+    }
+
+    public String getPackageName() {
+        return mOverlayPackage;
+    }
+
+    public void bindPreview(ViewGroup container) {
+        ViewGroup cardBody = container.findViewById(R.id.theme_preview_card_body_container);
+        if (cardBody.getChildCount() == 0) {
+            LayoutInflater.from(container.getContext()).inflate(
+                    R.layout.preview_card_font_content, cardBody, true);
+        }
+        TextView title = container.findViewById(R.id.font_card_title);
+        title.setTypeface(mHeadlineFont);
+        TextView bodyText = container.findViewById(R.id.font_card_body);
+        bodyText.setTypeface(mBodyFont);
+        container.findViewById(R.id.font_card_divider).setBackgroundColor(
+                title.getCurrentTextColor());
+    }
+}
diff --git a/src/org/leafos/customization/model/font/FontOptionProvider.java b/src/org/leafos/customization/model/font/FontOptionProvider.java
new file mode 100644
index 0000000..5d65bb9
--- /dev/null
+++ b/src/org/leafos/customization/model/font/FontOptionProvider.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.leafos.customization.model.font;
+
+import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
+import static com.android.customization.model.ResourceConstants.CONFIG_BODY_FONT_FAMILY;
+import static com.android.customization.model.ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT;
+
+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.Typeface;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.customization.model.ResourceConstants;
+import com.android.customization.model.theme.OverlayManagerCompat;
+import com.android.wallpaper.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class FontOptionProvider {
+
+    private static final String TAG = "FontOptionProvider";
+
+    private Context mContext;
+    private PackageManager mPm;
+    private final List<String> mOverlayPackages;
+    private final List<FontOption> mOptions = new ArrayList<>();
+    private String mActiveOverlay;
+
+    public FontOptionProvider(Context context, OverlayManagerCompat manager) {
+        mContext = context;
+        mPm = context.getPackageManager();
+        mOverlayPackages = new ArrayList<>();
+        mOverlayPackages.addAll(manager.getOverlayPackagesForCategory(OVERLAY_CATEGORY_FONT,
+                UserHandle.myUserId(), ResourceConstants.getPackagesToOverlay(mContext)));
+        mActiveOverlay = manager.getEnabledPackageName(ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT);
+    }
+
+    public List<FontOption> getOptions(boolean reload) {
+        if (reload) mOptions.clear();
+        if (mOptions.isEmpty()) loadOptions();
+        return mOptions;
+    }
+
+    private void loadOptions() {
+        addDefault();
+        for (String overlayPackage : mOverlayPackages) {
+            try {
+                Resources overlayRes = mPm.getResourcesForApplication(overlayPackage);
+                Typeface headlineFont = Typeface.create(
+                        getFontFamily(overlayPackage, overlayRes, CONFIG_HEADLINE_FONT_FAMILY),
+                        Typeface.NORMAL);
+                Typeface bodyFont = Typeface.create(
+                        getFontFamily(overlayPackage, overlayRes, CONFIG_BODY_FONT_FAMILY),
+                        Typeface.NORMAL);
+                String label = mPm.getApplicationInfo(overlayPackage, 0).loadLabel(mPm).toString();
+                mOptions.add(new FontOption(overlayPackage, label, headlineFont, bodyFont));
+            } catch (NameNotFoundException | NotFoundException e) {
+                Log.w(TAG, String.format("Couldn't load font overlay %s, will skip it",
+                        overlayPackage), e);
+            }
+        }
+    }
+
+    private void addDefault() {
+        Resources system = Resources.getSystem();
+        Typeface headlineFont = Typeface.create(system.getString(system.getIdentifier(
+                ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY,"string", ANDROID_PACKAGE)),
+                Typeface.NORMAL);
+        Typeface bodyFont = Typeface.create(system.getString(system.getIdentifier(
+                ResourceConstants.CONFIG_BODY_FONT_FAMILY,
+                "string", ANDROID_PACKAGE)),
+                Typeface.NORMAL);
+        mOptions.add(new FontOption(null, mContext.getString(R.string.default_theme_title),
+                headlineFont, bodyFont));
+    }
+
+    private String getFontFamily(String overlayPackage, Resources overlayRes, String configName) {
+        return overlayRes.getString(overlayRes.getIdentifier(configName, "string", overlayPackage));
+    }
+}
diff --git a/src/org/leafos/customization/model/font/FontSectionController.java b/src/org/leafos/customization/model/font/FontSectionController.java
new file mode 100644
index 0000000..19b6c54
--- /dev/null
+++ b/src/org/leafos/customization/model/font/FontSectionController.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2021 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 org.leafos.customization.model.font;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.customization.model.CustomizationManager.Callback;
+import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
+import com.android.customization.model.CustomizationOption;
+import com.android.customization.widget.OptionSelectorController;
+import com.android.customization.widget.OptionSelectorController.OptionSelectedListener;
+
+import com.android.wallpaper.R;
+import com.android.wallpaper.model.CustomizationSectionController;
+import com.android.wallpaper.util.LaunchUtils;
+
+import org.leafos.customization.picker.font.FontFragment;
+import org.leafos.customization.picker.font.FontSectionView;
+
+import java.util.List;
+
+/** A {@link CustomizationSectionController} for system fonts. */
+
+public class FontSectionController implements CustomizationSectionController<FontSectionView> {
+
+    private static final String TAG = "FontSectionController";
+
+    private final FontManager mFontOptionsManager;
+    private final CustomizationSectionNavigationController mSectionNavigationController;
+    private final Callback mApplyFontCallback = new Callback() {
+        @Override
+        public void onSuccess() {
+        }
+
+        @Override
+        public void onError(@Nullable Throwable throwable) {
+        }
+    };
+
+    public FontSectionController(FontManager fontOptionsManager,
+            CustomizationSectionNavigationController sectionNavigationController) {
+        mFontOptionsManager = fontOptionsManager;
+        mSectionNavigationController = sectionNavigationController;
+    }
+
+    @Override
+    public boolean isAvailable(Context context) {
+        return mFontOptionsManager.isAvailable();
+    }
+
+    @Override
+    public FontSectionView createView(Context context) {
+        FontSectionView fontSectionView = (FontSectionView) LayoutInflater.from(context)
+                .inflate(R.layout.font_section_view, /* root= */ null);
+
+        TextView sectionDescription = fontSectionView.findViewById(R.id.font_section_description);
+        View sectionTile = fontSectionView.findViewById(R.id.font_section_tile);
+
+        mFontOptionsManager.fetchOptions(new OptionsFetchedListener<FontOption>() {
+            @Override
+            public void onOptionsLoaded(List<FontOption> options) {
+                FontOption activeOption = getActiveOption(options);
+                sectionDescription.setText(activeOption.getTitle());
+                activeOption.bindThumbnailTile(sectionTile);
+            }
+
+            @Override
+            public void onError(@Nullable Throwable throwable) {
+                if (throwable != null) {
+                    Log.e(TAG, "Error loading font options", throwable);
+                }
+                sectionDescription.setText(R.string.something_went_wrong);
+                sectionTile.setVisibility(View.GONE);
+            }
+        }, /* reload= */ true);
+
+        fontSectionView.setOnClickListener(v -> mSectionNavigationController.navigateTo(
+                FontFragment.newInstance(context.getString(R.string.preview_name_font))));
+
+        return fontSectionView;
+    }
+
+    private FontOption getActiveOption(List<FontOption> options) {
+        return options.stream()
+                .filter(option -> mFontOptionsManager.isActive(option))
+                .findAny()
+                // For development only, as there should always be a grid set.
+                .orElse(options.get(0));
+    }
+}
diff --git a/src/org/leafos/customization/model/iconpack/IconPackManager.java b/src/org/leafos/customization/model/iconpack/IconPackManager.java
new file mode 100644
index 0000000..039be7b
--- /dev/null
+++ b/src/org/leafos/customization/model/iconpack/IconPackManager.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.leafos.customization.model.iconpack;
+
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_ANDROID;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SETTINGS;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SYSUI;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import com.android.customization.model.CustomizationManager;
+import com.android.customization.model.theme.OverlayManagerCompat;
+
+import java.util.Map;
+import java.util.List;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class IconPackManager implements CustomizationManager<IconPackOption> {
+
+    private static IconPackManager sIconPackOptionManager;
+    private Context mContext;
+    private IconPackOption mActiveOption;
+    private OverlayManagerCompat mOverlayManager;
+    private IconPackOptionProvider mProvider;
+    private static final String TAG = "IconPackManager";
+    private static final String KEY_STATE_CURRENT_SELECTION = "IconPackManager.currentSelection";
+    private static final String[] mCurrentCategories = new String[]{OVERLAY_CATEGORY_ICON_ANDROID, OVERLAY_CATEGORY_ICON_SETTINGS, OVERLAY_CATEGORY_ICON_SYSUI};
+
+    IconPackManager(Context context, OverlayManagerCompat overlayManager, IconPackOptionProvider provider) {
+        mContext = context;
+        mProvider = provider;
+        mOverlayManager = overlayManager;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mOverlayManager.isAvailable();
+    }
+
+    @Override
+    public void apply(IconPackOption option, @Nullable Callback callback) {
+        if (!persistOverlay(option)) {
+            Toast failed = Toast.makeText(mContext, "Failed to apply icon pack, reboot to try again.", Toast.LENGTH_SHORT);
+            failed.show();
+            if (callback != null) {
+                callback.onError(null);
+            }
+            return;
+        }
+        if (option.getTitle().equals("Default")) {
+            if (mActiveOption.getTitle().equals("Default")) return;
+            mActiveOption.getOverlayPackages().forEach((category, overlay) -> mOverlayManager.disableOverlay(overlay, UserHandle.myUserId()));
+        } else {
+            option.getOverlayPackages().forEach((category, overlay) -> mOverlayManager.setEnabledExclusiveInCategory(overlay, UserHandle.myUserId()));
+        }
+        if (callback != null) {
+            callback.onSuccess();
+        }
+        mActiveOption = option;
+    }
+
+    @Override
+    public void fetchOptions(OptionsFetchedListener<IconPackOption> callback, boolean reload) {
+        List<IconPackOption> options = mProvider.getOptions();
+        for (IconPackOption option : options) {
+            if (option.isActive(this)) {
+                mActiveOption = option;
+                break;
+            }
+        }
+        callback.onOptionsLoaded(options);
+    }
+
+    public OverlayManagerCompat getOverlayManager() {
+        return mOverlayManager;
+    }
+
+    private boolean persistOverlay(IconPackOption toPersist) {
+        String value = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, UserHandle.USER_CURRENT);
+        JSONObject json;
+        if (value == null) {
+            json = new JSONObject();
+        } else {
+            try {
+                json = new JSONObject(value);
+            } catch (JSONException e) {
+                Log.e(TAG, "Error parsing current settings value:\n" + e.getMessage());
+                return false;
+            }
+        }
+        // removing all currently enabled overlays from the json
+        for (String categoryName : mCurrentCategories) {
+            json.remove(categoryName);
+        }
+        // adding the new ones
+        for (String categoryName : mCurrentCategories) {
+            try {
+                json.put(categoryName, toPersist.getOverlayPackages().get(categoryName));
+            } catch (JSONException e) {
+                Log.e(TAG, "Error adding new settings value:\n" + e.getMessage());
+                return false;
+            }
+        }
+        // updating the setting
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+                json.toString(), UserHandle.USER_CURRENT);
+        return true;
+    }
+
+    public static IconPackManager getInstance(Context context, OverlayManagerCompat overlayManager) {
+        if (sIconPackOptionManager == null) {
+            Context applicationContext = context.getApplicationContext();
+            sIconPackOptionManager = new IconPackManager(context, overlayManager, new IconPackOptionProvider(applicationContext, overlayManager));
+        }
+        return sIconPackOptionManager;
+    }
+
+}
diff --git a/src/org/leafos/customization/model/iconpack/IconPackOption.java b/src/org/leafos/customization/model/iconpack/IconPackOption.java
new file mode 100644
index 0000000..6367fc5
--- /dev/null
+++ b/src/org/leafos/customization/model/iconpack/IconPackOption.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.leafos.customization.model.iconpack;
+
+import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
+import static com.android.customization.model.ResourceConstants.SETTINGS_PACKAGE;
+import static com.android.customization.model.ResourceConstants.SYSUI_PACKAGE;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_ANDROID;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SETTINGS;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SYSUI;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.PorterDuff.Mode;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.wallpaper.R;
+import com.android.wallpaper.util.ResourceUtils;
+
+import com.android.customization.model.CustomizationManager;
+import com.android.customization.model.CustomizationOption;
+import com.android.customization.model.ResourceConstants;
+import com.android.customization.model.theme.OverlayManagerCompat;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public class IconPackOption implements CustomizationOption<IconPackOption> {
+
+    public static final int THUMBNAIL_ICON_POSITION = 0;
+    private static int[] mIconIds = {
+            R.id.preview_icon_0, R.id.preview_icon_1, R.id.preview_icon_2, R.id.preview_icon_3,
+            R.id.preview_icon_4, R.id.preview_icon_5
+    };
+
+    private List<Drawable> mIcons = new ArrayList<>();
+    private String mTitle;
+    // Mapping from category to overlay package name
+    private final Map<String, String> mOverlayPackageNames = new HashMap<>();
+
+    public IconPackOption(String title) {
+        mTitle = title;
+    }
+
+    @Override
+    public void bindThumbnailTile(View view) {
+        Resources res = view.getContext().getResources();
+        Drawable icon = mIcons.get(THUMBNAIL_ICON_POSITION)
+                .getConstantState().newDrawable().mutate();
+        int colorFilter = ResourceUtils.getColorAttr(view.getContext(),
+                android.R.attr.textColorPrimary);
+        int resId = R.id.icon_section_tile;
+        if (view.findViewById(R.id.option_icon) != null) {
+            resId = R.id.option_icon;
+            colorFilter = ResourceUtils.getColorAttr(view.getContext(),
+                view.isActivated() ? android.R.attr.textColorPrimary :
+                android.R.attr.textColorTertiary);
+        }
+        icon.setColorFilter(colorFilter, Mode.SRC_ATOP);
+        ((ImageView) view.findViewById(resId)).setImageDrawable(icon);
+        view.setContentDescription(mTitle);
+    }
+
+    @Override
+    public boolean isActive(CustomizationManager<IconPackOption> manager) {
+        IconPackManager iconManager = (IconPackManager) manager;
+        OverlayManagerCompat overlayManager = iconManager.getOverlayManager();
+        if (mTitle.equals("Default")) {
+            return overlayManager.getEnabledPackageName(SYSUI_PACKAGE, OVERLAY_CATEGORY_ICON_SYSUI) == null &&
+                    overlayManager.getEnabledPackageName(SETTINGS_PACKAGE, OVERLAY_CATEGORY_ICON_SETTINGS) == null &&
+                    overlayManager.getEnabledPackageName(ANDROID_PACKAGE, OVERLAY_CATEGORY_ICON_ANDROID) == null;
+        }
+        for (Map.Entry<String, String> overlayEntry : getOverlayPackages().entrySet()) {
+            if(overlayEntry.getValue() == null || !overlayEntry.getValue().equals(overlayManager.getEnabledPackageName(determinePackage(overlayEntry.getKey()), overlayEntry.getKey()))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int getLayoutResId() {
+        return R.layout.theme_icon_option;
+    }
+
+    @Override
+    public String getTitle() {
+        return mTitle;
+    }
+
+    public void bindPreview(ViewGroup container) {
+        ViewGroup cardBody = container.findViewById(R.id.theme_preview_card_body_container);
+        if (cardBody.getChildCount() == 0) {
+            LayoutInflater.from(container.getContext()).inflate(
+                    R.layout.preview_card_icon_content, cardBody, true);
+        }
+        for (int i = 0; i < mIconIds.length && i < mIcons.size(); i++) {
+            ((ImageView) container.findViewById(mIconIds[i])).setImageDrawable(
+                    mIcons.get(i));
+        }
+    }
+
+    private String determinePackage(String category) {
+       switch(category) {
+           case OVERLAY_CATEGORY_ICON_SYSUI:
+               return SYSUI_PACKAGE;
+           case OVERLAY_CATEGORY_ICON_SETTINGS:
+               return SETTINGS_PACKAGE;
+           case OVERLAY_CATEGORY_ICON_ANDROID:
+               return ANDROID_PACKAGE;
+           default:
+               return null;
+       }
+    }
+
+    public void addIcon(Drawable previewIcon) {
+        mIcons.add(previewIcon);
+    }
+
+    public void addOverlayPackage(String category, String overlayPackage) {
+        mOverlayPackageNames.put(category, overlayPackage);
+    }
+
+    public Map<String, String> getOverlayPackages() {
+        return mOverlayPackageNames;
+    }
+
+    /**
+     * @return whether this icon option has overlays and previews for all the required packages
+     */
+    public boolean isValid(Context context) {
+        return mOverlayPackageNames.keySet().size() == 3;
+    }
+}
diff --git a/src/org/leafos/customization/model/iconpack/IconPackOptionProvider.java b/src/org/leafos/customization/model/iconpack/IconPackOptionProvider.java
new file mode 100644
index 0000000..68f5a33
--- /dev/null
+++ b/src/org/leafos/customization/model/iconpack/IconPackOptionProvider.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.leafos.customization.model.iconpack;
+
+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_ICON_ANDROID;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SETTINGS;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SYSUI;
+
+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.os.UserHandle;
+import android.util.Log;
+
+import com.android.customization.model.ResourceConstants;
+import com.android.customization.model.theme.OverlayManagerCompat;
+import com.android.wallpaper.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class IconPackOptionProvider {
+
+    private static final String TAG = "IconPackOptionProvider";
+
+    private Context mContext;
+    private PackageManager mPm;
+    private final List<String> mOverlayPackages;
+    private final List<IconPackOption> mOptions = new ArrayList<>();
+    private final List<String> mSysUiIconsOverlayPackages = new ArrayList<>();
+    private final List<String> mSettingsIconsOverlayPackages = new ArrayList<>();
+
+    public IconPackOptionProvider(Context context, OverlayManagerCompat manager) {
+        mContext = context;
+        mPm = context.getPackageManager();
+        String[] targetPackages = ResourceConstants.getPackagesToOverlay(context);
+        mSysUiIconsOverlayPackages.addAll(manager.getOverlayPackagesForCategory(
+                OVERLAY_CATEGORY_ICON_SYSUI, UserHandle.myUserId(), targetPackages));
+        mSettingsIconsOverlayPackages.addAll(manager.getOverlayPackagesForCategory(
+                OVERLAY_CATEGORY_ICON_SETTINGS, UserHandle.myUserId(), targetPackages));
+        mOverlayPackages = new ArrayList<>();
+        mOverlayPackages.addAll(manager.getOverlayPackagesForCategory(OVERLAY_CATEGORY_ICON_ANDROID,
+                UserHandle.myUserId(), ResourceConstants.getPackagesToOverlay(mContext)));
+    }
+
+    public List<IconPackOption> getOptions() {
+        if (mOptions.isEmpty()) loadOptions();
+        return mOptions;
+    }
+
+    private void loadOptions() {
+        addDefault();
+
+        Map<String, IconPackOption> optionsByPrefix = new HashMap<>();
+        for (String overlayPackage : mOverlayPackages) {
+            IconPackOption option = addOrUpdateOption(optionsByPrefix, overlayPackage,
+                    OVERLAY_CATEGORY_ICON_ANDROID);
+            try{
+                for (String iconName : ICONS_FOR_PREVIEW) {
+                    option.addIcon(loadIconPreviewDrawable(iconName, overlayPackage));
+                }
+            } catch (NotFoundException | NameNotFoundException e) {
+                Log.w(TAG, String.format("Couldn't load icon overlay details for %s, will skip it",
+                        overlayPackage), e);
+            }
+        }
+
+        for (String overlayPackage : mSysUiIconsOverlayPackages) {
+            addOrUpdateOption(optionsByPrefix, overlayPackage, OVERLAY_CATEGORY_ICON_SYSUI);
+        }
+
+        for (String overlayPackage : mSettingsIconsOverlayPackages) {
+            addOrUpdateOption(optionsByPrefix, overlayPackage, OVERLAY_CATEGORY_ICON_SETTINGS);
+        }
+
+        for (IconPackOption option : optionsByPrefix.values()) {
+            if (option.isValid(mContext)) {
+                mOptions.add(option);
+            }
+        }
+    }
+
+    private IconPackOption addOrUpdateOption(Map<String, IconPackOption> optionsByPrefix,
+            String overlayPackage, String category) {
+        String prefix = overlayPackage.substring(0, overlayPackage.lastIndexOf("."));
+        IconPackOption option = null;
+        try {
+            if (!optionsByPrefix.containsKey(prefix)) {
+                option = new IconPackOption(mPm.getApplicationInfo(overlayPackage, 0).loadLabel(mPm).toString());
+                optionsByPrefix.put(prefix, option);
+            } else {
+                option = optionsByPrefix.get(prefix);
+            }
+            option.addOverlayPackage(category, overlayPackage);
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, String.format("Package %s not found", overlayPackage), e);
+        }
+        return option;
+    }
+
+    private Drawable loadIconPreviewDrawable(String drawableName, String packageName)
+            throws NameNotFoundException, NotFoundException {
+        final Resources resources = ANDROID_PACKAGE.equals(packageName)
+                ? Resources.getSystem()
+                : mPm.getResourcesForApplication(packageName);
+        return resources.getDrawable(
+                resources.getIdentifier(drawableName, "drawable", packageName), null);
+    }
+
+    private void addDefault() {
+        IconPackOption option = new IconPackOption(mContext.getString(R.string.default_theme_title));
+        try {
+            for (String iconName : ICONS_FOR_PREVIEW) {
+                option.addIcon(loadIconPreviewDrawable(iconName, ANDROID_PACKAGE));
+            }
+        } catch (NameNotFoundException | NotFoundException e) {
+            Log.w(TAG, "Didn't find SystemUi package icons, will skip option", e);
+        }
+        option.addOverlayPackage(OVERLAY_CATEGORY_ICON_ANDROID, null);
+        option.addOverlayPackage(OVERLAY_CATEGORY_ICON_SYSUI, null);
+        option.addOverlayPackage(OVERLAY_CATEGORY_ICON_SETTINGS, null);
+        mOptions.add(option);
+    }
+
+}
diff --git a/src/org/leafos/customization/model/iconpack/IconPackSectionController.java b/src/org/leafos/customization/model/iconpack/IconPackSectionController.java
new file mode 100644
index 0000000..0fdb56f
--- /dev/null
+++ b/src/org/leafos/customization/model/iconpack/IconPackSectionController.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2021 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 org.leafos.customization.model.iconpack;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.customization.model.CustomizationManager.Callback;
+import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
+import com.android.customization.model.CustomizationOption;
+import com.android.customization.widget.OptionSelectorController;
+import com.android.customization.widget.OptionSelectorController.OptionSelectedListener;
+
+import com.android.wallpaper.R;
+import com.android.wallpaper.model.CustomizationSectionController;
+import com.android.wallpaper.util.LaunchUtils;
+
+import org.leafos.customization.picker.iconpack.IconPackFragment;
+import org.leafos.customization.picker.iconpack.IconPackSectionView;
+
+import java.util.List;
+
+/** A {@link CustomizationSectionController} for system icons. */
+
+public class IconPackSectionController implements CustomizationSectionController<IconPackSectionView> {
+
+    private static final String TAG = "IconPackSectionController";
+
+    private final IconPackManager mIconPackOptionsManager;
+    private final CustomizationSectionNavigationController mSectionNavigationController;
+    private final Callback mApplyIconCallback = new Callback() {
+        @Override
+        public void onSuccess() {
+        }
+
+        @Override
+        public void onError(@Nullable Throwable throwable) {
+        }
+    };
+
+    public IconPackSectionController(IconPackManager iconPackOptionsManager,
+            CustomizationSectionNavigationController sectionNavigationController) {
+        mIconPackOptionsManager = iconPackOptionsManager;
+        mSectionNavigationController = sectionNavigationController;
+    }
+
+    @Override
+    public boolean isAvailable(Context context) {
+        return mIconPackOptionsManager.isAvailable();
+    }
+
+    @Override
+    public IconPackSectionView createView(Context context) {
+        IconPackSectionView iconPackSectionView = (IconPackSectionView) LayoutInflater.from(context)
+                .inflate(R.layout.icon_section_view, /* root= */ null);
+
+        TextView sectionDescription = iconPackSectionView.findViewById(R.id.icon_section_description);
+        View sectionTile = iconPackSectionView.findViewById(R.id.icon_section_tile);
+
+        mIconPackOptionsManager.fetchOptions(new OptionsFetchedListener<IconPackOption>() {
+            @Override
+            public void onOptionsLoaded(List<IconPackOption> options) {
+                IconPackOption activeOption = getActiveOption(options);
+                sectionDescription.setText(activeOption.getTitle());
+                activeOption.bindThumbnailTile(sectionTile);
+            }
+
+            @Override
+            public void onError(@Nullable Throwable throwable) {
+                if (throwable != null) {
+                    Log.e(TAG, "Error loading icon options", throwable);
+                }
+                sectionDescription.setText(R.string.something_went_wrong);
+                sectionTile.setVisibility(View.GONE);
+            }
+        }, /* reload= */ true);
+
+        iconPackSectionView.setOnClickListener(v -> mSectionNavigationController.navigateTo(
+                IconPackFragment.newInstance(context.getString(R.string.preview_name_icon))));
+
+        return iconPackSectionView;
+    }
+
+    private IconPackOption getActiveOption(List<IconPackOption> options) {
+        return options.stream()
+                .filter(option -> option.isActive(mIconPackOptionsManager))
+                .findAny()
+                // For development only, as there should always be a grid set.
+                .orElse(options.get(0));
+    }
+}
diff --git a/src/org/leafos/customization/model/iconshape/IconShapeManager.java b/src/org/leafos/customization/model/iconshape/IconShapeManager.java
new file mode 100644
index 0000000..3ffccf7
--- /dev/null
+++ b/src/org/leafos/customization/model/iconshape/IconShapeManager.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.leafos.customization.model.iconshape;
+
+import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import com.android.customization.model.CustomizationManager;
+import com.android.customization.model.theme.OverlayManagerCompat;
+
+import java.util.List;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class IconShapeManager implements CustomizationManager<IconShapeOption> {
+
+    private static IconShapeManager sIconShapeOptionManager;
+    private Context mContext;
+    private IconShapeOption mActiveOption;
+    private OverlayManagerCompat mOverlayManager;
+    private IconShapeOptionProvider mProvider;
+    private static final String TAG = "IconShapeManager";
+    private static final String KEY_STATE_CURRENT_SELECTION = "IconShapeManager.currentSelection";
+
+    IconShapeManager(Context context, OverlayManagerCompat overlayManager, IconShapeOptionProvider provider) {
+        mContext = context;
+        mProvider = provider;
+        mOverlayManager = overlayManager;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mOverlayManager.isAvailable();
+    }
+
+    @Override
+    public void apply(IconShapeOption option, @Nullable Callback callback) {
+        if (!persistOverlay(option)) {
+            Toast failed = Toast.makeText(mContext, "Failed to apply font, reboot to try again.", Toast.LENGTH_SHORT);
+            failed.show();
+            if (callback != null) {
+                callback.onError(null);
+            }
+            return;
+        }
+        if (option.getPackageName() == null) {
+            if (mActiveOption.getPackageName() == null) return;
+            for (String overlay : mOverlayManager.getOverlayPackagesForCategory(
+                    OVERLAY_CATEGORY_SHAPE, UserHandle.myUserId(), ANDROID_PACKAGE)) {
+                mOverlayManager.disableOverlay(overlay, UserHandle.myUserId());
+            }
+        } else {
+            mOverlayManager.setEnabledExclusiveInCategory(option.getPackageName(), UserHandle.myUserId());
+        }
+        if (callback != null) {
+            callback.onSuccess();
+        }
+        mActiveOption = option;
+    }
+
+    @Override
+    public void fetchOptions(OptionsFetchedListener<IconShapeOption> callback, boolean reload) {
+        List<IconShapeOption> options = mProvider.getOptions();
+        for (IconShapeOption option : options) {
+            if (option.isActive(this)) {
+                mActiveOption = option;
+                break;
+            }
+        }
+        callback.onOptionsLoaded(options);
+    }
+
+    public OverlayManagerCompat getOverlayManager() {
+        return mOverlayManager;
+    }
+
+    private boolean persistOverlay(IconShapeOption toPersist) {
+        String value = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, UserHandle.USER_CURRENT);
+        JSONObject json;
+        if (value == null) {
+            json = new JSONObject();
+        } else {
+            try {
+                json = new JSONObject(value);
+            } catch (JSONException e) {
+                Log.e(TAG, "Error parsing current settings value:\n" + e.getMessage());
+                return false;
+            }
+        }
+        // removing all currently enabled overlays from the json
+        json.remove(OVERLAY_CATEGORY_SHAPE);
+        // adding the new ones
+        try {
+            json.put(OVERLAY_CATEGORY_SHAPE, toPersist.getPackageName());
+        } catch (JSONException e) {
+            Log.e(TAG, "Error adding new settings value:\n" + e.getMessage());
+            return false;
+        }
+        // updating the setting
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+                json.toString(), UserHandle.USER_CURRENT);
+        return true;
+    }
+
+    public static IconShapeManager getInstance(Context context, OverlayManagerCompat overlayManager) {
+        if (sIconShapeOptionManager == null) {
+            Context applicationContext = context.getApplicationContext();
+            sIconShapeOptionManager = new IconShapeManager(context, overlayManager, new IconShapeOptionProvider(applicationContext, overlayManager));
+        }
+        return sIconShapeOptionManager;
+    }
+
+}
diff --git a/src/org/leafos/customization/model/iconshape/IconShapeOption.java b/src/org/leafos/customization/model/iconshape/IconShapeOption.java
new file mode 100644
index 0000000..d5dda48
--- /dev/null
+++ b/src/org/leafos/customization/model/iconshape/IconShapeOption.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.leafos.customization.model.iconshape;
+
+import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE;
+
+import androidx.annotation.Dimension;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Path;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.core.graphics.ColorUtils;
+
+import com.android.customization.model.theme.ThemeBundle;
+import com.android.wallpaper.R;
+import com.android.wallpaper.util.ResourceUtils;
+
+import com.android.customization.model.CustomizationManager;
+import com.android.customization.model.CustomizationOption;
+import com.android.customization.model.theme.OverlayManagerCompat;
+
+import java.util.List;
+import java.util.Objects;
+
+public class IconShapeOption implements CustomizationOption<IconShapeOption> {
+
+    private final LayerDrawable mShape;
+    private final List<ThemeBundle.PreviewInfo.ShapeAppIcon> mAppIcons;
+    private final String mTitle;
+    private final String mOverlayPackage;
+    private final Path mPath;
+    private final int mCornerRadius;
+    private int[] mShapeIconIds = {
+            R.id.shape_preview_icon_0, R.id.shape_preview_icon_1, R.id.shape_preview_icon_2,
+            R.id.shape_preview_icon_3, R.id.shape_preview_icon_4, R.id.shape_preview_icon_5
+    };
+
+    public IconShapeOption(String packageName, String title, Path path,
+                           @Dimension int cornerRadius, Drawable shapeDrawable,
+                           List<ThemeBundle.PreviewInfo.ShapeAppIcon> appIcons) {
+        mOverlayPackage = packageName;
+        mTitle = title;
+        mAppIcons = appIcons;
+        mPath = path;
+        mCornerRadius = cornerRadius;
+        Drawable background = shapeDrawable.getConstantState().newDrawable();
+        Drawable foreground = shapeDrawable.getConstantState().newDrawable();
+        mShape = new LayerDrawable(new Drawable[]{background, foreground});
+        mShape.setLayerGravity(0, Gravity.CENTER);
+        mShape.setLayerGravity(1, Gravity.CENTER);
+    }
+
+    @Override
+    public void bindThumbnailTile(View view) {
+        Resources res = view.getContext().getResources();
+        int resId = R.id.icon_section_tile;
+        if (view.findViewById(R.id.shape_thumbnail) != null) {
+            resId = R.id.shape_thumbnail;
+        }
+
+        Resources.Theme theme = view.getContext().getTheme();
+        int borderWidth = 2 * res.getDimensionPixelSize(R.dimen.option_border_width);
+
+        Drawable background = mShape.getDrawable(0);
+        background.setTintList(res.getColorStateList(R.color.option_border_color, theme));
+
+        ShapeDrawable foreground = (ShapeDrawable) mShape.getDrawable(1);
+
+        foreground.setIntrinsicHeight(background.getIntrinsicHeight() - borderWidth);
+        foreground.setIntrinsicWidth(background.getIntrinsicWidth() - borderWidth);
+        TypedArray ta = view.getContext().obtainStyledAttributes(
+                new int[]{android.R.attr.colorPrimary});
+        int primaryColor = ta.getColor(0, 0);
+        ta.recycle();
+        int foregroundColor =
+                ResourceUtils.getColorAttr(view.getContext(), android.R.attr.textColorPrimary);
+
+        foreground.setTint(ColorUtils.blendARGB(primaryColor, foregroundColor, .05f));
+
+        ((ImageView) view.findViewById(resId)).setImageDrawable(mShape);
+        view.setContentDescription(mTitle);
+    }
+
+    @Override
+    public boolean isActive(CustomizationManager<IconShapeOption> manager) {
+        IconShapeManager iconManager = (IconShapeManager) manager;
+        OverlayManagerCompat overlayManager = iconManager.getOverlayManager();
+
+        return Objects.equals(mOverlayPackage,
+                overlayManager.getEnabledPackageName(ANDROID_PACKAGE, OVERLAY_CATEGORY_SHAPE));
+    }
+
+    @Override
+    public int getLayoutResId() {
+        return R.layout.theme_shape_option;
+    }
+
+    @Override
+    public String getTitle() {
+        return mTitle;
+    }
+
+    public String getPackageName() {
+        return mOverlayPackage;
+    }
+
+    public void bindPreview(ViewGroup container) {
+        ViewGroup cardBody = container.findViewById(R.id.theme_preview_card_body_container);
+        if (cardBody.getChildCount() == 0) {
+            LayoutInflater.from(container.getContext()).inflate(
+                    R.layout.preview_card_shape_content, cardBody, true);
+        }
+        for (int i = 0; i < mShapeIconIds.length && i < mAppIcons.size(); i++) {
+            ImageView iconView = cardBody.findViewById(mShapeIconIds[i]);
+            iconView.setBackground(mAppIcons.get(i).getDrawableCopy());
+        }
+    }
+}
diff --git a/src/org/leafos/customization/model/iconshape/IconShapeOptionProvider.java b/src/org/leafos/customization/model/iconshape/IconShapeOptionProvider.java
new file mode 100644
index 0000000..7fa93a5
--- /dev/null
+++ b/src/org/leafos/customization/model/iconshape/IconShapeOptionProvider.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.leafos.customization.model.iconshape;
+
+import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
+import static com.android.customization.model.ResourceConstants.CONFIG_CORNERRADIUS;
+import static com.android.customization.model.ResourceConstants.CONFIG_ICON_MASK;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE;
+import static com.android.customization.model.ResourceConstants.PATH_SIZE;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+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.Path;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.PathShape;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.PathParser;
+
+import androidx.annotation.Dimension;
+
+import com.android.customization.model.ResourceConstants;
+import com.android.customization.model.theme.OverlayManagerCompat;
+import com.android.customization.model.theme.ThemeBundle;
+import com.android.customization.widget.DynamicAdaptiveIconDrawable;
+import com.android.wallpaper.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class IconShapeOptionProvider {
+
+    private static final String TAG = "IconShapeOptionProvider";
+
+    private Context mContext;
+    private final List<String> mOverlayPackages;
+    private final List<IconShapeOption> mOptions = new ArrayList<>();
+    private final String[] mShapePreviewIconPackages;
+    private int mThumbSize;
+
+    public IconShapeOptionProvider(Context context, OverlayManagerCompat manager) {
+        mContext = context;
+        mOverlayPackages = new ArrayList<>();
+        mOverlayPackages.addAll(manager.getOverlayPackagesForCategory(OVERLAY_CATEGORY_SHAPE,
+                UserHandle.myUserId(), ResourceConstants.getPackagesToOverlay(mContext)));
+
+        mShapePreviewIconPackages = context.getResources().getStringArray(
+                R.array.icon_shape_preview_packages);
+        mThumbSize = mContext.getResources().getDimensionPixelSize(
+                R.dimen.component_shape_thumb_size);
+    }
+
+    public List<IconShapeOption> getOptions() {
+        if (mOptions.isEmpty()) loadOptions();
+        return mOptions;
+    }
+
+    private void loadOptions() {
+        addDefault();
+        for (String overlayPackage : mOverlayPackages) {
+            try {
+                Path path = loadPath(mContext.getPackageManager()
+                        .getResourcesForApplication(overlayPackage), overlayPackage);
+                PackageManager pm = mContext.getPackageManager();
+                String label = pm.getApplicationInfo(overlayPackage, 0).loadLabel(pm).toString();
+                mOptions.add(new IconShapeOption(overlayPackage, label, path,
+                        loadCornerRadius(overlayPackage), createShapeDrawable(path),
+                        getShapedAppIcons(path)));
+            } catch (NameNotFoundException | NotFoundException e) {
+                Log.w(TAG, String.format("Couldn't load shape overlay %s, will skip it",
+                        overlayPackage), e);
+            }
+        }
+    }
+
+    private void addDefault() {
+        Resources system = Resources.getSystem();
+        Path path = loadPath(system, ANDROID_PACKAGE);
+        mOptions.add(new IconShapeOption(null, mContext.getString(R.string.default_theme_title), path,
+                system.getDimensionPixelOffset(
+                        system.getIdentifier(CONFIG_CORNERRADIUS,
+                                "dimen", ResourceConstants.ANDROID_PACKAGE)),
+                createShapeDrawable(path), getShapedAppIcons(path)));
+    }
+
+    private ShapeDrawable createShapeDrawable(Path path) {
+        PathShape shape = new PathShape(path, PATH_SIZE, PATH_SIZE);
+        ShapeDrawable shapeDrawable = new ShapeDrawable(shape);
+        shapeDrawable.setIntrinsicHeight(mThumbSize);
+        shapeDrawable.setIntrinsicWidth(mThumbSize);
+        return shapeDrawable;
+    }
+
+    private List<ThemeBundle.PreviewInfo.ShapeAppIcon> getShapedAppIcons(Path path) {
+        List<ThemeBundle.PreviewInfo.ShapeAppIcon> shapedAppIcons = new ArrayList<>();
+        for (String packageName : mShapePreviewIconPackages) {
+            Drawable icon = null;
+            CharSequence name = null;
+            try {
+                Drawable appIcon = mContext.getPackageManager().getApplicationIcon(packageName);
+                if (appIcon instanceof AdaptiveIconDrawable) {
+                    AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) appIcon;
+                    icon = new DynamicAdaptiveIconDrawable(adaptiveIcon.getBackground(),
+                            adaptiveIcon.getForeground(), path);
+
+                    ApplicationInfo appInfo = mContext.getPackageManager()
+                            .getApplicationInfo(packageName, /* flag= */ 0);
+                    name = mContext.getPackageManager().getApplicationLabel(appInfo);
+                }
+            } catch (NameNotFoundException e) {
+                Log.d(TAG, "Couldn't find app " + packageName
+                        + ", won't use it for icon shape preview");
+            } finally {
+                if (icon != null && !TextUtils.isEmpty(name)) {
+                    shapedAppIcons.add(new ThemeBundle.PreviewInfo.ShapeAppIcon(icon, name));
+                }
+            }
+        }
+        return shapedAppIcons;
+    }
+
+    private Path loadPath(Resources overlayRes, String packageName) {
+        String shape = overlayRes.getString(overlayRes.getIdentifier(CONFIG_ICON_MASK, "string",
+                packageName));
+
+        if (!TextUtils.isEmpty(shape)) {
+            return PathParser.createPathFromPathData(shape);
+        }
+        return null;
+    }
+
+    @Dimension
+    private int loadCornerRadius(String packageName)
+            throws NameNotFoundException, NotFoundException {
+
+        Resources overlayRes =
+                mContext.getPackageManager().getResourcesForApplication(
+                        packageName);
+        return overlayRes.getDimensionPixelOffset(overlayRes.getIdentifier(
+                CONFIG_CORNERRADIUS, "dimen", packageName));
+    }
+
+}
diff --git a/src/org/leafos/customization/model/iconshape/IconShapeSectionController.java b/src/org/leafos/customization/model/iconshape/IconShapeSectionController.java
new file mode 100644
index 0000000..bc25812
--- /dev/null
+++ b/src/org/leafos/customization/model/iconshape/IconShapeSectionController.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 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 org.leafos.customization.model.iconshape;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.customization.model.CustomizationManager.Callback;
+import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
+
+import com.android.wallpaper.R;
+import com.android.wallpaper.model.CustomizationSectionController;
+
+import org.leafos.customization.picker.iconshape.IconShapeFragment;
+import org.leafos.customization.picker.iconshape.IconShapeSectionView;
+
+import java.util.List;
+
+/** A {@link CustomizationSectionController} for system icons. */
+
+public class IconShapeSectionController implements CustomizationSectionController<IconShapeSectionView> {
+
+    private static final String TAG = "IconShapeSectionController";
+
+    private final IconShapeManager mIconShapeOptionsManager;
+    private final CustomizationSectionNavigationController mSectionNavigationController;
+
+    public IconShapeSectionController(IconShapeManager iconShapeOptionsManager,
+            CustomizationSectionNavigationController sectionNavigationController) {
+        mIconShapeOptionsManager = iconShapeOptionsManager;
+        mSectionNavigationController = sectionNavigationController;
+    }
+
+    @Override
+    public boolean isAvailable(Context context) {
+        return mIconShapeOptionsManager.isAvailable();
+    }
+
+    @Override
+    public IconShapeSectionView createView(Context context) {
+        IconShapeSectionView iconShapeSectionView = (IconShapeSectionView) LayoutInflater.from(context)
+                .inflate(R.layout.icon_shape_section_view, /* root= */ null);
+
+        TextView sectionDescription = iconShapeSectionView.findViewById(R.id.icon_section_description);
+        View sectionTile = iconShapeSectionView.findViewById(R.id.icon_section_tile);
+
+        mIconShapeOptionsManager.fetchOptions(new OptionsFetchedListener<IconShapeOption>() {
+            @Override
+            public void onOptionsLoaded(List<IconShapeOption> options) {
+                IconShapeOption activeOption = getActiveOption(options);
+                sectionDescription.setText(activeOption.getTitle());
+                activeOption.bindThumbnailTile(sectionTile);
+            }
+
+            @Override
+            public void onError(@Nullable Throwable throwable) {
+                if (throwable != null) {
+                    Log.e(TAG, "Error loading icon options", throwable);
+                }
+                sectionDescription.setText(R.string.something_went_wrong);
+                sectionTile.setVisibility(View.GONE);
+            }
+        }, /* reload= */ true);
+
+        iconShapeSectionView.setOnClickListener(v -> mSectionNavigationController.navigateTo(
+                IconShapeFragment.newInstance(context.getString(R.string.preview_name_shape))));
+
+        return iconShapeSectionView;
+    }
+
+    private IconShapeOption getActiveOption(List<IconShapeOption> options) {
+        return options.stream()
+                .filter(option -> option.isActive(mIconShapeOptionsManager))
+                .findAny()
+                // For development only, as there should always be a grid set.
+                .orElse(options.get(0));
+    }
+}
diff --git a/src/org/leafos/customization/module/LeafCustomizationSections.java b/src/org/leafos/customization/module/LeafCustomizationSections.java
index dcc2a3b..6d752b0 100644
--- a/src/org/leafos/customization/module/LeafCustomizationSections.java
+++ b/src/org/leafos/customization/module/LeafCustomizationSections.java
@@ -21,6 +21,14 @@
 import androidx.fragment.app.FragmentActivity;
 import androidx.lifecycle.LifecycleOwner;
 
+import org.leafos.customization.model.font.FontManager;
+import org.leafos.customization.model.font.FontSectionController;
+import org.leafos.customization.model.iconpack.IconPackManager;
+import org.leafos.customization.model.iconpack.IconPackSectionController;
+import org.leafos.customization.model.iconshape.IconShapeManager;
+import org.leafos.customization.model.iconshape.IconShapeSectionController;
+
+import com.android.customization.model.theme.OverlayManagerCompat;
 import com.android.wallpaper.model.CustomizationSectionController;
 import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController;
 import com.android.wallpaper.model.PermissionRequester;
@@ -61,6 +69,19 @@
                         sectionNavigationController, savedInstanceState, wallpaperInfoFactory,
                         displayUtils, wallpaperQuickSwitchViewModel,
                         wallpaperInteractor);
+
+        // Icon pack selection section.
+        sections.add(new IconPackSectionController(
+                IconPackManager.getInstance(activity, new OverlayManagerCompat(activity)), sectionNavigationController));
+
+        // Font selection section.
+        sections.add(new FontSectionController(
+                FontManager.getInstance(activity, new OverlayManagerCompat(activity)), sectionNavigationController));
+
+        // Icon shape selection section.
+        sections.add(new IconShapeSectionController(
+                IconShapeManager.getInstance(activity, new OverlayManagerCompat(activity)), sectionNavigationController));
+
         return sections;
     }
 
@@ -79,6 +100,19 @@
                         activity, lifecycleOwner, wallpaperColorsViewModel, permissionRequester,
                         wallpaperPreviewNavigator, sectionNavigationController, savedInstanceState,
                         displayUtils);
+
+        // Icon pack selection section.
+        sections.add(new IconPackSectionController(
+                IconPackManager.getInstance(activity, new OverlayManagerCompat(activity)), sectionNavigationController));
+
+        // Font selection section.
+        sections.add(new FontSectionController(
+                FontManager.getInstance(activity, new OverlayManagerCompat(activity)), sectionNavigationController));
+
+        // Icon shape selection section.
+        sections.add(new IconShapeSectionController(
+                IconShapeManager.getInstance(activity, new OverlayManagerCompat(activity)), sectionNavigationController));
+
         return sections;
     }
 }
diff --git a/src/org/leafos/customization/picker/font/FontFragment.java b/src/org/leafos/customization/picker/font/FontFragment.java
new file mode 100644
index 0000000..efd4a72
--- /dev/null
+++ b/src/org/leafos/customization/picker/font/FontFragment.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2018 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 org.leafos.customization.picker.font;
+
+import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY_TEXT;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.widget.ContentLoadingProgressBar;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.customization.model.CustomizationManager.Callback;
+import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
+import com.android.customization.model.CustomizationOption;
+import com.android.customization.model.theme.OverlayManagerCompat;
+import com.android.customization.module.ThemesUserEventLogger;
+import com.android.customization.picker.WallpaperPreviewer;
+import com.android.customization.widget.OptionSelectorController;
+import com.android.customization.widget.OptionSelectorController.CheckmarkStyle;
+import com.android.wallpaper.R;
+import com.android.wallpaper.picker.AppbarFragment;
+import com.android.wallpaper.widget.BottomActionBar;
+
+import org.leafos.customization.model.font.FontOption;
+import org.leafos.customization.model.font.FontManager;
+
+import java.util.List;
+
+/**
+ * Fragment that contains the UI for selecting and applying a FontOption.
+ */
+public class FontFragment extends AppbarFragment {
+
+    private static final String TAG = "FontFragment";
+    private static final String KEY_STATE_SELECTED_OPTION = "FontFragment.selectedOption";
+    private static final String KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE =
+            "FontFragment.bottomActionBarVisible";
+
+    public static FontFragment newInstance(CharSequence title) {
+        FontFragment fragment = new FontFragment();
+        fragment.setArguments(AppbarFragment.createArguments(title));
+        return fragment;
+    }
+
+    private RecyclerView mOptionsContainer;
+    private OptionSelectorController<FontOption> mOptionsController;
+    private FontManager mFontManager;
+    private FontOption mSelectedOption;
+    private ContentLoadingProgressBar mLoading;
+    private ViewGroup mContent;
+    private View mError;
+    private BottomActionBar mBottomActionBar;
+
+    private final Callback mApplyFontCallback = new Callback() {
+        @Override
+        public void onSuccess() {
+        }
+
+        @Override
+        public void onError(@Nullable Throwable throwable) {
+            // Since we disabled it when clicked apply button.
+            mBottomActionBar.enableActions();
+            mBottomActionBar.hide();
+            //TODO(chihhangchuang): handle
+        }
+    };
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        View view = inflater.inflate(
+                R.layout.fragment_font_picker, container, /* attachToRoot */ false);
+        setUpToolbar(view);
+        mContent = view.findViewById(R.id.content_section);
+        mOptionsContainer = view.findViewById(R.id.options_container);
+        mLoading = view.findViewById(R.id.loading_indicator);
+        mError = view.findViewById(R.id.error_section);
+
+        // For nav bar edge-to-edge effect.
+        view.setOnApplyWindowInsetsListener((v, windowInsets) -> {
+            v.setPadding(
+                    v.getPaddingLeft(),
+                    windowInsets.getSystemWindowInsetTop(),
+                    v.getPaddingRight(),
+                    windowInsets.getSystemWindowInsetBottom());
+            return windowInsets.consumeSystemWindowInsets();
+        });
+
+        mFontManager = FontManager.getInstance(getContext(), new OverlayManagerCompat(getContext()));
+        setUpOptions(savedInstanceState);
+
+        return view;
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        if (mBottomActionBar != null) {
+            outState.putBoolean(KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE, mBottomActionBar.isVisible());
+        }
+    }
+
+    @Override
+    protected void onBottomActionBarReady(BottomActionBar bottomActionBar) {
+        super.onBottomActionBarReady(bottomActionBar);
+        mBottomActionBar = bottomActionBar;
+        mBottomActionBar.showActionsOnly(APPLY_TEXT);
+        mBottomActionBar.setActionClickListener(APPLY_TEXT, v -> applyFontOption(mSelectedOption));
+    }
+
+    private void applyFontOption(FontOption fontOption) {
+        mBottomActionBar.disableActions();
+        mFontManager.apply(fontOption, mApplyFontCallback);
+    }
+
+    private void setUpOptions(@Nullable Bundle savedInstanceState) {
+        hideError();
+        mLoading.show();
+        mFontManager.fetchOptions(new OptionsFetchedListener<FontOption>() {
+            @Override
+            public void onOptionsLoaded(List<FontOption> options) {
+                mLoading.hide();
+                mOptionsController = new OptionSelectorController<>(
+                        mOptionsContainer, options, /* useGrid= */ false, CheckmarkStyle.CORNER);
+                mOptionsController.initOptions(mFontManager);
+                mSelectedOption = getActiveOption(options);
+                mOptionsController.setSelectedOption(mSelectedOption);
+                onOptionSelected(mSelectedOption);
+                restoreBottomActionBarVisibility(savedInstanceState);
+
+                mOptionsController.addListener(selectedOption -> {
+                    onOptionSelected(selectedOption);
+                    mBottomActionBar.show();
+                });
+            }
+
+            @Override
+            public void onError(@Nullable Throwable throwable) {
+                if (throwable != null) {
+                    Log.e(TAG, "Error loading Font options", throwable);
+                }
+                showError();
+            }
+        }, /*reload= */ true);
+    }
+
+    private FontOption getActiveOption(List<FontOption> options) {
+        return options.stream()
+                .filter(option -> mFontManager.isActive(option))
+                .findAny()
+                // For development only, as there should always be an Font set.
+                .orElse(options.get(0));
+    }
+
+    private void hideError() {
+        mContent.setVisibility(View.VISIBLE);
+        mError.setVisibility(View.GONE);
+    }
+
+    private void showError() {
+        mLoading.hide();
+        mContent.setVisibility(View.GONE);
+        mError.setVisibility(View.VISIBLE);
+    }
+
+    private void onOptionSelected(CustomizationOption selectedOption) {
+        mSelectedOption = (FontOption) selectedOption;
+        refreshPreview();
+    }
+
+    private void refreshPreview() {
+        mSelectedOption.bindPreview(mContent);
+    }
+
+    private void restoreBottomActionBarVisibility(@Nullable Bundle savedInstanceState) {
+        boolean isBottomActionBarVisible = savedInstanceState != null
+                && savedInstanceState.getBoolean(KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE);
+        if (mBottomActionBar == null) return;
+        if (isBottomActionBarVisible) {
+            mBottomActionBar.show();
+        } else {
+            mBottomActionBar.hide();
+        }
+    }
+}
diff --git a/src/org/leafos/customization/picker/font/FontSectionView.java b/src/org/leafos/customization/picker/font/FontSectionView.java
new file mode 100644
index 0000000..d8b75f4
--- /dev/null
+++ b/src/org/leafos/customization/picker/font/FontSectionView.java
@@ -0,0 +1,12 @@
+package org.leafos.customization.picker.font;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.wallpaper.picker.SectionView;
+
+public final class FontSectionView extends SectionView {
+    public FontSectionView(Context context, AttributeSet attributeSet) {
+        super(context, attributeSet);
+    }
+}
diff --git a/src/org/leafos/customization/picker/iconpack/IconPackFragment.java b/src/org/leafos/customization/picker/iconpack/IconPackFragment.java
new file mode 100644
index 0000000..f35c79b
--- /dev/null
+++ b/src/org/leafos/customization/picker/iconpack/IconPackFragment.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2018 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 org.leafos.customization.picker.iconpack;
+
+import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY_TEXT;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.widget.ContentLoadingProgressBar;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.customization.model.CustomizationManager.Callback;
+import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
+import com.android.customization.model.CustomizationOption;
+import com.android.customization.model.theme.OverlayManagerCompat;
+import com.android.customization.module.ThemesUserEventLogger;
+import com.android.customization.picker.WallpaperPreviewer;
+import com.android.customization.widget.OptionSelectorController;
+import com.android.customization.widget.OptionSelectorController.CheckmarkStyle;
+import com.android.wallpaper.R;
+import com.android.wallpaper.picker.AppbarFragment;
+import com.android.wallpaper.widget.BottomActionBar;
+
+import org.leafos.customization.model.iconpack.IconPackOption;
+import org.leafos.customization.model.iconpack.IconPackManager;
+
+import java.util.List;
+
+/**
+ * Fragment that contains the UI for selecting and applying a IconPackOption.
+ */
+public class IconPackFragment extends AppbarFragment {
+
+    private static final String TAG = "IconPackFragment";
+    private static final String KEY_STATE_SELECTED_OPTION = "IconPackFragment.selectedOption";
+    private static final String KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE =
+            "IconPackFragment.bottomActionBarVisible";
+
+    public static IconPackFragment newInstance(CharSequence title) {
+        IconPackFragment fragment = new IconPackFragment();
+        fragment.setArguments(AppbarFragment.createArguments(title));
+        return fragment;
+    }
+
+    private RecyclerView mOptionsContainer;
+    private OptionSelectorController<IconPackOption> mOptionsController;
+    private IconPackManager mIconPackManager;
+    private IconPackOption mSelectedOption;
+    private ContentLoadingProgressBar mLoading;
+    private ViewGroup mContent;
+    private View mError;
+    private BottomActionBar mBottomActionBar;
+
+    private final Callback mApplyIconPackCallback = new Callback() {
+        @Override
+        public void onSuccess() {
+        }
+
+        @Override
+        public void onError(@Nullable Throwable throwable) {
+            // Since we disabled it when clicked apply button.
+            mBottomActionBar.enableActions();
+            mBottomActionBar.hide();
+            //TODO(chihhangchuang): handle
+        }
+    };
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        View view = inflater.inflate(
+                R.layout.fragment_icon_pack_picker, container, /* attachToRoot */ false);
+        setUpToolbar(view);
+        mContent = view.findViewById(R.id.content_section);
+        mOptionsContainer = view.findViewById(R.id.options_container);
+        mLoading = view.findViewById(R.id.loading_indicator);
+        mError = view.findViewById(R.id.error_section);
+
+        // For nav bar edge-to-edge effect.
+        view.setOnApplyWindowInsetsListener((v, windowInsets) -> {
+            v.setPadding(
+                    v.getPaddingLeft(),
+                    windowInsets.getSystemWindowInsetTop(),
+                    v.getPaddingRight(),
+                    windowInsets.getSystemWindowInsetBottom());
+            return windowInsets.consumeSystemWindowInsets();
+        });
+
+        mIconPackManager = IconPackManager.getInstance(getContext(), new OverlayManagerCompat(getContext()));
+        setUpOptions(savedInstanceState);
+
+        return view;
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        if (mBottomActionBar != null) {
+            outState.putBoolean(KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE, mBottomActionBar.isVisible());
+        }
+    }
+
+    @Override
+    protected void onBottomActionBarReady(BottomActionBar bottomActionBar) {
+        super.onBottomActionBarReady(bottomActionBar);
+        mBottomActionBar = bottomActionBar;
+        mBottomActionBar.showActionsOnly(APPLY_TEXT);
+        mBottomActionBar.setActionClickListener(APPLY_TEXT, v -> applyIconPackOption(mSelectedOption));
+    }
+
+    private void applyIconPackOption(IconPackOption iconPackOption) {
+        mBottomActionBar.disableActions();
+        mIconPackManager.apply(iconPackOption, mApplyIconPackCallback);
+    }
+
+    private void setUpOptions(@Nullable Bundle savedInstanceState) {
+        hideError();
+        mLoading.show();
+        mIconPackManager.fetchOptions(new OptionsFetchedListener<IconPackOption>() {
+            @Override
+            public void onOptionsLoaded(List<IconPackOption> options) {
+                mLoading.hide();
+                mOptionsController = new OptionSelectorController<>(
+                        mOptionsContainer, options, /* useGrid= */ false, CheckmarkStyle.CORNER);
+                mOptionsController.initOptions(mIconPackManager);
+                mSelectedOption = getActiveOption(options);
+                mOptionsController.setSelectedOption(mSelectedOption);
+                onOptionSelected(mSelectedOption);
+                restoreBottomActionBarVisibility(savedInstanceState);
+
+                mOptionsController.addListener(selectedOption -> {
+                    onOptionSelected(selectedOption);
+                    mBottomActionBar.show();
+                });
+            }
+
+            @Override
+            public void onError(@Nullable Throwable throwable) {
+                if (throwable != null) {
+                    Log.e(TAG, "Error loading iconpack options", throwable);
+                }
+                showError();
+            }
+        }, /*reload= */ true);
+    }
+
+    private IconPackOption getActiveOption(List<IconPackOption> options) {
+        return options.stream()
+                .filter(option -> option.isActive(mIconPackManager))
+                .findAny()
+                // For development only, as there should always be an iconpack set.
+                .orElse(options.get(0));
+    }
+
+    private void hideError() {
+        mContent.setVisibility(View.VISIBLE);
+        mError.setVisibility(View.GONE);
+    }
+
+    private void showError() {
+        mLoading.hide();
+        mContent.setVisibility(View.GONE);
+        mError.setVisibility(View.VISIBLE);
+    }
+
+    private void onOptionSelected(CustomizationOption selectedOption) {
+        mSelectedOption = (IconPackOption) selectedOption;
+        refreshPreview();
+    }
+
+    private void refreshPreview() {
+        mSelectedOption.bindPreview(mContent);
+    }
+
+    private void restoreBottomActionBarVisibility(@Nullable Bundle savedInstanceState) {
+        boolean isBottomActionBarVisible = savedInstanceState != null
+                && savedInstanceState.getBoolean(KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE);
+        if (mBottomActionBar == null) return;
+        if (isBottomActionBarVisible) {
+            mBottomActionBar.show();
+        } else {
+            mBottomActionBar.hide();
+        }
+    }
+}
diff --git a/src/org/leafos/customization/picker/iconpack/IconPackSectionView.java b/src/org/leafos/customization/picker/iconpack/IconPackSectionView.java
new file mode 100644
index 0000000..551cdcb
--- /dev/null
+++ b/src/org/leafos/customization/picker/iconpack/IconPackSectionView.java
@@ -0,0 +1,12 @@
+package org.leafos.customization.picker.iconpack;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.wallpaper.picker.SectionView;
+
+public final class IconPackSectionView extends SectionView {
+    public IconPackSectionView(Context context, AttributeSet attributeSet) {
+        super(context, attributeSet);
+    }
+}
diff --git a/src/org/leafos/customization/picker/iconshape/IconShapeFragment.java b/src/org/leafos/customization/picker/iconshape/IconShapeFragment.java
new file mode 100644
index 0000000..37f7b57
--- /dev/null
+++ b/src/org/leafos/customization/picker/iconshape/IconShapeFragment.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2018 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 org.leafos.customization.picker.iconshape;
+
+import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY_TEXT;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.widget.ContentLoadingProgressBar;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.customization.model.CustomizationManager.Callback;
+import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
+import com.android.customization.model.CustomizationOption;
+import com.android.customization.model.theme.OverlayManagerCompat;
+import com.android.customization.widget.OptionSelectorController;
+import com.android.customization.widget.OptionSelectorController.CheckmarkStyle;
+import com.android.wallpaper.R;
+import com.android.wallpaper.picker.AppbarFragment;
+import com.android.wallpaper.widget.BottomActionBar;
+
+import org.leafos.customization.model.iconshape.IconShapeOption;
+import org.leafos.customization.model.iconshape.IconShapeManager;
+
+import java.util.List;
+
+/**
+ * Fragment that contains the UI for selecting and applying a IconShapeOption.
+ */
+public class IconShapeFragment extends AppbarFragment {
+
+    private static final String TAG = "IconShapeFragment";
+    private static final String KEY_STATE_SELECTED_OPTION = "IconShapeFragment.selectedOption";
+    private static final String KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE =
+            "IconShapeFragment.bottomActionBarVisible";
+
+    public static IconShapeFragment newInstance(CharSequence title) {
+        IconShapeFragment fragment = new IconShapeFragment();
+        fragment.setArguments(AppbarFragment.createArguments(title));
+        return fragment;
+    }
+
+    private RecyclerView mOptionsContainer;
+    private OptionSelectorController<IconShapeOption> mOptionsController;
+    private IconShapeManager mIconShapeManager;
+    private IconShapeOption mSelectedOption;
+    private ContentLoadingProgressBar mLoading;
+    private ViewGroup mContent;
+    private View mError;
+    private BottomActionBar mBottomActionBar;
+
+    private final Callback mApplyIconShapeCallback = new Callback() {
+        @Override
+        public void onSuccess() {
+        }
+
+        @Override
+        public void onError(@Nullable Throwable throwable) {
+            // Since we disabled it when clicked apply button.
+            mBottomActionBar.enableActions();
+            mBottomActionBar.hide();
+            //TODO(chihhangchuang): handle
+        }
+    };
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        View view = inflater.inflate(
+                R.layout.fragment_icon_shape_picker, container, /* attachToRoot */ false);
+        setUpToolbar(view);
+        mContent = view.findViewById(R.id.content_section);
+        mOptionsContainer = view.findViewById(R.id.options_container);
+        mLoading = view.findViewById(R.id.loading_indicator);
+        mError = view.findViewById(R.id.error_section);
+
+        // For nav bar edge-to-edge effect.
+        view.setOnApplyWindowInsetsListener((v, windowInsets) -> {
+            v.setPadding(
+                    v.getPaddingLeft(),
+                    windowInsets.getSystemWindowInsetTop(),
+                    v.getPaddingRight(),
+                    windowInsets.getSystemWindowInsetBottom());
+            return windowInsets.consumeSystemWindowInsets();
+        });
+
+        mIconShapeManager = IconShapeManager.getInstance(getContext(), new OverlayManagerCompat(getContext()));
+        setUpOptions(savedInstanceState);
+
+        return view;
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        if (mBottomActionBar != null) {
+            outState.putBoolean(KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE, mBottomActionBar.isVisible());
+        }
+    }
+
+    @Override
+    protected void onBottomActionBarReady(BottomActionBar bottomActionBar) {
+        super.onBottomActionBarReady(bottomActionBar);
+        mBottomActionBar = bottomActionBar;
+        mBottomActionBar.showActionsOnly(APPLY_TEXT);
+        mBottomActionBar.setActionClickListener(APPLY_TEXT, v -> applyIconShapeOption(mSelectedOption));
+    }
+
+    private void applyIconShapeOption(IconShapeOption iconPackOption) {
+        mBottomActionBar.disableActions();
+        mIconShapeManager.apply(iconPackOption, mApplyIconShapeCallback);
+    }
+
+    private void setUpOptions(@Nullable Bundle savedInstanceState) {
+        hideError();
+        mLoading.show();
+        mIconShapeManager.fetchOptions(new OptionsFetchedListener<IconShapeOption>() {
+            @Override
+            public void onOptionsLoaded(List<IconShapeOption> options) {
+                mLoading.hide();
+                mOptionsController = new OptionSelectorController<>(
+                        mOptionsContainer, options, /* useGrid= */ false, CheckmarkStyle.CORNER);
+                mOptionsController.initOptions(mIconShapeManager);
+                mSelectedOption = getActiveOption(options);
+                mOptionsController.setSelectedOption(mSelectedOption);
+                onOptionSelected(mSelectedOption);
+                restoreBottomActionBarVisibility(savedInstanceState);
+
+                mOptionsController.addListener(selectedOption -> {
+                    onOptionSelected(selectedOption);
+                    mBottomActionBar.show();
+                });
+            }
+
+            @Override
+            public void onError(@Nullable Throwable throwable) {
+                if (throwable != null) {
+                    Log.e(TAG, "Error loading iconpack options", throwable);
+                }
+                showError();
+            }
+        }, /*reload= */ true);
+    }
+
+    private IconShapeOption getActiveOption(List<IconShapeOption> options) {
+        return options.stream()
+                .filter(option -> option.isActive(mIconShapeManager))
+                .findAny()
+                // For development only, as there should always be an iconpack set.
+                .orElse(options.get(0));
+    }
+
+    private void hideError() {
+        mContent.setVisibility(View.VISIBLE);
+        mError.setVisibility(View.GONE);
+    }
+
+    private void showError() {
+        mLoading.hide();
+        mContent.setVisibility(View.GONE);
+        mError.setVisibility(View.VISIBLE);
+    }
+
+    private void onOptionSelected(CustomizationOption selectedOption) {
+        mSelectedOption = (IconShapeOption) selectedOption;
+        refreshPreview();
+    }
+
+    private void refreshPreview() {
+        mSelectedOption.bindPreview(mContent);
+    }
+
+    private void restoreBottomActionBarVisibility(@Nullable Bundle savedInstanceState) {
+        boolean isBottomActionBarVisible = savedInstanceState != null
+                && savedInstanceState.getBoolean(KEY_STATE_BOTTOM_ACTION_BAR_VISIBLE);
+        if (mBottomActionBar == null) return;
+        if (isBottomActionBarVisible) {
+            mBottomActionBar.show();
+        } else {
+            mBottomActionBar.hide();
+        }
+    }
+}
diff --git a/src/org/leafos/customization/picker/iconshape/IconShapeSectionView.java b/src/org/leafos/customization/picker/iconshape/IconShapeSectionView.java
new file mode 100644
index 0000000..0d81c03
--- /dev/null
+++ b/src/org/leafos/customization/picker/iconshape/IconShapeSectionView.java
@@ -0,0 +1,12 @@
+package org.leafos.customization.picker.iconshape;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.wallpaper.picker.SectionView;
+
+public final class IconShapeSectionView extends SectionView {
+    public IconShapeSectionView(Context context, AttributeSet attributeSet) {
+        super(context, attributeSet);
+    }
+}