ThemePicker: add support for custom themed icon pack [2/3]
Change-Id: Ib59201fea0939c9ebf2ebbc252e9259532dcbc72
Signed-off-by: jhonboy121 <alfredmathew05@gmail.com>
Signed-off-by: minaripenguin <minaripenguin@users.noreply.github.com>
Signed-off-by: PainKiller3 <ninadpatil100@gmail.com>
Signed-off-by: Dmitrii <bankersenator@gmail.com>
Signed-off-by: Jackeagle <jackeagle102@gmail.com>
diff --git a/Android.bp b/Android.bp
index cce9aed..9e5448e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -60,6 +60,9 @@
"SettingsLibSettingsTheme",
"SystemUI-statsd",
"styleprotoslite",
+ "androidx.lifecycle_lifecycle-runtime-ktx",
+ "kotlinx_coroutines_android",
+ "kotlinx_coroutines",
],
jni_libs: [
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index bc2103d..0af3cab 100755
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -50,6 +50,8 @@
<uses-permission android:name="com.android.launcher3.permission.READ_SETTINGS" />
<uses-permission android:name="com.android.launcher3.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+
<application
tools:replace="android:icon,android:name"
android:extractNativeLibs="false"
diff --git a/res/layout/themed_icon_pack_section_view.xml b/res/layout/themed_icon_pack_section_view.xml
new file mode 100644
index 0000000..573920b
--- /dev/null
+++ b/res/layout/themed_icon_pack_section_view.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 FlamingoOS Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.customization.picker.themedicon.ThemedIconPackSectionView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?selectableItemBackground"
+ android:clickable="true"
+ android:orientation="vertical"
+ android:paddingHorizontal="@dimen/section_horizontal_padding"
+ android:paddingVertical="@dimen/section_vertical_padding">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/themed_icon_custom_title"
+ style="@style/SectionTitleTextStyle" />
+
+ <TextView
+ android:id="@+id/themed_icon_pack_summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/SectionSubtitleTextStyle"/>
+ </LinearLayout>
+</com.android.customization.picker.themedicon.ThemedIconPackSectionView>
diff --git a/res/layout/themed_icons_app_list_item.xml b/res/layout/themed_icons_app_list_item.xml
new file mode 100644
index 0000000..37979a8
--- /dev/null
+++ b/res/layout/themed_icons_app_list_item.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 FlamingoOS 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.constraintlayout.widget.ConstraintLayout 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="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginVertical="4dp"
+ android:scaleType="centerInside"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent" />
+
+ <TextView
+ android:id="@+id/label"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/default_margin"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ app:layout_constraintStart_toEndOf="@id/icon"
+ app:layout_constraintEnd_toStartOf="@id/radio_button"
+ app:layout_constraintTop_toTopOf="@id/icon"
+ app:layout_constraintBottom_toTopOf="@id/package_name" />
+
+ <TextView
+ android:id="@+id/package_name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
+ app:layout_constraintStart_toStartOf="@id/label"
+ app:layout_constraintEnd_toEndOf="@id/label"
+ app:layout_constraintTop_toBottomOf="@id/label"
+ app:layout_constraintBottom_toBottomOf="@id/icon" />
+
+ <RadioButton
+ android:id="@+id/radio_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="false"
+ android:clickable="false"
+ app:layout_constraintBottom_toBottomOf="@id/icon"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="@id/icon" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/res/layout/themed_icons_app_list_layout.xml b/res/layout/themed_icons_app_list_layout.xml
new file mode 100644
index 0000000..be337be
--- /dev/null
+++ b/res/layout/themed_icons_app_list_layout.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 FlamingoOS 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" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/apps_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <androidx.core.widget.ContentLoadingProgressBar
+ android:id="@+id/loading_progress"
+ style="@android:style/Widget.DeviceDefault.ProgressBar"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:indeterminate="true"
+ android:layout_gravity="center" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/bliss_strings.xml b/res/values/bliss_strings.xml
index 6eeb3b1..abeb26b 100644
--- a/res/values/bliss_strings.xml
+++ b/res/values/bliss_strings.xml
@@ -1,20 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- 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
+ Copyright (C) 2020-2022 The AwakenOS Project
- http://www.apache.org/licenses/LICENSE-2.0
+ 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
- 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.
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
-->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
<!-- Icon's section -->
<string name="icon_preview_card_content_description">Icon Preview</string>
@@ -23,5 +24,7 @@
<!-- Font's section -->
<string name="font_title">System Fonts</string>
<string name="font_preview_card_content_description">Font Preview</string>
-</resources>
+ <string name="system_icons">System icons</string>
+ <string name="themed_icon_custom_title">Custom themed icons</string>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 0ad221e..a64838b 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -135,4 +135,7 @@
<dimen name="color_seed_option_tile_padding">10dp</dimen>
<dimen name="color_seed_option_tile_padding_selected">6dp</dimen>
<dimen name="color_seed_chip_margin">14dp</dimen>
+
+ <!-- For the custom themed icons -->
+ <dimen name="default_margin">16dp</dimen>
</resources>
diff --git a/src/com/android/customization/ResourceProxy.java b/src/com/android/customization/ResourceProxy.java
new file mode 100644
index 0000000..55cd908
--- /dev/null
+++ b/src/com/android/customization/ResourceProxy.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 FlamingoOS 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;
+
+import com.android.wallpaper.R;
+
+/**
+ * Hack of the century. For some reason still unknown,
+ * aapt compiled resources are unresolved in kt files.
+ * So added proxy variables to work around this mess.
+ */
+public class ResourceProxy {
+ public static final class Id {
+ public static final int themed_icon_pack_summary = R.id.themed_icon_pack_summary;
+
+ public static final int apps_list = R.id.apps_list;
+ public static final int loading_progress = R.id.loading_progress;
+
+ public static final int icon = R.id.icon;
+ public static final int label = R.id.label;
+ public static final int package_name = R.id.package_name;
+ public static final int radio_button = R.id.radio_button;
+ }
+
+ public static final class String {
+ public static final int themed_icon_custom_title = R.string.themed_icon_custom_title;
+ public static final int system_icons = R.string.system_icons;
+ }
+
+ public static final class Layout {
+ public static final int themed_icon_pack_section_view = R.layout.themed_icon_pack_section_view;
+ public static final int themed_icons_app_list_layout = R.layout.themed_icons_app_list_layout;
+ public static final int themed_icons_app_list_item = R.layout.themed_icons_app_list_item;
+ }
+}
diff --git a/src/com/android/customization/model/themedicon/ThemedIconPackSectionController.kt b/src/com/android/customization/model/themedicon/ThemedIconPackSectionController.kt
new file mode 100644
index 0000000..bfc1d09
--- /dev/null
+++ b/src/com/android/customization/model/themedicon/ThemedIconPackSectionController.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2022 FlamingoOS 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.themedicon
+
+import android.content.ContentValues
+import android.content.Context
+import android.content.pm.PackageManager.NameNotFoundException
+import android.database.Cursor
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.TextView
+
+import androidx.lifecycle.LifecycleCoroutineScope
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+
+import com.android.customization.picker.themedicon.ThemedIconPackPickerFragment
+import com.android.customization.picker.themedicon.ThemedIconPackSectionView
+import com.android.customization.ResourceProxy
+import com.android.wallpaper.model.CustomizationSectionController
+import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+
+class ThemedIconPackSectionController(
+ private val context: Context,
+ private val navigationController: CustomizationSectionNavigationController,
+ private val themedIconSwitchProvider: ThemedIconSwitchProvider,
+ private val lifecycleOwner: LifecycleOwner,
+ savedInstanceState: Bundle?,
+) : CustomizationSectionController<ThemedIconPackSectionView> {
+
+ private val pm = context.packageManager
+ private val mutex = Mutex()
+
+ private val lifecycleScope: LifecycleCoroutineScope
+ get() = lifecycleOwner.lifecycleScope
+
+ private val themedIconUtils: ThemedIconUtils
+ get() = themedIconSwitchProvider.themedIconUtils
+
+ private var themedIconPackage: String? = null
+ private var summaryView: TextView? = null
+
+ init {
+ val pkg = savedInstanceState?.getString(
+ KEY_THEMED_ICON_PACK,
+ null /* defaultValue */
+ )
+ if (pkg == null) {
+ if (isAvailable(context)) {
+ fetchThemedIconPack()
+ }
+ } else {
+ themedIconPackage = pkg
+ }
+ }
+
+ private fun fetchThemedIconPack() {
+ val uri = themedIconUtils.getUriForPath(KEY_THEMED_ICON_PACK)
+ lifecycleScope.launchWhenCreated {
+ withContext(Dispatchers.Default) {
+ val cursor = context.contentResolver.query(
+ uri,
+ null, /* projection*/
+ null, /* selection */
+ null, /* selectionArgs */
+ null, /* sortOrder */
+ ) ?: return@withContext
+ cursor.use {
+ it.moveToNext()
+ mutex.withLock {
+ themedIconPackage = it.getString(it.getColumnIndex(KEY_NAME))
+ }
+ }
+ }
+ }
+ }
+
+ override fun isAvailable(context: Context?) = themedIconSwitchProvider.isThemedIconAvailable()
+
+ override fun createView(context: Context): ThemedIconPackSectionView {
+ val view = LayoutInflater.from(context).inflate(
+ ResourceProxy.Layout.themed_icon_pack_section_view,
+ null /* root */
+ ) as ThemedIconPackSectionView
+ summaryView = view.findViewById(ResourceProxy.Id.themed_icon_pack_summary)
+ lifecycleScope.launch {
+ mutex.withLock {
+ updateSummary(themedIconPackage)
+ }
+ }
+ view.setOnClickListener {
+ val pickerFragment = ThemedIconPackPickerFragment()
+ pickerFragment.setOnAppSelectListener {
+ mutex.withLock {
+ themedIconPackage = it
+ }
+ withContext(Dispatchers.Default) {
+ savePackage(it)
+ }
+ // Toggling it on and off will trigger updates. Hacky but rather easy.
+ themedIconSwitchProvider.setThemedIconEnabled(false)
+ themedIconSwitchProvider.setThemedIconEnabled(it != null)
+ }
+ pickerFragment.setInitalCheckedApp(themedIconPackage)
+ navigationController.navigateTo(pickerFragment)
+ }
+ return view
+ }
+
+ override fun onSaveInstanceState(savedInstanceState: Bundle) {
+ lifecycleScope.launch {
+ mutex.withLock {
+ savedInstanceState.putString(KEY_THEMED_ICON_PACK, themedIconPackage)
+ }
+ }
+ }
+
+ private fun updateSummary(packageName: String?) {
+ summaryView?.text = if (packageName != null) {
+ val packageLabel = getLabelForPackageName(packageName)
+ if (packageLabel != null) {
+ "$packageLabel ($packageName)"
+ } else {
+ packageName
+ }
+ } else {
+ context.getString(ResourceProxy.String.system_icons)
+ }
+ }
+
+ private fun getLabelForPackageName(packageName: String): CharSequence? =
+ try {
+ pm.getApplicationInfo(packageName, 0 /* flags */).loadLabel(pm)
+ } catch (e: NameNotFoundException) {
+ null
+ }
+
+ private suspend fun savePackage(packageName: String?) {
+ val values = ContentValues().apply {
+ put(KEY_NAME, packageName)
+ }
+ val uri = themedIconUtils.getUriForPath(KEY_THEMED_ICON_PACK)
+ val result = context.contentResolver.update(
+ uri,
+ values,
+ null, /* where */
+ null, /* selectionArgs */
+ )
+ if (result != RESULT_SUCCESS) {
+ Log.e(TAG, "Failed to update themed icon pack")
+ }
+ }
+
+ companion object {
+ private const val TAG = "ThemedIconPackSectionController"
+
+ private const val KEY_THEMED_ICON_PACK = "themed_icon_pack"
+ private const val KEY_NAME = "name"
+
+ private const val RESULT_SUCCESS = 1
+ }
+}
diff --git a/src/com/android/customization/model/themedicon/ThemedIconSwitchProvider.java b/src/com/android/customization/model/themedicon/ThemedIconSwitchProvider.java
index 9acd319..d52b6f5 100644
--- a/src/com/android/customization/model/themedicon/ThemedIconSwitchProvider.java
+++ b/src/com/android/customization/model/themedicon/ThemedIconSwitchProvider.java
@@ -130,6 +130,10 @@
});
}
+ protected ThemedIconUtils getThemedIconUtils() {
+ return mThemedIconUtils;
+ }
+
private void postMainThread(Runnable runnable) {
new Handler(Looper.getMainLooper()).post(runnable);
}
diff --git a/src/com/android/customization/module/DefaultCustomizationSections.java b/src/com/android/customization/module/DefaultCustomizationSections.java
index d538530..a34d51c 100644
--- a/src/com/android/customization/module/DefaultCustomizationSections.java
+++ b/src/com/android/customization/module/DefaultCustomizationSections.java
@@ -16,6 +16,7 @@
import com.android.customization.model.iconpack.IconPackSectionController;
import com.android.customization.model.mode.DarkModeSectionController;
import com.android.customization.model.theme.OverlayManagerCompat;
+import com.android.customization.model.themedicon.ThemedIconPackSectionController;
import com.android.customization.model.themedicon.ThemedIconSectionController;
import com.android.customization.model.themedicon.ThemedIconSwitchProvider;
import com.android.wallpaper.model.CustomizationSectionController;
@@ -40,7 +41,7 @@
WallpaperPreviewNavigator wallpaperPreviewNavigator,
CustomizationSectionNavigationController sectionNavigationController,
@Nullable Bundle savedInstanceState) {
- List<CustomizationSectionController<?>> sectionControllers = new ArrayList<>();
+ final List<CustomizationSectionController<?>> sectionControllers = new ArrayList<>();
// Wallpaper section.
sectionControllers.add(new WallpaperSectionController(
@@ -48,7 +49,7 @@
workspaceViewModel, sectionNavigationController, wallpaperPreviewNavigator,
savedInstanceState));
- // Color section
+ // Theme color section.
sectionControllers.add(new ColorSectionController(
activity, wallpaperColorsViewModel, lifecycleOwner, savedInstanceState));
@@ -61,6 +62,12 @@
ThemedIconSwitchProvider.getInstance(activity), workspaceViewModel,
savedInstanceState));
+ // Custom themed icon pack section.
+ sectionControllers.add(new ThemedIconPackSectionController(
+ activity, sectionNavigationController,
+ ThemedIconSwitchProvider.getInstance(activity),
+ lifecycleOwner, savedInstanceState));
+
// App grid section.
sectionControllers.add(new GridSectionController(
GridOptionsManager.getInstance(activity), sectionNavigationController));
diff --git a/src/com/android/customization/picker/themedicon/ThemedIconPackPickerFragment.kt b/src/com/android/customization/picker/themedicon/ThemedIconPackPickerFragment.kt
new file mode 100644
index 0000000..01efe79
--- /dev/null
+++ b/src/com/android/customization/picker/themedicon/ThemedIconPackPickerFragment.kt
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2022 FlamingoOS 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.themedicon
+
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.widget.ImageView
+import android.widget.RadioButton
+import android.widget.TextView
+
+import androidx.core.widget.ContentLoadingProgressBar
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+
+import com.android.customization.ResourceProxy
+import com.android.wallpaper.picker.AppbarFragment
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class ThemedIconPackPickerFragment : AppbarFragment() {
+
+ private lateinit var pm: PackageManager
+ private lateinit var adapter: AppListAdapter
+
+ private var recyclerView: RecyclerView? = null
+ private var progressBar: ContentLoadingProgressBar? = null
+ private var appSelectListener: suspend CoroutineScope.(String?) -> Unit = {}
+ private var initialCheckedApp: String? = null
+
+ fun setOnAppSelectListener(listener: suspend CoroutineScope.(String?) -> Unit) {
+ appSelectListener = listener
+ }
+
+ fun setInitalCheckedApp(checkedApp: String?) {
+ initialCheckedApp = checkedApp
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ pm = requireContext().packageManager
+ }
+
+ override fun getDefaultTitle(): CharSequence = getString(ResourceProxy.String.themed_icon_custom_title)
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? = inflater.inflate(
+ ResourceProxy.Layout.themed_icons_app_list_layout,
+ container,
+ false /* attachToParent */
+ )
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ setUpToolbar(view)
+
+ // For nav bar edge-to-edge effect.
+ view.setOnApplyWindowInsetsListener { v, windowInsets ->
+ val systemBarsInsets = windowInsets.getInsets(WindowInsets.Type.systemBars())
+ v.setPadding(
+ v.paddingLeft,
+ systemBarsInsets.top,
+ v.paddingRight,
+ systemBarsInsets.bottom
+ )
+ return@setOnApplyWindowInsetsListener WindowInsets.CONSUMED
+ }
+
+ adapter = AppListAdapter(requireActivity().layoutInflater).also {
+ it.setCheckedItem(initialCheckedApp)
+ it.setOnItemSelectListener {
+ lifecycleScope.launch {
+ appSelectListener(it)
+ }
+ }
+ }
+
+ recyclerView = view.findViewById<RecyclerView>(ResourceProxy.Id.apps_list)?.also {
+ it.layoutManager = LinearLayoutManager(context)
+ it.adapter = adapter
+ }
+
+ progressBar = view.findViewById(ResourceProxy.Id.loading_progress)
+ progressBar?.show()
+ lifecycleScope.launch {
+ refreshList()
+ }
+ }
+
+ private suspend fun refreshList() {
+ val list = withContext(Dispatchers.Default) {
+ pm.getInstalledPackages(PackageManager.MATCH_ALL).filter {
+ !it.applicationInfo.isSystemApp() && hasIconResourceMap(it)
+ }.map {
+ AppInfo(
+ it.packageName,
+ it.applicationInfo.loadLabel(pm).toString(),
+ it.applicationInfo.loadIcon(pm),
+ )
+ }.sortedBy {
+ it.label
+ }.toMutableList()
+ }
+ list.add(
+ 0 /* index */,
+ AppInfo(
+ null /* packageName */,
+ getString(ResourceProxy.String.system_icons),
+ pm.getDefaultActivityIcon()
+ )
+ )
+
+ adapter.submitList(list.toList())
+ progressBar?.hide()
+ }
+
+ private fun hasIconResourceMap(packageInfo: PackageInfo): Boolean {
+ val res = try {
+ pm.getResourcesForApplication(packageInfo.applicationInfo)
+ } catch (e: PackageManager.NameNotFoundException) {
+ null
+ } ?: return false
+
+ val resID = res.getIdentifier(
+ "grayscale_icon_map",
+ "xml",
+ packageInfo.packageName
+ )
+ return resID != 0
+ }
+
+ private class AppListAdapter(
+ private val layoutInflater: LayoutInflater
+ ) : ListAdapter<AppInfo, AppListViewHolder>(itemCallback) {
+
+ // Package name of the checked item
+ private var checkedItem: String? = null
+ // Index of the checked item
+ private var checkedItemIndex = -1
+ private var itemSelectListener: (String?) -> Unit = {}
+
+ /**
+ * @param item package name of the checked item
+ */
+ fun setCheckedItem(item: String?) {
+ checkedItem = item
+ updateCheckedItemIndex(currentList)
+ }
+
+ private fun updateCheckedItemIndex(list: List<AppInfo>?) {
+ if (list == null) {
+ checkedItemIndex = -1
+ return
+ }
+ checkedItemIndex = if (checkedItem == null) {
+ 0
+ } else list.indexOfFirst {
+ it.packageName == checkedItem
+ }
+ }
+
+ /**
+ * Lambda parameter will be package name of the checked item
+ */
+ fun setOnItemSelectListener(listener: (String?) -> Unit) {
+ itemSelectListener = listener
+ }
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ) = AppListViewHolder(
+ layoutInflater.inflate(
+ ResourceProxy.Layout.themed_icons_app_list_item,
+ parent,
+ false /* attachToParent */
+ )
+ )
+
+ override fun onBindViewHolder(holder: AppListViewHolder, position: Int) {
+ val item = getItem(position)
+
+ holder.label.text = item.label
+ holder.packageName.visibility = if (item.packageName == null) View.GONE else View.VISIBLE
+ holder.packageName.text = item.packageName
+ holder.icon.setImageDrawable(item.icon)
+ holder.radioButton.isChecked = checkedItemIndex == position
+
+ holder.itemView.setOnClickListener {
+ if (checkedItemIndex == position) return@setOnClickListener
+
+ checkedItem = item.packageName
+ val tmpIndex = checkedItemIndex
+ checkedItemIndex = position
+
+ if (tmpIndex != -1) notifyItemChanged(tmpIndex)
+ notifyItemChanged(checkedItemIndex)
+
+ itemSelectListener(item.packageName)
+ }
+ }
+
+ override fun submitList(list: List<AppInfo>?) {
+ updateCheckedItemIndex(list)
+ super.submitList(list)
+ }
+
+ companion object {
+ private val itemCallback = object : DiffUtil.ItemCallback<AppInfo>() {
+ override fun areItemsTheSame(oldInfo: AppInfo, newInfo: AppInfo) =
+ oldInfo.packageName == newInfo.packageName
+
+ override fun areContentsTheSame(oldInfo: AppInfo, newInfo: AppInfo) =
+ oldInfo == newInfo
+ }
+ }
+ }
+
+ private class AppListViewHolder(
+ itemView: View
+ ) : RecyclerView.ViewHolder(itemView) {
+
+ val icon: ImageView = itemView.findViewById(ResourceProxy.Id.icon)
+ val label: TextView = itemView.findViewById(ResourceProxy.Id.label)
+ val packageName: TextView = itemView.findViewById(ResourceProxy.Id.package_name)
+ val radioButton: RadioButton = itemView.findViewById(ResourceProxy.Id.radio_button)
+ }
+
+ private data class AppInfo(
+ val packageName: String?,
+ val label: String,
+ val icon: Drawable,
+ )
+}
diff --git a/src/com/android/customization/picker/themedicon/ThemedIconPackSectionView.kt b/src/com/android/customization/picker/themedicon/ThemedIconPackSectionView.kt
new file mode 100644
index 0000000..e2491a8
--- /dev/null
+++ b/src/com/android/customization/picker/themedicon/ThemedIconPackSectionView.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 FlamingoOS 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.themedicon
+
+import android.content.Context
+import android.util.AttributeSet
+
+import com.android.wallpaper.picker.SectionView
+
+class ThemedIconPackSectionView(
+ context: Context,
+ attrs: AttributeSet?
+) : SectionView(context, attrs)
\ No newline at end of file