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);
+ }
+}