diff --git a/lawnchair/res/values-v31/config.xml b/lawnchair/res/values-v31/config.xml
new file mode 100644
index 0000000000..95efc64315
--- /dev/null
+++ b/lawnchair/res/values-v31/config.xml
@@ -0,0 +1,4 @@
+
+
+ app.lawnchair.AccentColorExtractor
+
diff --git a/lawnchair/src/app/lawnchair/AccentColorExtractor.java b/lawnchair/src/app/lawnchair/AccentColorExtractor.java
new file mode 100644
index 0000000000..6c19f36287
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/AccentColorExtractor.java
@@ -0,0 +1,191 @@
+/*
+ * 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 app.lawnchair;
+
+import android.app.WallpaperColors;
+import android.content.Context;
+import android.graphics.RectF;
+import android.os.Build;
+import android.util.SparseIntArray;
+import android.widget.RemoteViews;
+
+import androidx.annotation.Keep;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import com.android.launcher3.widget.LocalColorExtractor;
+
+import java.util.List;
+import java.util.Map;
+
+import app.lawnchair.theme.ThemeProvider;
+import dev.kdrag0n.colorkt.Color;
+import dev.kdrag0n.colorkt.rgb.Srgb;
+import dev.kdrag0n.monet.theme.ColorScheme;
+
+@RequiresApi(api = Build.VERSION_CODES.S)
+public class AccentColorExtractor extends LocalColorExtractor implements ThemeProvider.ColorSchemeChangeListener {
+
+ private final ThemeProvider mThemeProvider;
+ private final RectF mTmpRect = new RectF();
+ private Listener mListener;
+
+ @Keep
+ public AccentColorExtractor(Context context) {
+ mThemeProvider = ThemeProvider.INSTANCE.get(context);
+ }
+
+ @Override
+ public void setListener(@Nullable Listener listener) {
+ mListener = listener;
+ notifyListener(mTmpRect, null);
+ }
+
+ @Override
+ public void addLocation(List locations) {
+ mThemeProvider.addListener(this);
+ }
+
+ @Override
+ public void removeLocations() {
+ mThemeProvider.removeListener(this);
+ }
+
+ @Nullable
+ protected SparseIntArray generateColorsOverride(ColorScheme colorScheme) {
+ SparseIntArray colorRes = new SparseIntArray(5 * 13);
+
+ addColorsToArray(colorScheme.getAccent1(), ACCENT1_RES, colorRes);
+ addColorsToArray(colorScheme.getAccent2(), ACCENT2_RES, colorRes);
+ addColorsToArray(colorScheme.getAccent3(), ACCENT3_RES, colorRes);
+ addColorsToArray(colorScheme.getNeutral1(), NEUTRAL1_RES, colorRes);
+ addColorsToArray(colorScheme.getNeutral2(), NEUTRAL2_RES, colorRes);
+
+ return colorRes;
+ }
+
+ @Override
+ public void applyColorsOverride(Context base, WallpaperColors colors) {
+ RemoteViews.ColorResources res =
+ RemoteViews.ColorResources.create(base, generateColorsOverride(colors));
+ if (res != null) {
+ res.apply(base);
+ }
+ }
+
+ @Override
+ public void onColorSchemeChanged() {
+ notifyListener(mTmpRect, null);
+ }
+
+ protected void notifyListener(RectF area, WallpaperColors colors) {
+ if (mListener != null) {
+ mListener.onColorsChanged(area, generateColorsOverride(mThemeProvider.getColorScheme()));
+ }
+ }
+
+ // Shade number -> color resource ID maps
+ private static final SparseIntArray ACCENT1_RES = new SparseIntArray(13);
+ private static final SparseIntArray ACCENT2_RES = new SparseIntArray(13);
+ private static final SparseIntArray ACCENT3_RES = new SparseIntArray(13);
+ private static final SparseIntArray NEUTRAL1_RES = new SparseIntArray(13);
+ private static final SparseIntArray NEUTRAL2_RES = new SparseIntArray(13);
+
+ static {
+ ACCENT1_RES.put( 0, android.R.color.system_accent1_0);
+ ACCENT1_RES.put( 10, android.R.color.system_accent1_10);
+ ACCENT1_RES.put( 50, android.R.color.system_accent1_50);
+ ACCENT1_RES.put( 100, android.R.color.system_accent1_100);
+ ACCENT1_RES.put( 200, android.R.color.system_accent1_200);
+ ACCENT1_RES.put( 300, android.R.color.system_accent1_300);
+ ACCENT1_RES.put( 400, android.R.color.system_accent1_400);
+ ACCENT1_RES.put( 500, android.R.color.system_accent1_500);
+ ACCENT1_RES.put( 600, android.R.color.system_accent1_600);
+ ACCENT1_RES.put( 700, android.R.color.system_accent1_700);
+ ACCENT1_RES.put( 800, android.R.color.system_accent1_800);
+ ACCENT1_RES.put( 900, android.R.color.system_accent1_900);
+ ACCENT1_RES.put(1000, android.R.color.system_accent1_1000);
+
+ ACCENT2_RES.put( 0, android.R.color.system_accent2_0);
+ ACCENT2_RES.put( 10, android.R.color.system_accent2_10);
+ ACCENT2_RES.put( 50, android.R.color.system_accent2_50);
+ ACCENT2_RES.put( 100, android.R.color.system_accent2_100);
+ ACCENT2_RES.put( 200, android.R.color.system_accent2_200);
+ ACCENT2_RES.put( 300, android.R.color.system_accent2_300);
+ ACCENT2_RES.put( 400, android.R.color.system_accent2_400);
+ ACCENT2_RES.put( 500, android.R.color.system_accent2_500);
+ ACCENT2_RES.put( 600, android.R.color.system_accent2_600);
+ ACCENT2_RES.put( 700, android.R.color.system_accent2_700);
+ ACCENT2_RES.put( 800, android.R.color.system_accent2_800);
+ ACCENT2_RES.put( 900, android.R.color.system_accent2_900);
+ ACCENT2_RES.put(1000, android.R.color.system_accent2_1000);
+
+ ACCENT3_RES.put( 0, android.R.color.system_accent3_0);
+ ACCENT3_RES.put( 10, android.R.color.system_accent3_10);
+ ACCENT3_RES.put( 50, android.R.color.system_accent3_50);
+ ACCENT3_RES.put( 100, android.R.color.system_accent3_100);
+ ACCENT3_RES.put( 200, android.R.color.system_accent3_200);
+ ACCENT3_RES.put( 300, android.R.color.system_accent3_300);
+ ACCENT3_RES.put( 400, android.R.color.system_accent3_400);
+ ACCENT3_RES.put( 500, android.R.color.system_accent3_500);
+ ACCENT3_RES.put( 600, android.R.color.system_accent3_600);
+ ACCENT3_RES.put( 700, android.R.color.system_accent3_700);
+ ACCENT3_RES.put( 800, android.R.color.system_accent3_800);
+ ACCENT3_RES.put( 900, android.R.color.system_accent3_900);
+ ACCENT3_RES.put(1000, android.R.color.system_accent3_1000);
+
+ NEUTRAL1_RES.put( 0, android.R.color.system_neutral1_0);
+ NEUTRAL1_RES.put( 10, android.R.color.system_neutral1_10);
+ NEUTRAL1_RES.put( 50, android.R.color.system_neutral1_50);
+ NEUTRAL1_RES.put( 100, android.R.color.system_neutral1_100);
+ NEUTRAL1_RES.put( 200, android.R.color.system_neutral1_200);
+ NEUTRAL1_RES.put( 300, android.R.color.system_neutral1_300);
+ NEUTRAL1_RES.put( 400, android.R.color.system_neutral1_400);
+ NEUTRAL1_RES.put( 500, android.R.color.system_neutral1_500);
+ NEUTRAL1_RES.put( 600, android.R.color.system_neutral1_600);
+ NEUTRAL1_RES.put( 700, android.R.color.system_neutral1_700);
+ NEUTRAL1_RES.put( 800, android.R.color.system_neutral1_800);
+ NEUTRAL1_RES.put( 900, android.R.color.system_neutral1_900);
+ NEUTRAL1_RES.put(1000, android.R.color.system_neutral1_1000);
+
+ NEUTRAL2_RES.put( 0, android.R.color.system_neutral2_0);
+ NEUTRAL2_RES.put( 10, android.R.color.system_neutral2_10);
+ NEUTRAL2_RES.put( 50, android.R.color.system_neutral2_50);
+ NEUTRAL2_RES.put( 100, android.R.color.system_neutral2_100);
+ NEUTRAL2_RES.put( 200, android.R.color.system_neutral2_200);
+ NEUTRAL2_RES.put( 300, android.R.color.system_neutral2_300);
+ NEUTRAL2_RES.put( 400, android.R.color.system_neutral2_400);
+ NEUTRAL2_RES.put( 500, android.R.color.system_neutral2_500);
+ NEUTRAL2_RES.put( 600, android.R.color.system_neutral2_600);
+ NEUTRAL2_RES.put( 700, android.R.color.system_neutral2_700);
+ NEUTRAL2_RES.put( 800, android.R.color.system_neutral2_800);
+ NEUTRAL2_RES.put( 900, android.R.color.system_neutral2_900);
+ NEUTRAL2_RES.put(1000, android.R.color.system_neutral2_1000);
+ }
+
+ private static void addColorsToArray(Map swatch,
+ SparseIntArray resMap, SparseIntArray array) {
+ for (Map.Entry entry : swatch.entrySet()) {
+ int shade = entry.getKey();
+ int resId = resMap.get(shade, -1);
+ if (resId != -1) {
+ Srgb color = (Srgb) entry.getValue();
+ array.put(resId, 0xff000000 | color.toRgb8());
+ }
+ }
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/theme/ThemeProvider.kt b/lawnchair/src/app/lawnchair/theme/ThemeProvider.kt
index e273eaaa45..307a674255 100644
--- a/lawnchair/src/app/lawnchair/theme/ThemeProvider.kt
+++ b/lawnchair/src/app/lawnchair/theme/ThemeProvider.kt
@@ -9,6 +9,7 @@ import android.os.Looper
import android.os.PatternMatcher
import android.util.SparseArray
import androidx.core.graphics.ColorUtils
+import app.lawnchair.preferences.PreferenceChangeListener
import app.lawnchair.preferences.PreferenceManager
import app.lawnchair.theme.color.AndroidColor
import app.lawnchair.theme.color.ColorOption
@@ -32,17 +33,31 @@ import dev.kdrag0n.monet.theme.MaterialYouTargets
class ThemeProvider(private val context: Context) {
private val prefs = PreferenceManager.getInstance(context)
+ private val wallpaperManager = WallpaperManagerCompat.INSTANCE.get(context)
private val accentColor by prefs.accentColor
private val enableColorfulTheme by prefs.enableColorfulTheme
private val targets = MaterialYouTargets(1.0, false, viewingCondition)
private val colorSchemeMap = SparseArray()
+ private val listeners = mutableListOf()
init {
if (Utilities.ATLEAST_S) {
colorSchemeMap.append(0, SystemColorScheme(context))
registerOverlayChangedListener()
}
+ wallpaperManager.addOnChangeListener(object : WallpaperManagerCompat.OnColorsChangedListener {
+ override fun onColorsChanged() {
+ if (accentColor is ColorOption.WallpaperPrimary) {
+ notifyColorSchemeChanged()
+ }
+ }
+ })
+ prefs.accentColor.addListener(object : PreferenceChangeListener {
+ override fun onPreferenceChange() {
+ notifyColorSchemeChanged()
+ }
+ })
}
private fun registerOverlayChangedListener() {
@@ -53,6 +68,9 @@ class ThemeProvider(private val context: Context) {
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
colorSchemeMap.append(0, SystemColorScheme(context))
+ if (accentColor is ColorOption.SystemAccent) {
+ notifyColorSchemeChanged()
+ }
}
},
packageFilter,
@@ -64,7 +82,6 @@ class ThemeProvider(private val context: Context) {
val colorScheme get() = when (val accentColor = this.accentColor) {
is ColorOption.SystemAccent -> systemColorScheme
is ColorOption.WallpaperPrimary -> {
- val wallpaperManager = WallpaperManagerCompat.INSTANCE.get(context)
val wallpaperPrimary = wallpaperManager.wallpaperColors?.primaryColor
getColorScheme(wallpaperPrimary ?: ColorOption.LawnchairBlue.color)
}
@@ -86,6 +103,19 @@ class ThemeProvider(private val context: Context) {
return colorScheme
}
+ fun addListener(listener: ColorSchemeChangeListener) {
+ listeners.add(listener)
+ }
+
+ fun removeListener(listener: ColorSchemeChangeListener) {
+ listeners.remove(listener)
+ }
+
+ private fun notifyColorSchemeChanged() {
+ ArrayList(listeners)
+ .forEach(ColorSchemeChangeListener::onColorSchemeChanged)
+ }
+
companion object {
@JvmField
val INSTANCE = MainThreadInitializedObject(::ThemeProvider)
@@ -97,6 +127,10 @@ class ThemeProvider(private val context: Context) {
Illuminants.D65.toAbs(CieXyzAbs.DEFAULT_SDR_WHITE_LUMINANCE)
)
}
+
+ interface ColorSchemeChangeListener {
+ fun onColorSchemeChanged()
+ }
}
fun Color.toAndroidColor(): Int {