google_pixel: ThemePicker: Pixel inspired accent color selection menu

Inspired by https://github.com/DotOS/android_packages_apps_Customizations

Change-Id: I96670305884efaae8ba1d707ddd406375ed78ed1
Co-authored-by: Adithya R <gh0strider.2k18.reborn@gmail.com>
lineage-19.1
Anay Wadhera 3 years ago committed by Nolen Johnson
parent 025087c2da
commit 814ffba7d5
  1. 29
      ThemePickerGoogle/Android.bp
  2. 2
      ThemePickerGoogle/AndroidManifest.xml
  3. 8
      ThemePickerGoogle/res/drawable/color_chip_medium_filled.xml
  4. 10
      ThemePickerGoogle/res/drawable/color_chip_seed_filled0.xml
  5. 10
      ThemePickerGoogle/res/drawable/color_chip_seed_filled1.xml
  6. 10
      ThemePickerGoogle/res/drawable/color_chip_seed_filled2.xml
  7. 10
      ThemePickerGoogle/res/drawable/color_chip_seed_filled3.xml
  8. 24
      ThemePickerGoogle/res/layout/color_option.xml
  9. 6
      ThemePickerGoogle/res/layout/color_options_view.xml
  10. 27
      ThemePickerGoogle/res/layout/color_section_view.xml
  11. 53
      ThemePickerGoogle/res/layout/color_seed_option.xml
  12. 34
      ThemePickerGoogle/res/values/dimens.xml
  13. 5
      ThemePickerGoogle/res/values/strings.xml
  14. 59
      ThemePickerGoogle/src/com/google/android/customization/model/color/ColorBundle.kt
  15. 238
      ThemePickerGoogle/src/com/google/android/customization/model/color/ColorCustomizationManager.kt
  16. 97
      ThemePickerGoogle/src/com/google/android/customization/model/color/ColorOption.kt
  17. 3
      ThemePickerGoogle/src/com/google/android/customization/model/color/ColorOptionsProvider.kt
  18. 241
      ThemePickerGoogle/src/com/google/android/customization/model/color/ColorProvider.kt
  19. 260
      ThemePickerGoogle/src/com/google/android/customization/model/color/ColorSectionController.kt
  20. 56
      ThemePickerGoogle/src/com/google/android/customization/model/color/ColorSeedOption.kt
  21. 47
      ThemePickerGoogle/src/com/google/android/customization/model/color/ColorUtils.java
  22. 7
      ThemePickerGoogle/src/com/google/android/customization/module/GoogleCustomizationSections.java
  23. 8
      ThemePickerGoogle/src/com/google/android/customization/picker/color/ColorSectionView.kt

@ -46,13 +46,11 @@ genrule {
+ "$(location soong_zip) -o $(out) -C $$RES_DIR -D $$RES_DIR" + "$(location soong_zip) -o $(out) -C $$RES_DIR -D $$RES_DIR"
} }
// android_library {
// Build app code. name: "ThemePickerGoogle_core",
//
android_app {
name: "ThemePickerGoogle",
static_libs: [ static_libs: [
"monet",
"wallpaper-common-deps", "wallpaper-common-deps",
"SettingsLibSettingsTheme", "SettingsLibSettingsTheme",
"SystemUIFlagsLib", "SystemUIFlagsLib",
@ -60,6 +58,27 @@ android_app {
"styleprotoslite", "styleprotoslite",
], ],
optimize: {
enabled: false,
},
resource_dirs: ["res"],
resource_zips: [":WallpaperPicker2_res", ":ThemePicker_res"],
kotlincflags: ["-Xjvm-default=enable"],
}
//
// Build app code.
//
android_app {
name: "ThemePickerGoogle",
static_libs: [
"ThemePickerGoogle_core",
],
srcs: [ srcs: [
":WallpaperPicker2_srcs", ":WallpaperPicker2_srcs",
":ThemePicker_srcs", ":ThemePicker_srcs",

@ -5,6 +5,8 @@
<uses-sdk android:targetSdkVersion="32" android:minSdkVersion="28"/> <uses-sdk android:targetSdkVersion="32" android:minSdkVersion="28"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application <application
tools:replace="android:name" tools:replace="android:name"
android:extractNativeLibs="false" android:extractNativeLibs="false"

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:width="@dimen/component_color_chip_medium_size"
android:height="@dimen/component_color_chip_medium_size" />
<solid android:color="@android:color/black" />
</shape>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<corners android:topLeftRadius="@dimen/component_color_chip_small_size_default" />
<size android:width="@dimen/component_color_chip_small_size_default" android:height="@dimen/component_color_chip_small_size_default" />
<solid android:color="@android:color/black" />
</shape>
</item>
</selector>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<corners android:bottomLeftRadius="@dimen/component_color_chip_small_size_default" />
<size android:width="@dimen/component_color_chip_small_size_default" android:height="@dimen/component_color_chip_small_size_default" />
<solid android:color="@android:color/black" />
</shape>
</item>
</selector>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<corners android:topRightRadius="@dimen/component_color_chip_small_size_default" />
<size android:width="@dimen/component_color_chip_small_size_default" android:height="@dimen/component_color_chip_small_size_default" />
<solid android:color="@android:color/black" />
</shape>
</item>
</selector>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<corners android:bottomRightRadius="@dimen/component_color_chip_small_size_default" />
<size android:width="@dimen/component_color_chip_small_size_default" android:height="@dimen/component_color_chip_small_size_default" />
<solid android:color="@android:color/black" />
</shape>
</item>
</selector>

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingBottom="@dimen/color_option_tile_padding_bottom">
<RelativeLayout
android:id="@+id/option_tile"
android:layout_width="@dimen/option_tile_width_big"
android:layout_height="@dimen/option_tile_width_big"
android:layout_gravity="center_horizontal"
android:background="@drawable/option_border_color"
android:gravity="center">
<ImageView
android:id="@+id/color_preview_icon"
android:layout_width="@dimen/component_color_chip_container_medium_size"
android:layout_height="@dimen/component_color_chip_container_medium_size"
android:layout_marginHorizontal="@dimen/color_option_tile_margin_horizontal" />
</RelativeLayout>
</FrameLayout>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/color_option_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" />

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.customization.picker.color.ColorSectionView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/separated_tabs_horizontal_margin">
<include layout="@layout/separated_tabs" />
</FrameLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/color_view_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/color_options_container_top_margin"
android:clipChildren="false"
android:clipToPadding="false"
android:minHeight="@dimen/color_options_container_min_height"
android:paddingHorizontal="@dimen/section_horizontal_padding" />
</com.google.android.customization.picker.color.ColorSectionView>

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingBottom="@dimen/color_option_tile_padding_bottom">
<FrameLayout
android:id="@+id/option_tile"
android:layout_width="@dimen/option_tile_width_big"
android:layout_height="@dimen/option_tile_width_big"
android:layout_gravity="center_horizontal"
android:background="@drawable/option_border_color">
<ImageView
android:id="@+id/color_preview_0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/color_seed_chip_margin"
android:layout_marginBottom="@dimen/color_seed_chip_margin"
android:src="@drawable/color_chip_seed_filled0" />
<ImageView
android:id="@+id/color_preview_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/color_seed_chip_margin"
android:layout_marginBottom="@dimen/color_seed_chip_margin"
android:src="@drawable/color_chip_seed_filled2" />
<ImageView
android:id="@+id/color_preview_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/color_seed_chip_margin"
android:layout_marginEnd="@dimen/color_seed_chip_margin"
android:src="@drawable/color_chip_seed_filled1" />
<ImageView
android:id="@+id/color_preview_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/color_seed_chip_margin"
android:layout_marginTop="@dimen/color_seed_chip_margin"
android:src="@drawable/color_chip_seed_filled3" />
</FrameLayout>
</FrameLayout>

@ -0,0 +1,34 @@
<?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
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>
<!-- Color section -->
<dimen name="color_option_tile_margin_horizontal">2dp</dimen>
<dimen name="color_option_tile_padding_bottom">11dp</dimen>
<dimen name="color_options_container_min_height">96dp</dimen>
<dimen name="color_options_container_top_margin">24dp</dimen>
<dimen name="color_seed_chip_margin">12dp</dimen>
<dimen name="color_seed_option_tile_padding">10dp</dimen>
<dimen name="color_seed_option_tile_padding_selected">6dp</dimen>
<dimen name="component_color_chip_container_medium_size">48dp</dimen>
<dimen name="component_color_chip_medium_size">48dp</dimen>
<dimen name="component_color_chip_small_size_default">24dp</dimen>
<dimen name="component_color_chip_small_size_selected">32dp</dimen>
<dimen name="option_tile_width_big">82dp</dimen>
<!-- Default page horizontal margin (24dp) - separated tabs inset horizontal (4dp) -->
<dimen name="separated_tabs_horizontal_margin">20dp</dimen>
</resources>

@ -18,4 +18,9 @@
<!-- The name of this application, a theme picker. [CHAR LIMIT=50] --> <!-- The name of this application, a theme picker. [CHAR LIMIT=50] -->
<string name="app_name">Styles</string> <string name="app_name">Styles</string>
<!-- Color Section -->
<string name="wallpaper_color_tab">Wallpaper colors</string>
<string name="wallpaper_color_title">Wallpaper color</string>
<string name="preset_color_tab">Basic colors</string>
<string name="color_changed">Color changed</string>
</resources> </resources>

@ -0,0 +1,59 @@
package com.google.android.customization.model.color
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.graphics.drawable.GradientDrawable
import android.view.View
import android.widget.ImageView
import com.android.wallpaper.R
class ColorBundle(
title: String?,
map: Map<String?, String?>?,
isDefault: Boolean,
index: Int,
private val mPreviewInfo: PreviewInfo
) : ColorOption(
title!!, map!!, isDefault, index
) {
class PreviewInfo(
val secondaryColorLight: Int,
val secondaryColorDark: Int,
)
@SuppressLint("UseCompatLoadingForDrawables")
override fun bindThumbnailTile(view: View) {
val resources = view.context.resources
val thumbnailView = view.findViewById<ImageView>(R.id.color_preview_icon)
val secondaryColor =
if ((resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES) mPreviewInfo.secondaryColorDark
else mPreviewInfo.secondaryColorLight
val gradientDrawable = view.resources.getDrawable(
R.drawable.color_chip_medium_filled, thumbnailView.context.theme
) as GradientDrawable
if (secondaryColor != 0) {
gradientDrawable.setTintList(ColorStateList.valueOf(secondaryColor))
} else {
gradientDrawable.setTintList(ColorStateList.valueOf(resources.getColor(R.color.material_white_100)))
}
thumbnailView.setImageDrawable(gradientDrawable)
val context = view.context
if (mContentDescription == null) {
val string = context.getString(R.string.default_theme_title)
mContentDescription = if (mIsDefault) {
string
} else {
mTitle
}
}
view.contentDescription = mContentDescription
}
override fun getLayoutResId(): Int {
return R.layout.color_option
}
override val source: String
get() = "preset"
}

@ -0,0 +1,238 @@
package com.google.android.customization.model.color
import android.app.WallpaperColors
import android.content.ContentResolver
import android.content.Context
import android.database.ContentObserver
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import android.provider.Settings
import android.text.TextUtils
import android.util.Log
import com.android.wallpaper.R
import com.android.customization.model.CustomizationManager
import com.android.customization.model.CustomizationManager.OptionsFetchedListener
import com.google.android.customization.picker.color.ColorSectionView
import org.json.JSONException
import org.json.JSONObject
import java.util.concurrent.Executors
class ColorCustomizationManager(
val mProvider: ColorOptionsProvider,
private val mContentResolver: ContentResolver
) : CustomizationManager<ColorOption> {
var mCurrentOverlays: Map<String, String>? = null
private var mCurrentSource: String? = null
var mHomeWallpaperColors: WallpaperColors? = null
var mLockWallpaperColors: WallpaperColors? = null
companion object {
var COLOR_OVERLAY_SETTINGS: Set<String>? = null
private var sColorCustomizationManager: ColorCustomizationManager? = null
val sExecutorService = Executors.newSingleThreadExecutor()!!
fun getInstance(
context: Context
): ColorCustomizationManager? {
if (sColorCustomizationManager == null) {
val applicationContext = context.applicationContext
sColorCustomizationManager = ColorCustomizationManager(
ColorProvider(
applicationContext, applicationContext.getString(
R.string.themes_stub_package
)
), applicationContext.contentResolver
)
}
return sColorCustomizationManager
}
init {
val hashSet = HashSet<String>()
COLOR_OVERLAY_SETTINGS = hashSet
hashSet.add("android.theme.customization.system_palette")
hashSet.add("android.theme.customization.accent_color")
hashSet.add("android.theme.customization.color_source")
}
}
init {
mContentResolver.registerContentObserver(Settings.Secure.CONTENT_URI, true, object :
ContentObserver(Handler(Looper.getMainLooper())) {
override fun onChange(selfChange: Boolean, uri: Uri?) {
super.onChange(selfChange, uri)
if (TextUtils.equals(
uri!!.lastPathSegment,
"theme_customization_overlay_packages"
)
) {
mCurrentOverlays = null
}
}
})
}
val currentColorSource: String?
get() {
if (mCurrentSource == null) {
parseSettings(storedOverlays)
}
return mCurrentSource
}
val storedOverlays: String?
get() = Settings.Secure.getString(mContentResolver, "theme_customization_overlay_packages")
fun parseSettings(str: String?) {
val hashMap = HashMap<String, String>()
if (str != null) {
try {
val jSONObject = JSONObject(str)
val names = jSONObject.names()
if (names != null) {
for (i in 0 until names.length()) {
val string = names.getString(i)
if ((COLOR_OVERLAY_SETTINGS as HashSet<String>?)!!.contains(string)) {
try {
hashMap[string] = jSONObject.getString(string)
} catch (e: JSONException) {
Log.e(
"ColorCustomizationManager",
"parseColorOverlays: " + e.localizedMessage,
e
)
}
}
}
}
} catch (e2: JSONException) {
Log.e("ColorCustomizationManager", e2.localizedMessage!!)
}
}
mCurrentSource = hashMap.remove("android.theme.customization.color_source")
mCurrentOverlays = hashMap
}
override fun isAvailable(): Boolean {
return true
}
fun setThemeBundle(colorSectionController: ColorSectionController, option: ColorOption) {
if (SystemClock.elapsedRealtime() - colorSectionController.mLastColorApplyingTime >= 500) {
colorSectionController.mLastColorApplyingTime = SystemClock.elapsedRealtime()
val callback: CustomizationManager.Callback = object : CustomizationManager.Callback {
override fun onError(th2: Throwable?) {
Log.w("ColorSectionController", "Apply theme with error: null")
}
override fun onSuccess() {
val colorSectionView: ColorSectionView =
colorSectionController.mColorSectionView!!
colorSectionView.announceForAccessibility(
colorSectionView.context.getString(
R.string.color_changed
)
)
val wallpaperColors = colorSectionController.mLockWallpaperColors
var i3 = 0
val z2 =
wallpaperColors == null || wallpaperColors == colorSectionController.mHomeWallpaperColors
if (TextUtils.equals(option.source, "preset")) {
i3 = 26
} else if (z2) {
i3 = 25
} else {
val source = option.source
if (source == "lock_wallpaper") {
i3 = 24
} else if (source == "home_wallpaper") {
i3 = 23
}
}
colorSectionController.mEventLogger.logColorApplied(i3, option.mIndex)
}
}
sExecutorService.submit {
applyBundle(option, callback)
}
return
}
}
private fun applyBundle(option: ColorOption, callback: CustomizationManager.Callback) {
var mStoredOverlays = storedOverlays
if (TextUtils.isEmpty(mStoredOverlays) || mStoredOverlays == null) {
mStoredOverlays = "{}"
}
var z4: Boolean
var jSONObject: JSONObject? = null
try {
jSONObject = JSONObject(mStoredOverlays)
try {
val jsonPackages: JSONObject = option.getJsonPackages(true)
val it: Iterator<*> =
(COLOR_OVERLAY_SETTINGS as HashSet?)!!.iterator()
while (it.hasNext()) {
jSONObject.remove(it.next() as String?)
}
val keys: Iterator<String> = jsonPackages.keys()
while (keys.hasNext()) {
val next = keys.next()
jSONObject.put(next, jsonPackages.get(next))
}
jSONObject.put(
"android.theme.customization.color_source",
option.source
)
jSONObject.put(
"android.theme.customization.color_index",
option.mIndex.toString()
)
if ("preset" != option.source) {
val wallpaperColors = mLockWallpaperColors
if (wallpaperColors != null && wallpaperColors != mHomeWallpaperColors) {
z4 = false
jSONObject.put(
"android.theme.customization.color_both",
if (!z4) "1" else "0"
)
}
z4 = true
jSONObject.put(
"android.theme.customization.color_both",
if (!z4) "1" else "0"
)
} else {
jSONObject.remove("android.theme.customization.color_both")
}
} catch (e2: JSONException) {
e2.printStackTrace()
Handler(Looper.getMainLooper()).post {
val success = Settings.Secure.putString(
mContentResolver,
"theme_customization_overlay_packages", jSONObject.toString()
)
if (success) callback.onSuccess()
else callback.onError(null)
}
return
}
} catch (e3: JSONException) {
e3.printStackTrace()
}
Handler(Looper.getMainLooper()).post {
val success = jSONObject != null && Settings.Secure.putString(
mContentResolver,
"theme_customization_overlay_packages", jSONObject.toString()
)
if (success) callback.onSuccess()
else callback.onError(null)
}
}
override fun apply(option: ColorOption, callback: CustomizationManager.Callback) {
applyBundle(option, callback)
}
override fun fetchOptions(callback: OptionsFetchedListener<ColorOption>, reload: Boolean) {}
}

@ -0,0 +1,97 @@
package com.google.android.customization.model.color
import android.text.TextUtils
import android.util.Log
import com.android.customization.model.CustomizationManager
import com.android.customization.model.CustomizationOption
import org.json.JSONException
import org.json.JSONObject
import java.util.*
import java.util.stream.Collectors
abstract class ColorOption(
val mTitle: String,
map: Map<String?, String?>,
val mIsDefault: Boolean,
val mIndex: Int
) : CustomizationOption<ColorOption> {
var mContentDescription: CharSequence? = null
private val mPackagesByCategory: Map<String?, String?>
fun getJsonPackages(z: Boolean): JSONObject {
val jSONObject: JSONObject = if (mIsDefault) {
JSONObject()
} else {
val jSONObject2 = JSONObject(mPackagesByCategory)
val keys = jSONObject2.keys()
val hashSet: HashSet<String> = HashSet<String>()
while (keys.hasNext()) {
val next = keys.next()
if (jSONObject2.isNull(next)) {
hashSet.add(next)
}
}
val it: Iterator<*> = hashSet.iterator()
while (it.hasNext()) {
jSONObject2.remove(it.next() as String?)
}
jSONObject2
}
if (z) {
try {
jSONObject.put(TIMESTAMP_FIELD, System.currentTimeMillis())
} catch (unused: JSONException) {
Log.e("ColorOption", "Couldn't add timestamp to serialized themebundle")
}
}
return jSONObject
}
abstract val source: String
override fun getTitle(): String {
return mTitle
}
override fun isActive(customizationManager: CustomizationManager<ColorOption>): Boolean {
val colorCustomizationManager = customizationManager as ColorCustomizationManager
if (mIsDefault) {
val storedOverlays = colorCustomizationManager.storedOverlays
if (!TextUtils.isEmpty(storedOverlays) && "{}" != storedOverlays) {
if (colorCustomizationManager.mCurrentOverlays == null) {
colorCustomizationManager.parseSettings(colorCustomizationManager.storedOverlays)
}
if (colorCustomizationManager.mCurrentOverlays!!.isNotEmpty() &&
(storedOverlays!!.contains("android.theme.customization.system_palette") ||
storedOverlays.contains("android.theme.customization.accent_color"))
) {
return false
}
}
return true
}
if (colorCustomizationManager.mCurrentOverlays == null) {
colorCustomizationManager.parseSettings(colorCustomizationManager.storedOverlays)
}
val map = colorCustomizationManager.mCurrentOverlays
val currentColorSource = colorCustomizationManager.currentColorSource
return (TextUtils.isEmpty(currentColorSource) || source == currentColorSource) && mPackagesByCategory == map
}
companion object {
const val TIMESTAMP_FIELD = "_applied_timestamp"
}
init {
mPackagesByCategory = Collections.unmodifiableMap(
map.entries.stream().filter { t -> t.value != null }
.collect(
Collectors.toMap(
{ t -> t.key },
{ t -> t.value }
)
) as Map<String?, String?>
)
}
}

@ -0,0 +1,3 @@
package com.google.android.customization.model.color
interface ColorOptionsProvider

@ -0,0 +1,241 @@
package com.google.android.customization.model.color
import android.app.WallpaperColors
import android.content.Context
import android.content.res.ColorStateList
import android.util.Log
import androidx.core.graphics.ColorUtils
import com.android.customization.model.ResourcesApkProvider
import com.android.wallpaper.compat.WallpaperManagerCompat
import com.android.wallpaper.module.InjectorProvider
import com.android.systemui.monet.ColorScheme
import com.google.android.customization.model.color.ColorUtils.toColorString
import kotlinx.coroutines.CoroutineScope
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
class ColorProvider(context: Context, stubPackageName: String) :
ResourcesApkProvider(context, stubPackageName), ColorOptionsProvider {
var colorBundles: List<ColorOption>? = null
var homeWallpaperColors: WallpaperColors? = null
var lockWallpaperColors: WallpaperColors? = null
val scope: CoroutineScope? = null
private fun buildBundle(
seed: Int,
index: Int,
mIsDefault: Boolean,
source: String?,
list: ArrayList<ColorOption>
) {
val hashMap: HashMap<String?, String?> = HashMap()
val colorScheme = ColorScheme(seed, false)
val colorSchemeDark = ColorScheme(seed, true)
val secondaryColorLight = intArrayOf(
ColorUtils.setAlphaComponent(colorScheme.accent1[2], 255), ColorUtils.setAlphaComponent(
colorScheme.accent1[2], 255
), ColorStateList.valueOf(
colorScheme.accent3[6]
).withLStar(85.0f).colors[0], ColorUtils.setAlphaComponent(
colorScheme.accent1[6], 255
)
)
val secondaryColorDark = intArrayOf(
ColorUtils.setAlphaComponent(colorSchemeDark.accent1[2], 255),
ColorUtils.setAlphaComponent(
colorSchemeDark.accent1[2], 255
),
ColorStateList.valueOf(
colorSchemeDark.accent3[6]
).withLStar(85.0f).colors[0],
ColorUtils.setAlphaComponent(
colorSchemeDark.accent1[6], 255
)
)
var source3 = ""
val source2 = if (mIsDefault) {
source3
} else {
toColorString(seed)
}
hashMap["android.theme.customization.system_palette"] = source2
if (!mIsDefault) {
source3 = toColorString(seed)
}
hashMap["android.theme.customization.accent_color"] = source3
list.add(
ColorSeedOption(
source,
hashMap,
mIsDefault,
source!!,
1 + index,
ColorSeedOption.PreviewInfo(secondaryColorLight, secondaryColorDark)
)
)
}
private fun loadPreset() {
val bundlesList = ArrayList<String>()
val bundleNames = mStubApkResources.getStringArray(
mStubApkResources.getIdentifier(
"color_bundles",
"array",
mStubPackageName
)
)
for (i in bundleNames.indices) {
if (i == 4) break
bundlesList.add(bundleNames[i])
}
val colorPresetBundles = ArrayList<ColorOption>()
var position = 1;
for (bundle in bundlesList) {
val hashMap: HashMap<String?, String?> = HashMap()
val bundleName = getItemStringFromStub("bundle_name_", bundle)
val bundleColorPrimary = getItemColorFromStub("color_primary_", bundle)
val bundleColorSecondary = getItemColorFromStub("color_secondary_", bundle)
hashMap["android.theme.customization.system_palette"] =
toColorString(bundleColorSecondary)
hashMap["android.theme.customization.accent_color"] = toColorString(bundleColorPrimary)
val accentColor = ColorScheme(bundleColorPrimary, false).accentColor
val accentColor2 = ColorScheme(bundleColorPrimary, true).accentColor
colorPresetBundles.add(
ColorBundle(
bundleName,
hashMap,
false,
index = position,
mPreviewInfo = ColorBundle.PreviewInfo(
accentColor,
accentColor2
)
)
)
position++
}
this.colorBundles = colorPresetBundles
}
fun buildColorSeeds(
wallpaperColors: WallpaperColors,
count: Int,
source: String?,
isDefault: Boolean,
list: ArrayList<ColorOption>
) {
val list2: List<Int>
val list3: List<Int>
val seedColors: List<Int> = ColorScheme.getSeedColors(wallpaperColors)
loadPreset()
buildBundle(
seedColors[0],
0,
isDefault,
source,
list
)
val size = seedColors.size - 1
list2 = if (size <= 0) {
ArrayList()
} else if (size != 1) {
val arrayList: ArrayList<Int> = ArrayList(size)
val listIterator = seedColors.listIterator(1)
while (listIterator.hasNext()) {
arrayList.add(listIterator.next())
}
arrayList
} else if (seedColors.isNotEmpty()) {
listOf(seedColors[seedColors.size - 1])
} else {
throw NoSuchElementException("List is empty.")
}
val i3 = count - 1
var index4 = 0
if (i3 >= 0) {
list3 = if (i3 == 0) {
ArrayList()
} else (if (i3 >= list2.size) {
list2
} else if (i3 == 1) {
listOf(list2[0])
} else {
val arrayList2: ArrayList<Int> = ArrayList(i3)
var i5 = 0
for (obj in list2) {
arrayList2.add(obj)
i5++
if (i5 == i3) {
break
}
}
arrayList2
})
for (seed in list3) {
index4++
buildBundle(seed, index4, false, source, list)
}
return
}
throw IllegalArgumentException("Requested element count $i3 is less than zero.")
}
companion object {
fun loadSeedColors(
colorProvider: ColorProvider,
wallpaperColors: WallpaperColors?,
wallpaperColors2: WallpaperColors?
) {
val arrayList = ArrayList<ColorOption>()
if (wallpaperColors != null) {
val count = if (wallpaperColors2 == null) 4 else 2
if (wallpaperColors2 != null) {
val wallpaperManagerCompat: WallpaperManagerCompat =
InjectorProvider.getInjector()
.getWallpaperManagerCompat(colorProvider.mContext)
var isDefault = true
if (wallpaperManagerCompat.getWallpaperId(WallpaperManagerCompat.FLAG_LOCK) <= wallpaperManagerCompat.getWallpaperId(
WallpaperManagerCompat.FLAG_SYSTEM
)
) {
isDefault = false
}
colorProvider.buildColorSeeds(
if (isDefault) wallpaperColors2 else wallpaperColors,
count,
if (isDefault) "lock_wallpaper" else "home_wallpaper",
true,
arrayList
)
colorProvider.buildColorSeeds(
if (isDefault) wallpaperColors else wallpaperColors2,
count,
if (isDefault) "home_wallpaper" else "lock_wallpaper",
false,
arrayList
)
} else {
colorProvider.buildColorSeeds(
wallpaperColors,
count,
"home_wallpaper",
true,
arrayList
)
}
val list = colorProvider.colorBundles
val arrayList2 = ArrayList<ColorOption>()
if (list != null) {
for (t in list) {
arrayList2.add(t)
}
}
arrayList.addAll(arrayList2)
colorProvider.colorBundles = arrayList
}
}
}
}

@ -0,0 +1,260 @@
package com.google.android.customization.model.color
import android.app.Activity
import android.app.WallpaperColors
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.android.customization.model.CustomizationManager
import com.android.customization.module.CustomizationInjector
import com.android.customization.module.ThemesUserEventLogger
import com.android.customization.widget.OptionSelectorController
import com.android.wallpaper.R
import com.android.wallpaper.model.CustomizationSectionController;
import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController;
import com.android.wallpaper.module.InjectorProvider
import com.android.wallpaper.widget.SeparatedTabLayout
import com.android.wallpaper.model.WallpaperColorsViewModel
import com.google.android.customization.picker.color.ColorSectionView
import kotlinx.coroutines.launch
class ColorSectionController(
activity: Activity?,
wallpaperColorsViewModel: WallpaperColorsViewModel,
lifecycleOwner: LifecycleOwner,
bundle: Bundle?,
private val navigationController: CustomizationSectionNavigationController
) : CustomizationSectionController<ColorSectionView?> {
val mColorManager: ColorCustomizationManager
var mColorSectionView: ColorSectionView? = null
private var mColorSectionAdapter = ColorSectionAdapter()
private lateinit var mColorViewPager: ViewPager2
val mEventLogger: ThemesUserEventLogger
var mHomeWallpaperColors: WallpaperColors? = null
private var mHomeWallpaperColorsReady = false
private val mLifecycleOwner: LifecycleOwner
var mLockWallpaperColors: WallpaperColors? = null
private var mLockWallpaperColorsReady = false
val mPresetColorOptions: MutableList<ColorOption?> = ArrayList()
var mSelectedColor: ColorOption? = null
private var mTabLayout: SeparatedTabLayout? = null
private var mTabPositionToRestore: Int? = null
val mWallpaperColorOptions: MutableList<ColorOption?> = ArrayList()
private val mWallpaperColorsViewModel: WallpaperColorsViewModel
var mLastColorApplyingTime: Long = 0
inner class ColorSectionAdapter :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val mItemCounts = 2
inner class ColorOptionsViewHolder(view: View) :
RecyclerView.ViewHolder(
view
)
override fun getItemCount(): Int {
return mItemCounts
}
override fun getItemViewType(i: Int): Int {
return R.layout.color_options_view
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
val view = viewHolder.itemView as? RecyclerView ?: return
val colorSectionController = this@ColorSectionController
val colorOptions =
if (position == 0) colorSectionController.mWallpaperColorOptions
else colorSectionController.mPresetColorOptions
val optionSelectorController = OptionSelectorController(
view,
colorOptions,
true,
OptionSelectorController.CheckmarkStyle.CENTER
)
optionSelectorController.initOptions(colorSectionController.mColorManager)
colorSectionController.setUpColorOptionsController(optionSelectorController)
}
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): RecyclerView.ViewHolder {
return ColorOptionsViewHolder(
LayoutInflater.from(viewGroup.context).inflate(i, viewGroup, false)
)
}
}
override fun createView(context: Context): ColorSectionView {
mColorSectionView = LayoutInflater.from(context)
.inflate(R.layout.color_section_view, null as ViewGroup?) as ColorSectionView
mColorViewPager = mColorSectionView!!.requireViewById<ViewPager2>(R.id.color_view_pager)
mTabLayout = mColorSectionView!!.requireViewById(R.id.separated_tabs)
if (mColorViewPager.adapter == null) {
mColorViewPager.setAdapter(mColorSectionAdapter)
}
mTabLayout!!.setViewPager(mColorViewPager)
mWallpaperColorsViewModel.homeWallpaperColors.observe(mLifecycleOwner) {
mHomeWallpaperColors = it
mHomeWallpaperColorsReady = true
maybeLoadColors()
}
mWallpaperColorsViewModel.lockWallpaperColors.observe(mLifecycleOwner) {
mLockWallpaperColors = it
mLockWallpaperColorsReady = true
maybeLoadColors()
}
mLifecycleOwner.lifecycleScope.launchWhenResumed {
mTabPositionToRestore?.let { mColorViewPager.setCurrentItem(it, false) }
}
return mColorSectionView!!
}
override fun isAvailable(context: Context?): Boolean {
return context != null && ColorUtils.isMonetEnabled(context)
}
private fun maybeLoadColors() {
if (mHomeWallpaperColorsReady && mLockWallpaperColorsReady) {
val wallpaperColors = mHomeWallpaperColors
var wallpaperColors2 = mLockWallpaperColors
mColorManager.mHomeWallpaperColors = wallpaperColors
mColorManager.mLockWallpaperColors = wallpaperColors2
val optionsFetcher =
object : CustomizationManager.OptionsFetchedListener<ColorOption?> {
override fun onError(th: Throwable?) {
if (th != null) {
Log.e("ColorSectionController", "Error loading theme bundles", th)
}
}
override fun onOptionsLoaded(list: List<ColorOption?>) {
if (list.isNotEmpty()) {
var colorOption: ColorOption?
val colorOption2: ColorOption
mWallpaperColorOptions.clear()
mPresetColorOptions.clear()
for (colorOption3 in list) {
if (colorOption3 is ColorSeedOption) {
mWallpaperColorOptions.add(colorOption3)
} else if (colorOption3 is ColorBundle) {
mPresetColorOptions.add(colorOption3)
}
}
val allColors = ArrayList<ColorOption?>()
allColors.addAll(mWallpaperColorOptions)
allColors.addAll(mPresetColorOptions)
val iterator = allColors.iterator()
while (true) {
if (!iterator.hasNext()) {
colorOption = null
break
}
colorOption = iterator.next()
if (colorOption!!.isActive(mColorManager)) {
break
}
}
if (colorOption == null) {
colorOption2 =
(if (mWallpaperColorOptions.isEmpty()) mPresetColorOptions[0]!! else mWallpaperColorOptions[0]!!)
colorOption = colorOption2
}
mSelectedColor = colorOption
mColorViewPager.post {
mColorViewPager.adapter?.notifyItemChanged(0)
if (mTabLayout != null && mTabLayout!!.tabCount == 0) {
val newTab = mTabLayout!!.newTab()
newTab.setText(R.string.wallpaper_color_tab)
mTabLayout!!.addTab(newTab, 0, mTabLayout!!.tabCount == 0)
val newTab2 = mTabLayout!!.newTab()
newTab2.setText(R.string.preset_color_tab)
mTabLayout!!.addTab(newTab2, 1, mTabLayout!!.tabCount == 0)
}
if (mWallpaperColorOptions.isEmpty()) {
mTabLayout!!.getTabAt(0)!!.view.isEnabled = false
mColorViewPager.setCurrentItem(1, false)
}
mColorViewPager.setCurrentItem(
if ("preset" == mColorManager.currentColorSource) 1 else 0,
false
)
}
}
}
}
if (wallpaperColors2 != null && wallpaperColors2 == wallpaperColors) {
wallpaperColors2 = null
}
val wallpaperColors3 = mColorManager.mHomeWallpaperColors
val colorProvider = mColorManager.mProvider as ColorProvider
val wallpapersColorsChanged = (colorProvider.homeWallpaperColors == wallpaperColors3
) || (colorProvider.lockWallpaperColors == wallpaperColors2)
if (wallpapersColorsChanged) {
colorProvider.homeWallpaperColors = wallpaperColors3
colorProvider.lockWallpaperColors = wallpaperColors2
}
val list = colorProvider.colorBundles
if (list == null || wallpapersColorsChanged) {
mLifecycleOwner.lifecycleScope.launch {
if (wallpapersColorsChanged) {
ColorProvider.loadSeedColors(
colorProvider,
wallpaperColors3,
wallpaperColors2
)
}
optionsFetcher.onOptionsLoaded(colorProvider.colorBundles!!)
}
} else {
optionsFetcher.onOptionsLoaded(list)
}
}
}
fun setUpColorOptionsController(optionSelectorController: OptionSelectorController<ColorOption>) {
if (mSelectedColor != null && optionSelectorController.containsOption(mSelectedColor)) {
optionSelectorController.setSelectedOption(mSelectedColor)
}
optionSelectorController.addListener {
if (mSelectedColor != it) {
mSelectedColor = (it as ColorOption)
Handler(Looper.getMainLooper()).postDelayed({
mColorManager.setThemeBundle(this, it)
}, 100L)
return@addListener
}
}
}
override fun onSaveInstanceState(bundle: Bundle) {
val viewPager2 = mColorViewPager
bundle.putInt("COLOR_TAB_POSITION", viewPager2.currentItem)
}
init {
mEventLogger =
(InjectorProvider.getInjector() as CustomizationInjector).getUserEventLogger(activity) as ThemesUserEventLogger
mColorManager =
ColorCustomizationManager.getInstance(activity!!)!!
mWallpaperColorsViewModel = wallpaperColorsViewModel
mLifecycleOwner = lifecycleOwner
if (bundle != null && bundle.containsKey("COLOR_TAB_POSITION")) {
mTabPositionToRestore = bundle.getInt("COLOR_TAB_POSITION")
}
}
}

@ -0,0 +1,56 @@
package com.google.android.customization.model.color
import android.content.res.Configuration
import android.graphics.PorterDuff
import android.view.View
import android.widget.ImageView
import com.android.wallpaper.R
class ColorSeedOption(
title: String?,
map: Map<String?, String?>?,
isDefault: Boolean,
override val source: String,
index: Int,
private val mPreviewInfo: PreviewInfo
) : ColorOption(
title!!, map!!, isDefault, index
) {
private val mPreviewColorIds = intArrayOf(
R.id.color_preview_0,
R.id.color_preview_1,
R.id.color_preview_2,
R.id.color_preview_3
)
override fun bindThumbnailTile(view: View) {
val padding: Int
val resources = view.context.resources
var iterator = 0
val mPreviewColorTint =
if ((resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES) mPreviewInfo.darkColors else mPreviewInfo.lightColors
padding = if (view.isActivated) {
resources.getDimensionPixelSize(R.dimen.color_seed_option_tile_padding_selected)
} else {
resources.getDimensionPixelSize(R.dimen.color_seed_option_tile_padding)
}
while (true) {
if (iterator < mPreviewColorIds.size) {
val imageView = view.findViewById<View>(mPreviewColorIds[iterator]) as ImageView
imageView.drawable.setColorFilter(mPreviewColorTint[iterator], PorterDuff.Mode.SRC)
imageView.setPadding(padding, padding, padding, padding)
iterator++
} else {
view.contentDescription =
view.context.getString(R.string.wallpaper_color_title)
return
}
}
}
override fun getLayoutResId(): Int {
return R.layout.color_seed_option
}
class PreviewInfo(var lightColors: IntArray, var darkColors: IntArray)
}

@ -0,0 +1,47 @@
package com.google.android.customization.model.color;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.SystemProperties;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public final class ColorUtils {
public static int sFlagId;
@Nullable
public static Resources sSysuiRes;
public static boolean isMonetEnabled(@NonNull Context context) {
boolean monet = SystemProperties.getBoolean("persist.systemui.flag_monet", false);
if (monet) {
return true;
}
if (sSysuiRes == null) {
try {
PackageManager packageManager = context.getPackageManager();
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(
"com.android.systemui", 0);
if (applicationInfo != null) {
sSysuiRes = packageManager.getResourcesForApplication(applicationInfo);
}
} catch (PackageManager.NameNotFoundException e) {
Log.w("ColorUtils", "Couldn't read color flag, skipping section", e);
}
}
if (sFlagId == 0 && sSysuiRes != null) {
sFlagId = sSysuiRes.getIdentifier("flag_monet", "bool", "com.android.systemui");
}
if (sFlagId <= 0) {
return false;
}
return sSysuiRes.getBoolean(sFlagId);
}
public static String toColorString(int color) {
return String.format("#%06X", color & 0x00ffffff);
}
}

@ -20,6 +20,8 @@ import com.android.wallpaper.model.WallpaperSectionController;
import com.android.wallpaper.model.WorkspaceViewModel; import com.android.wallpaper.model.WorkspaceViewModel;
import com.android.wallpaper.module.CustomizationSections; import com.android.wallpaper.module.CustomizationSections;
import com.google.android.customization.model.color.ColorSectionController;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -41,6 +43,11 @@ public final class GoogleCustomizationSections implements CustomizationSections
workspaceViewModel, sectionNavigationController, wallpaperPreviewNavigator, workspaceViewModel, sectionNavigationController, wallpaperPreviewNavigator,
savedInstanceState)); savedInstanceState));
// Color section
sectionControllers.add(
new ColorSectionController(activity, wallpaperColorsViewModel, lifecycleOwner,
savedInstanceState, sectionNavigationController));
// Dark/Light theme section. // Dark/Light theme section.
sectionControllers.add(new DarkModeSectionController(activity, sectionControllers.add(new DarkModeSectionController(activity,
lifecycleOwner.getLifecycle())); lifecycleOwner.getLifecycle()));

@ -0,0 +1,8 @@
package com.google.android.customization.picker.color
import android.content.Context
import android.util.AttributeSet
import com.android.wallpaper.picker.SectionView
class ColorSectionView(context: Context?, attributeSet: AttributeSet?) :
SectionView(context, attributeSet)
Loading…
Cancel
Save