Merge tag 'android-13.0.0_r52' into leaf-2.0
Android 13.0.0 Release 52 (TQ3A.230605.012)
* tag 'android-13.0.0_r52': (109 commits)
Shows blurred thumbnail before fading to live preview
Fades in preview of feathers to avoid flicker
Import translations. DO NOT MERGE ANYWHERE
Update app grid options after changing grid
Use the same cropping for outer screen and unfolded vertical hinge
Revert and fix "Fix for color picker pager getting clipped"
Import translations. DO NOT MERGE ANYWHERE
Import translations. DO NOT MERGE ANYWHERE
Color Picker Fragment UX Polish
Import translations. DO NOT MERGE ANYWHERE
Fix for color picker pager getting clipped
Grid Picker Selection Animation Fix (1/2)
System Colors picker selection animation (1/2)
Wallpaper Quick Switcher Reload (2/3)
Use no-op snapshot store in restorers (2/2).
[TP] Get string array
Handle the case of no grid options available
[TP] Make carousel scale and alpha dimen res
[TP] Make clock previews tick
[TP] Smooth scroll to clock color option position
...
Change-Id: I9dbe7dd75b792270d9e245ccdc73b6612b671b93
diff --git a/Android.bp b/Android.bp
index f6c8558..2802a69 100644
--- a/Android.bp
+++ b/Android.bp
@@ -102,7 +102,7 @@
enabled: false,
},
kotlincflags: ["-Xjvm-default=enable"],
- certificate: "",
+ certificate: "platform",
privileged: true,
system_ext_specific: true,
@@ -118,5 +118,31 @@
platform_apis: true,
manifest: "AndroidManifest.xml",
additional_manifests: [":WallpaperPicker2_Manifest"],
- overrides: ["WallpaperPicker2"],
+
+ required: [
+ "privapp_whitelist_com.android.wallpaper.xml",
+ "default_permissions_com.android.wallpaper.xml",
+ ],
+
+ overrides: [
+ "WallpaperCropper",
+ "WallpaperPicker2",
+ "WallpaperPicker",
+ ],
+}
+
+prebuilt_etc_xml {
+ name: "privapp_whitelist_com.android.wallpaper.xml",
+ src: "privapp_whitelist_com.android.wallpaper.xml",
+ system_ext_specific: true,
+ filename_from_src: true,
+ sub_dir: "permissions",
+}
+
+prebuilt_etc_xml {
+ name: "default_permissions_com.android.wallpaper.xml",
+ src: "default_permissions_com.android.wallpaper.xml",
+ system_ext_specific: true,
+ filename_from_src: true,
+ sub_dir: "default-permissions",
}
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4e71bcc..16c15ca 100755
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -8,9 +8,13 @@
<uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
<uses-permission android:name="android.permission.SET_WALLPAPER_COMPONENT" />
- <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.BIND_WALLPAPER" />
+
<uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" />
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.CUSTOMIZE_SYSTEM_UI" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<queries>
<!-- Specific intents Wallpaper picker query for -->
@@ -45,6 +49,9 @@
</intent>
</queries>
+ <uses-permission android:name="com.android.launcher3.permission.READ_SETTINGS" />
+ <uses-permission android:name="com.android.launcher3.permission.WRITE_SETTINGS" />
+
<application
tools:replace="android:icon,android:name"
android:extractNativeLibs="false"
diff --git a/default_permissions_com.android.wallpaper.xml b/default_permissions_com.android.wallpaper.xml
new file mode 100644
index 0000000..41b23ce
--- /dev/null
+++ b/default_permissions_com.android.wallpaper.xml
@@ -0,0 +1,37 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<!--
+ Copyright (C) 2019-2020 The LineageOS 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.
+-->
+
+<!--
+ This file contains permissions to be granted by default. Default
+ permissions are granted to special platform components and to apps
+ that are approved to get default grants. The special components
+ are apps that are expected to work out-of-the-box as they provide
+ core use cases such as default dialer, default email, etc. These
+ grants are managed by the platform. The apps that are additionally
+ approved for default grants are ones that provide carrier specific
+ functionality, ones legally required at some location, ones providing
+ alternative disclosure and opt-out UI, ones providing highlight features
+ of a dedicated device, etc. This file contains only the latter exceptions.
+ Fixed permissions cannot be controlled by the user and need a special
+ approval. Typically these are to ensure either legally mandated functions
+ or the app is considered a part of the OS.
+-->
+<exceptions>
+ <exception package="com.android.wallpaper">
+ <permission name="android.permission.READ_EXTERNAL_STORAGE" fixed="false"/>
+ </exception>
+</exceptions>
diff --git a/privapp_whitelist_com.android.wallpaper.xml b/privapp_whitelist_com.android.wallpaper.xml
new file mode 100644
index 0000000..79439a3
--- /dev/null
+++ b/privapp_whitelist_com.android.wallpaper.xml
@@ -0,0 +1,26 @@
+<?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
+ -->
+<permissions>
+ <privapp-permissions package="com.android.wallpaper">
+ <permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
+ <permission name="android.permission.BIND_WALLPAPER"/>
+ <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
+ <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
+ <permission name="android.permission.WRITE_SECURE_SETTINGS" />
+ <permission name="android.permission.READ_WALLPAPER_INTERNAL"/>
+ </privapp-permissions>
+</permissions>
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..9119b48
--- /dev/null
+++ b/res/layout/font_section_view.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.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:layout_gravity="center_vertical"
+ 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>
+
+</com.android.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/grid_section_view.xml b/res/layout/grid_section_view.xml
index ee7b76f..61ebee8 100644
--- a/res/layout/grid_section_view.xml
+++ b/res/layout/grid_section_view.xml
@@ -20,7 +20,7 @@
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:clickable="true"
- android:paddingVertical="@dimen/section_top_padding"
+ android:paddingBottom="@dimen/section_bottom_padding"
android:paddingHorizontal="@dimen/section_horizontal_padding"
android:orientation="horizontal">
@@ -28,6 +28,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
+ android:layout_gravity="center_vertical"
android:orientation="vertical">
<TextView
@@ -52,4 +53,4 @@
android:background="@drawable/option_border_color"
android:contentDescription="@string/gird_picker_entry_content_description" />
-</com.android.customization.picker.grid.GridSectionView>
\ No newline at end of file
+</com.android.customization.picker.grid.GridSectionView>
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..fce7c64
--- /dev/null
+++ b/res/layout/icon_section_view.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.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:layout_gravity="center_vertical"
+ 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" />
+
+</com.android.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..0fceac5
--- /dev/null
+++ b/res/layout/icon_shape_section_view.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.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:layout_gravity="center_vertical"
+ 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>
+
+</com.android.customization.picker.iconshape.IconShapeSectionView>
diff --git a/res/layout/theme_font_option.xml b/res/layout/theme_font_option.xml
index dea4f78..583ddde 100644
--- a/res/layout/theme_font_option.xml
+++ b/res/layout/theme_font_option.xml
@@ -38,4 +38,17 @@
android:textColor="?android:attr/colorForeground"
android:text="@string/font_component_option_thumbnail"/>
</FrameLayout>
+
+ <TextView
+ android:id="@+id/option_label"
+ android:layout_width="@dimen/option_label_width"
+ android:layout_height="24dp"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="@dimen/theme_option_label_margin"
+ android:gravity="center"
+ android:textAppearance="@style/OptionTitleTextAppearance"
+ android:singleLine="true"
+ android:scrollHorizontally="true"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"/>
</LinearLayout>
diff --git a/res/layout/theme_icon_option.xml b/res/layout/theme_icon_option.xml
index 292b8cd..0c61372 100644
--- a/res/layout/theme_icon_option.xml
+++ b/res/layout/theme_icon_option.xml
@@ -35,4 +35,17 @@
android:layout_gravity="center"
android:tint="?android:colorForeground"/>
</FrameLayout>
+
+ <TextView
+ android:id="@+id/option_label"
+ android:layout_width="@dimen/option_label_width"
+ android:layout_height="24dp"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="@dimen/theme_option_label_margin"
+ android:gravity="center"
+ android:textAppearance="@style/OptionTitleTextAppearance"
+ android:singleLine="true"
+ android:scrollHorizontally="true"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"/>
</LinearLayout>
diff --git a/res/layout/theme_shape_option.xml b/res/layout/theme_shape_option.xml
index c5682c0..d28fee3 100644
--- a/res/layout/theme_shape_option.xml
+++ b/res/layout/theme_shape_option.xml
@@ -22,12 +22,28 @@
android:id="@+id/option_tile"
android:layout_width="@dimen/option_tile_width"
android:layout_height="@dimen/option_tile_width"
- android:layout_gravity="center"
- android:layout_marginHorizontal="@dimen/component_options_margin_horizontal">
+ 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="@dimen/option_label_width"
+ android:layout_height="24dp"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="@dimen/theme_option_label_margin"
+ android:gravity="center"
+ android:textAppearance="@style/OptionTitleTextAppearance"
+ android:singleLine="true"
+ android:scrollHorizontally="true"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"/>
</LinearLayout>
diff --git a/res/layout/themed_icon_section_view.xml b/res/layout/themed_icon_section_view.xml
index 95be207..2ca84a9 100644
--- a/res/layout/themed_icon_section_view.xml
+++ b/res/layout/themed_icon_section_view.xml
@@ -51,18 +51,4 @@
</LinearLayout>
- <Space
- android:layout_width="0dp"
- android:layout_height="8dp" />
-
- <TextView
- android:id="@+id/beta_tag"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingHorizontal="8dp"
- android:paddingVertical="4dp"
- android:text="@string/beta_title"
- android:textColor="@color/text_color_on_accent"
- style="@style/BetaTagTextStyle" />
-
</com.android.customization.picker.themedicon.ThemedIconSectionView>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 0d5a9a8..ba8abcd 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -33,6 +33,7 @@
<dimen name="options_container_width">0dp</dimen>
<dimen name="option_bottom_margin">8dp</dimen>
<dimen name="option_padding_horizontal">2dp</dimen>
+ <dimen name="option_label_width">100dp</dimen>
<dimen name="option_tile_width">82dp</dimen>
<dimen name="option_tile_radius">20dp</dimen>
<dimen name="option_tile_margin_horizontal">6dp</dimen>
diff --git a/res_override/values/override.xml b/res_override/values/override.xml
index 05af982..a03389f 100644
--- a/res_override/values/override.xml
+++ b/res_override/values/override.xml
@@ -40,5 +40,11 @@
<item>com.google.android.apps.docs</item>
<item>com.google.android.youtube</item>
<item>com.android.vending</item>
+ <item>com.android.settings</item>
+ <item>com.android.deskclock</item>
+ <item>com.android.messaging</item>
+ <item>com.android.contacts</item>
+ <item>com.android.dialer</item>
+ <item>com.android.gallery3d</item>
</array>
</resources>
diff --git a/src/com/android/customization/model/ResourceConstants.java b/src/com/android/customization/model/ResourceConstants.java
index aaee935..c6168ec 100644
--- a/src/com/android/customization/model/ResourceConstants.java
+++ b/src/com/android/customization/model/ResourceConstants.java
@@ -108,8 +108,6 @@
if (sTargetPackages.isEmpty()) {
sTargetPackages.addAll(Arrays.asList(ANDROID_PACKAGE, SETTINGS_PACKAGE,
SYSUI_PACKAGE));
- sTargetPackages.add(getLauncherPackage(context));
- sTargetPackages.add(context.getPackageName());
}
return sTargetPackages.toArray(new String[0]);
}
diff --git a/src/com/android/customization/model/font/FontManager.java b/src/com/android/customization/model/font/FontManager.java
new file mode 100644
index 0000000..0f51b24
--- /dev/null
+++ b/src/com/android/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 com.android.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.myUserId());
+ 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.myUserId());
+ 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/com/android/customization/model/font/FontOption.java b/src/com/android/customization/model/font/FontOption.java
new file mode 100644
index 0000000..05e3ad4
--- /dev/null
+++ b/src/com/android/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 com.android.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/com/android/customization/model/font/FontOptionProvider.java b/src/com/android/customization/model/font/FontOptionProvider.java
new file mode 100644
index 0000000..226fcdf
--- /dev/null
+++ b/src/com/android/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 com.android.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/com/android/customization/model/font/FontSectionController.java b/src/com/android/customization/model/font/FontSectionController.java
new file mode 100644
index 0000000..95c2c9b
--- /dev/null
+++ b/src/com/android/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 com.android.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 com.android.customization.picker.font.FontFragment;
+import com.android.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/com/android/customization/model/iconpack/IconPackManager.java b/src/com/android/customization/model/iconpack/IconPackManager.java
new file mode 100644
index 0000000..affdc73
--- /dev/null
+++ b/src/com/android/customization/model/iconpack/IconPackManager.java
@@ -0,0 +1,141 @@
+/*
+ * 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 com.android.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.isDefault()) {
+ if (mActiveOption.isDefault()) return;
+ mActiveOption.getOverlayPackages().forEach((category, overlay) -> mOverlayManager.disableOverlay(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.myUserId());
+ 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.myUserId());
+ 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/com/android/customization/model/iconpack/IconPackOption.java b/src/com/android/customization/model/iconpack/IconPackOption.java
new file mode 100644
index 0000000..ba83e1a
--- /dev/null
+++ b/src/com/android/customization/model/iconpack/IconPackOption.java
@@ -0,0 +1,167 @@
+/*
+ * 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 com.android.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;
+ private boolean mIsDefault;
+
+ // Mapping from category to overlay package name
+ private final Map<String, String> mOverlayPackageNames = new HashMap<>();
+
+ public IconPackOption(String title, boolean isDefault) {
+ mTitle = title;
+ mIsDefault = isDefault;
+ }
+
+ public IconPackOption(String title) {
+ this(title, false);
+ }
+
+ @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 (mIsDefault) {
+ 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() ==
+ ResourceConstants.getPackagesToOverlay(context).length;
+ }
+
+ public boolean isDefault() {
+ return mIsDefault;
+ }
+}
diff --git a/src/com/android/customization/model/iconpack/IconPackOptionProvider.java b/src/com/android/customization/model/iconpack/IconPackOptionProvider.java
new file mode 100644
index 0000000..ef25182
--- /dev/null
+++ b/src/com/android/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 com.android.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), true);
+ 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/com/android/customization/model/iconpack/IconPackSectionController.java b/src/com/android/customization/model/iconpack/IconPackSectionController.java
new file mode 100644
index 0000000..c84d5ed
--- /dev/null
+++ b/src/com/android/customization/model/iconpack/IconPackSectionController.java
@@ -0,0 +1,109 @@
+/*
+ * 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 com.android.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.picker.iconpack.IconPackFragment;
+import com.android.customization.picker.iconpack.IconPackSectionView;
+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 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/com/android/customization/model/iconshape/IconShapeManager.java b/src/com/android/customization/model/iconshape/IconShapeManager.java
new file mode 100644
index 0000000..3ab510c
--- /dev/null
+++ b/src/com/android/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 com.android.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.myUserId());
+ 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.myUserId());
+ 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/com/android/customization/model/iconshape/IconShapeOption.java b/src/com/android/customization/model/iconshape/IconShapeOption.java
new file mode 100644
index 0000000..aa3fa0d
--- /dev/null
+++ b/src/com/android/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 com.android.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/com/android/customization/model/iconshape/IconShapeOptionProvider.java b/src/com/android/customization/model/iconshape/IconShapeOptionProvider.java
new file mode 100644
index 0000000..cede539
--- /dev/null
+++ b/src/com/android/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 com.android.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/com/android/customization/model/iconshape/IconShapeSectionController.java b/src/com/android/customization/model/iconshape/IconShapeSectionController.java
new file mode 100644
index 0000000..3741cc1
--- /dev/null
+++ b/src/com/android/customization/model/iconshape/IconShapeSectionController.java
@@ -0,0 +1,95 @@
+/*
+ * 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 com.android.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.customization.picker.iconshape.IconShapeFragment;
+import com.android.customization.picker.iconshape.IconShapeSectionView;
+
+import com.android.wallpaper.R;
+import com.android.wallpaper.model.CustomizationSectionController;
+
+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/com/android/customization/module/DefaultCustomizationSections.java b/src/com/android/customization/module/DefaultCustomizationSections.java
index 232e948..61f0978 100644
--- a/src/com/android/customization/module/DefaultCustomizationSections.java
+++ b/src/com/android/customization/module/DefaultCustomizationSections.java
@@ -8,10 +8,17 @@
import androidx.lifecycle.ViewModelProvider;
import com.android.customization.model.color.ColorSectionController;
+import com.android.customization.model.font.FontManager;
+import com.android.customization.model.font.FontSectionController;
import com.android.customization.model.grid.GridOptionsManager;
import com.android.customization.model.grid.GridSectionController;
+import com.android.customization.model.iconpack.IconPackManager;
+import com.android.customization.model.iconpack.IconPackSectionController;
+import com.android.customization.model.iconshape.IconShapeManager;
+import com.android.customization.model.iconshape.IconShapeSectionController;
import com.android.customization.model.mode.DarkModeSectionController;
import com.android.customization.model.mode.DarkModeSnapshotRestorer;
+import com.android.customization.model.theme.OverlayManagerCompat;
import com.android.customization.model.themedicon.ThemedIconSectionController;
import com.android.customization.model.themedicon.ThemedIconSwitchProvider;
import com.android.customization.model.themedicon.domain.interactor.ThemedIconInteractor;
@@ -242,6 +249,29 @@
lifecycleOwner,
/* isRevampedUiEnabled= */ false));
+ // Lock screen quick affordances section.
+ sectionControllers.add(
+ new KeyguardQuickAffordanceSectionController(
+ sectionNavigationController,
+ mKeyguardQuickAffordancePickerInteractor,
+ new ViewModelProvider(
+ activity,
+ mKeyguardQuickAffordancePickerViewModelFactory)
+ .get(KeyguardQuickAffordancePickerViewModel.class),
+ lifecycleOwner));
+
+ // Icon pack selection section.
+ sectionControllers.add(new IconPackSectionController(
+ IconPackManager.getInstance(activity, new OverlayManagerCompat(activity)), sectionNavigationController));
+
+ // Font selection section.
+ sectionControllers.add(new FontSectionController(
+ FontManager.getInstance(activity, new OverlayManagerCompat(activity)), sectionNavigationController));
+
+ // Icon shape selection section.
+ sectionControllers.add(new IconShapeSectionController(
+ IconShapeManager.getInstance(activity, new OverlayManagerCompat(activity)), sectionNavigationController));
+
return sectionControllers;
}
}
diff --git a/src/com/android/customization/picker/font/FontFragment.java b/src/com/android/customization/picker/font/FontFragment.java
new file mode 100644
index 0000000..66984e9
--- /dev/null
+++ b/src/com/android/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 com.android.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 com.android.customization.model.font.FontOption;
+import com.android.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/com/android/customization/picker/font/FontSectionView.java b/src/com/android/customization/picker/font/FontSectionView.java
new file mode 100644
index 0000000..f1c76b8
--- /dev/null
+++ b/src/com/android/customization/picker/font/FontSectionView.java
@@ -0,0 +1,12 @@
+package com.android.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/com/android/customization/picker/iconpack/IconPackFragment.java b/src/com/android/customization/picker/iconpack/IconPackFragment.java
new file mode 100644
index 0000000..73e3877
--- /dev/null
+++ b/src/com/android/customization/picker/iconpack/IconPackFragment.java
@@ -0,0 +1,205 @@
+/*
+ * 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 com.android.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.iconpack.IconPackOption;
+import com.android.customization.model.iconpack.IconPackManager;
+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 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/com/android/customization/picker/iconpack/IconPackSectionView.java b/src/com/android/customization/picker/iconpack/IconPackSectionView.java
new file mode 100644
index 0000000..7e626f5
--- /dev/null
+++ b/src/com/android/customization/picker/iconpack/IconPackSectionView.java
@@ -0,0 +1,12 @@
+package com.android.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/com/android/customization/picker/iconshape/IconShapeFragment.java b/src/com/android/customization/picker/iconshape/IconShapeFragment.java
new file mode 100644
index 0000000..257bb39
--- /dev/null
+++ b/src/com/android/customization/picker/iconshape/IconShapeFragment.java
@@ -0,0 +1,202 @@
+/*
+ * 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 com.android.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.iconshape.IconShapeOption;
+import com.android.customization.model.iconshape.IconShapeManager;
+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 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/com/android/customization/picker/iconshape/IconShapeSectionView.java b/src/com/android/customization/picker/iconshape/IconShapeSectionView.java
new file mode 100644
index 0000000..654b0d0
--- /dev/null
+++ b/src/com/android/customization/picker/iconshape/IconShapeSectionView.java
@@ -0,0 +1,12 @@
+package com.android.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);
+ }
+}
diff --git a/src/com/android/customization/widget/OptionSelectorController.java b/src/com/android/customization/widget/OptionSelectorController.java
index 8c7af00..6eb052c 100644
--- a/src/com/android/customization/widget/OptionSelectorController.java
+++ b/src/com/android/customization/widget/OptionSelectorController.java
@@ -207,6 +207,7 @@
}
if (holder.labelView != null) {
holder.labelView.setText(option.getTitle());
+ holder.labelView.setSelected(true);
}
holder.itemView.setActivated(option.equals(mSelectedOption));
option.bindThumbnailTile(holder.tileView);