Send messages to Launcher preview when previewing color
When the user selects a color in the color picker, generate a list of
color ids and another list of their corresponding color overrides, and
send messages to the Launcher preview to update accordingly.
Color generation was previously done in the Launcher
MaterialColorsGenerator, but now moved to the picker.
Flag: com.android.systemui.shared.new_customization_picker_ui
Test: manually verified that colors are previewed and not applied
Bug: 350718581
Change-Id: I839afd886e23df4d505517a5b7a60108855fa01c
diff --git a/src/com/android/customization/model/color/ColorOption.java b/src/com/android/customization/model/color/ColorOption.java
index ae695dd..d3886a0 100644
--- a/src/com/android/customization/model/color/ColorOption.java
+++ b/src/com/android/customization/model/color/ColorOption.java
@@ -103,6 +103,23 @@
}
/**
+ * Gets the seed color from the overlay packages in hex string.
+ *
+ * @return a string representing the seed color, or null if the color option is generated from
+ * the default seed.
+ */
+ public Integer getSeedColor() {
+ String seedColor = mPackagesByCategory.get(OVERLAY_CATEGORY_SYSTEM_PALETTE);
+ if (TextUtils.isEmpty(seedColor)) {
+ return null;
+ }
+ if (!seedColor.startsWith("#")) {
+ seedColor = "#" + seedColor;
+ }
+ return Color.parseColor(seedColor);
+ }
+
+ /**
* Gets the seed color from the overlay packages for logging.
*
* @return an int representing the seed color, or NULL_SEED_COLOR
diff --git a/src/com/android/customization/picker/color/data/util/MaterialColorsGenerator.kt b/src/com/android/customization/picker/color/data/util/MaterialColorsGenerator.kt
new file mode 100644
index 0000000..a921365
--- /dev/null
+++ b/src/com/android/customization/picker/color/data/util/MaterialColorsGenerator.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.customization.picker.color.data.util
+
+import android.app.WallpaperColors
+import android.content.Context
+import android.content.res.Configuration
+import android.provider.Settings
+import android.util.Log
+import android.util.SparseIntArray
+import com.android.customization.model.ResourceConstants
+import com.android.systemui.monet.ColorScheme
+import com.android.systemui.monet.Style
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+import javax.inject.Singleton
+import org.json.JSONException
+import org.json.JSONObject
+
+/**
+ * Extract material next colors from wallpaper colors. Based on Nexus Launcher's
+ * MaterialColorsGenerator, nexuslauncher/widget/MaterialColorsGenerator.java
+ */
+@Singleton
+class MaterialColorsGenerator
+@Inject
+constructor(
+ @ApplicationContext private val applicationContext: Context,
+ private val secureSettingsRepository: SecureSettingsRepository,
+) {
+ private fun addShades(shades: List<Int>, resources: IntArray, output: SparseIntArray) {
+ if (shades.size != resources.size) {
+ Log.e(TAG, "The number of shades computed doesn't match the number of resources.")
+ return
+ }
+ for (i in resources.indices) {
+ output.put(resources[i], 0xff000000.toInt() or shades[i])
+ }
+ }
+
+ /**
+ * Generates the mapping from system color resources to values from wallpaper colors.
+ *
+ * @return a list of color resource IDs and a corresponding list of their color values
+ */
+ suspend fun generate(colors: WallpaperColors): Pair<IntArray, IntArray> {
+ val isDarkMode =
+ (applicationContext.resources.configuration.uiMode and
+ Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
+ val colorScheme = ColorScheme(colors, isDarkMode, fetchThemeStyleFromSetting())
+ return generate(colorScheme)
+ }
+
+ /**
+ * Generates the mapping from system color resources to values from color seed and style.
+ *
+ * @return a list of color resource IDs and a corresponding list of their color values
+ */
+ fun generate(colorSeed: Int, style: Style): Pair<IntArray, IntArray> {
+ val isDarkMode =
+ (applicationContext.resources.configuration.uiMode and
+ Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
+ val colorScheme = ColorScheme(colorSeed, isDarkMode, style)
+ return generate(colorScheme)
+ }
+
+ private fun generate(colorScheme: ColorScheme): Pair<IntArray, IntArray> {
+ val allNeutralColors: MutableList<Int> = ArrayList()
+ allNeutralColors.addAll(colorScheme.neutral1.allShades)
+ allNeutralColors.addAll(colorScheme.neutral2.allShades)
+
+ val allAccentColors: MutableList<Int> = ArrayList()
+ allAccentColors.addAll(colorScheme.accent1.allShades)
+ allAccentColors.addAll(colorScheme.accent2.allShades)
+ allAccentColors.addAll(colorScheme.accent3.allShades)
+
+ return Pair(
+ NEUTRAL_RESOURCES + ACCENT_RESOURCES,
+ (allNeutralColors + allAccentColors).toIntArray(),
+ )
+ }
+
+ private suspend fun fetchThemeStyleFromSetting(): Style {
+ val overlayPackageJson =
+ secureSettingsRepository.getString(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES)
+ return if (!overlayPackageJson.isNullOrEmpty()) {
+ try {
+ val jsonObject = JSONObject(overlayPackageJson)
+ Style.valueOf(jsonObject.getString(ResourceConstants.OVERLAY_CATEGORY_THEME_STYLE))
+ } catch (e: (JSONException)) {
+ Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e)
+ Style.TONAL_SPOT
+ } catch (e: IllegalArgumentException) {
+ Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e)
+ Style.TONAL_SPOT
+ }
+ } else {
+ Style.TONAL_SPOT
+ }
+ }
+
+ companion object {
+ private const val TAG = "MaterialColorsGenerator"
+
+ private val ACCENT_RESOURCES =
+ intArrayOf(
+ android.R.color.system_accent1_0,
+ android.R.color.system_accent1_10,
+ android.R.color.system_accent1_50,
+ android.R.color.system_accent1_100,
+ android.R.color.system_accent1_200,
+ android.R.color.system_accent1_300,
+ android.R.color.system_accent1_400,
+ android.R.color.system_accent1_500,
+ android.R.color.system_accent1_600,
+ android.R.color.system_accent1_700,
+ android.R.color.system_accent1_800,
+ android.R.color.system_accent1_900,
+ android.R.color.system_accent1_1000,
+ android.R.color.system_accent2_0,
+ android.R.color.system_accent2_10,
+ android.R.color.system_accent2_50,
+ android.R.color.system_accent2_100,
+ android.R.color.system_accent2_200,
+ android.R.color.system_accent2_300,
+ android.R.color.system_accent2_400,
+ android.R.color.system_accent2_500,
+ android.R.color.system_accent2_600,
+ android.R.color.system_accent2_700,
+ android.R.color.system_accent2_800,
+ android.R.color.system_accent2_900,
+ android.R.color.system_accent2_1000,
+ android.R.color.system_accent3_0,
+ android.R.color.system_accent3_10,
+ android.R.color.system_accent3_50,
+ android.R.color.system_accent3_100,
+ android.R.color.system_accent3_200,
+ android.R.color.system_accent3_300,
+ android.R.color.system_accent3_400,
+ android.R.color.system_accent3_500,
+ android.R.color.system_accent3_600,
+ android.R.color.system_accent3_700,
+ android.R.color.system_accent3_800,
+ android.R.color.system_accent3_900,
+ android.R.color.system_accent3_1000,
+ )
+ private val NEUTRAL_RESOURCES =
+ intArrayOf(
+ android.R.color.system_neutral1_0,
+ android.R.color.system_neutral1_10,
+ android.R.color.system_neutral1_50,
+ android.R.color.system_neutral1_100,
+ android.R.color.system_neutral1_200,
+ android.R.color.system_neutral1_300,
+ android.R.color.system_neutral1_400,
+ android.R.color.system_neutral1_500,
+ android.R.color.system_neutral1_600,
+ android.R.color.system_neutral1_700,
+ android.R.color.system_neutral1_800,
+ android.R.color.system_neutral1_900,
+ android.R.color.system_neutral1_1000,
+ android.R.color.system_neutral2_0,
+ android.R.color.system_neutral2_10,
+ android.R.color.system_neutral2_50,
+ android.R.color.system_neutral2_100,
+ android.R.color.system_neutral2_200,
+ android.R.color.system_neutral2_300,
+ android.R.color.system_neutral2_400,
+ android.R.color.system_neutral2_500,
+ android.R.color.system_neutral2_600,
+ android.R.color.system_neutral2_700,
+ android.R.color.system_neutral2_800,
+ android.R.color.system_neutral2_900,
+ android.R.color.system_neutral2_1000,
+ )
+ }
+}
diff --git a/src/com/android/wallpaper/picker/common/preview/ui/binder/ThemePickerWorkspaceCallbackBinder.kt b/src/com/android/wallpaper/picker/common/preview/ui/binder/ThemePickerWorkspaceCallbackBinder.kt
index eec7d5a..b9cc997 100644
--- a/src/com/android/wallpaper/picker/common/preview/ui/binder/ThemePickerWorkspaceCallbackBinder.kt
+++ b/src/com/android/wallpaper/picker/common/preview/ui/binder/ThemePickerWorkspaceCallbackBinder.kt
@@ -16,6 +16,7 @@
package com.android.wallpaper.picker.common.preview.ui.binder
+import android.app.WallpaperManager
import android.os.Bundle
import android.os.Message
import androidx.core.os.bundleOf
@@ -23,6 +24,9 @@
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.customization.model.color.ColorOptionsProvider
+import com.android.customization.picker.color.data.util.MaterialColorsGenerator
+import com.android.systemui.monet.ColorScheme
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID
@@ -44,8 +48,11 @@
@Singleton
class ThemePickerWorkspaceCallbackBinder
@Inject
-constructor(private val defaultWorkspaceCallbackBinder: DefaultWorkspaceCallbackBinder) :
- WorkspaceCallbackBinder {
+constructor(
+ private val wallpaperManager: WallpaperManager,
+ private val defaultWorkspaceCallbackBinder: DefaultWorkspaceCallbackBinder,
+ private val materialColorsGenerator: MaterialColorsGenerator,
+) : WorkspaceCallbackBinder {
override fun bind(
workspaceCallback: Message,
@@ -81,7 +88,7 @@
KEY_INITIALLY_SELECTED_SLOT_ID,
SLOT_ID_BOTTOM_START,
)
- }
+ },
)
else ->
workspaceCallback.sendMessage(
@@ -135,7 +142,32 @@
viewModel.shapeAndGridPickerViewModel.previewingGridOptionKey.collect {
workspaceCallback.sendMessage(
MESSAGE_ID_UPDATE_GRID,
- bundleOf(KEY_GRID_NAME to it)
+ bundleOf(KEY_GRID_NAME to it),
+ )
+ }
+ }
+
+ launch {
+ viewModel.colorPickerViewModel2.previewingColorOption.collect {
+ if (it == null) {
+ workspaceCallback.sendMessage(MESSAGE_ID_UPDATE_COLOR, Bundle())
+ return@collect
+ }
+ val seedColor =
+ it.colorOption.seedColor
+ ?: getSeedColorFromSource(it.colorOption.source)
+ ?: return@collect
+ val (ids, colors) =
+ materialColorsGenerator.generate(
+ seedColor,
+ it.colorOption.style,
+ )
+ workspaceCallback.sendMessage(
+ MESSAGE_ID_UPDATE_COLOR,
+ Bundle().apply {
+ putIntArray(KEY_COLOR_RESOURCE_IDS, ids)
+ putIntArray(KEY_COLOR_VALUES, colors)
+ },
)
}
}
@@ -144,8 +176,22 @@
}
}
+ private fun getSeedColorFromSource(source: String?): Int? {
+ return when (source) {
+ ColorOptionsProvider.COLOR_SOURCE_HOME -> WallpaperManager.FLAG_SYSTEM
+ ColorOptionsProvider.COLOR_SOURCE_LOCK -> WallpaperManager.FLAG_LOCK
+ else -> null
+ }
+ ?.let { wallpaperManager.getWallpaperColors(it) }
+ ?.let { ColorScheme.getSeedColor(it) }
+ }
+
companion object {
const val MESSAGE_ID_UPDATE_GRID = 7414
const val KEY_GRID_NAME = "grid_name"
+
+ const val MESSAGE_ID_UPDATE_COLOR = 856
+ const val KEY_COLOR_RESOURCE_IDS: String = "color_resource_ids"
+ const val KEY_COLOR_VALUES: String = "color_values"
}
}