Add colors to tiles
Protoype for modifying color of tiles. Provides 2 modes: qs_color_enabled: allows changing background of tiles qs_color_icon: allows changing color of icon (only use with qs_color_enabled) Enable using: adb shell settings put system <name> <value> where name is one of (qs_color_enabled|qs_color_icon) and value is (0|1) With qs_color_enabled, color of individual tiles can be changed using adb shell settings put system qs_color_enabled/<spec> <color> where spec is as defined in QSFactoryImpl and color is one of (red|green|blue|yellow|accent). Test: manual Change-Id: I66096cfa5cb8570074b1547ca3e41bd0888973fa
This commit is contained in:
@@ -14,6 +14,9 @@
|
||||
|
||||
package com.android.systemui.plugins.qs;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.metrics.LogMaker;
|
||||
@@ -25,6 +28,7 @@ import com.android.systemui.plugins.qs.QSTile.Callback;
|
||||
import com.android.systemui.plugins.qs.QSTile.Icon;
|
||||
import com.android.systemui.plugins.qs.QSTile.State;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -72,6 +76,17 @@ public interface QSTile {
|
||||
return logMaker;
|
||||
}
|
||||
|
||||
@Retention(SOURCE)
|
||||
@IntDef({COLOR_TILE_ACCENT, COLOR_TILE_RED, COLOR_TILE_BLUE, COLOR_TILE_YELLOW,
|
||||
COLOR_TILE_GREEN})
|
||||
@interface ColorTile {}
|
||||
int COLOR_TILE_ACCENT = 0;
|
||||
int COLOR_TILE_RED = 1;
|
||||
int COLOR_TILE_BLUE = 2;
|
||||
int COLOR_TILE_YELLOW = 3;
|
||||
int COLOR_TILE_GREEN = 4;
|
||||
default void setColor(@ColorTile int color) {}
|
||||
|
||||
@ProvidesInterface(version = Callback.VERSION)
|
||||
public interface Callback {
|
||||
public static final int VERSION = 1;
|
||||
@@ -118,6 +133,7 @@ public interface QSTile {
|
||||
public SlashState slash;
|
||||
public boolean handlesLongClick = true;
|
||||
public boolean showRippleEffect = true;
|
||||
public int colorActive = -1;
|
||||
|
||||
public boolean copyTo(State other) {
|
||||
if (other == null) throw new IllegalArgumentException();
|
||||
@@ -137,7 +153,8 @@ public interface QSTile {
|
||||
|| !Objects.equals(other.dualTarget, dualTarget)
|
||||
|| !Objects.equals(other.slash, slash)
|
||||
|| !Objects.equals(other.handlesLongClick, handlesLongClick)
|
||||
|| !Objects.equals(other.showRippleEffect, showRippleEffect);
|
||||
|| !Objects.equals(other.showRippleEffect, showRippleEffect)
|
||||
|| !Objects.equals(other.colorActive, colorActive);
|
||||
other.icon = icon;
|
||||
other.iconSupplier = iconSupplier;
|
||||
other.label = label;
|
||||
@@ -152,6 +169,7 @@ public interface QSTile {
|
||||
other.slash = slash != null ? slash.copy() : null;
|
||||
other.handlesLongClick = handlesLongClick;
|
||||
other.showRippleEffect = showRippleEffect;
|
||||
other.colorActive = colorActive;
|
||||
return changed;
|
||||
}
|
||||
|
||||
|
||||
@@ -83,4 +83,9 @@
|
||||
<color name="biometric_dialog_accent">#ff80cbc4</color> <!-- light teal -->
|
||||
<color name="biometric_dialog_error">#fff28b82</color> <!-- red 300 -->
|
||||
|
||||
<color name="GM2_green_500">#FF41Af6A</color>
|
||||
<color name="GM2_blue_500">#5195EA</color>
|
||||
<color name="GM2_red_500">#E25142</color>
|
||||
<color name="GM2_yellow_500">#F5C518</color>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -204,4 +204,6 @@
|
||||
|
||||
<color name="GM2_yellow_500">#FFFBBC04</color>
|
||||
<color name="GM2_green_500">#FF34A853</color>
|
||||
<color name="GM2_blue_500">#FF4285F4</color>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.qs
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.database.ContentObserver
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import com.android.systemui.plugins.qs.QSTile
|
||||
|
||||
private const val TAG = "QSColorController"
|
||||
private const val QS_COLOR_ICON = "qs_color_icon"
|
||||
private const val QS_COLOR_ENABLED = "qs_color_enabled"
|
||||
private const val QS_COLOR_OVERRIDDEN_TILES = "qs_color_overridden_tiles"
|
||||
private val qsColorIconUri = Settings.System.getUriFor(QS_COLOR_ICON)
|
||||
private val qsColorEnabledUri = Settings.System.getUriFor(QS_COLOR_ENABLED)
|
||||
class QSColorController private constructor() {
|
||||
|
||||
private var overrideColor = false
|
||||
private var colorIcon = false
|
||||
private lateinit var colorCache: SettingBackedMap
|
||||
|
||||
companion object {
|
||||
val instance = QSColorController()
|
||||
internal fun overrideColor() = instance.overrideColor
|
||||
internal fun colorIcon() = instance.colorIcon
|
||||
|
||||
@QSTile.ColorTile
|
||||
private fun getColorFromSetting(setting: String): Int {
|
||||
return when (setting.toLowerCase()) {
|
||||
"red" -> QSTile.COLOR_TILE_RED
|
||||
"blue" -> QSTile.COLOR_TILE_BLUE
|
||||
"green" -> QSTile.COLOR_TILE_GREEN
|
||||
"yellow" -> QSTile.COLOR_TILE_YELLOW
|
||||
else -> QSTile.COLOR_TILE_ACCENT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBooleanSetting(key: String, default: Boolean = false): Boolean =
|
||||
try {
|
||||
Settings.System.getInt(contentResolver, key) != 0
|
||||
} catch (_: Settings.SettingNotFoundException) {
|
||||
default
|
||||
}
|
||||
|
||||
private lateinit var tileHost: QSHost
|
||||
private lateinit var contentResolver: ContentResolver
|
||||
|
||||
fun initQSTileHost(host: QSHost) {
|
||||
tileHost = host
|
||||
contentResolver = tileHost.context.contentResolver
|
||||
colorCache = SettingBackedMap(contentResolver, mutableMapOf())
|
||||
colorIcon = getBooleanSetting(QS_COLOR_ICON)
|
||||
overrideColor = getBooleanSetting(QS_COLOR_ENABLED)
|
||||
readExistingSettings()
|
||||
contentResolver.registerContentObserver(qsColorEnabledUri, true, settingsListener)
|
||||
contentResolver.registerContentObserver(qsColorIconUri, false, settingsListener)
|
||||
}
|
||||
|
||||
private fun readExistingSettings() {
|
||||
Settings.System.getString(contentResolver, QS_COLOR_OVERRIDDEN_TILES)?.split(",")
|
||||
?.mapNotNull { spec ->
|
||||
Settings.System.getString(contentResolver, "$QS_COLOR_ENABLED/$spec")?.let {
|
||||
spec to it
|
||||
}
|
||||
}?.forEach {
|
||||
modifyTileColor(it.first, getColorFromSetting(it.second))
|
||||
}
|
||||
}
|
||||
|
||||
private val settingsListener = object : ContentObserver(Handler(Looper.getMainLooper())) {
|
||||
override fun onChange(selfChange: Boolean, uri: Uri) {
|
||||
super.onChange(selfChange, uri)
|
||||
when (uri) {
|
||||
qsColorIconUri -> colorIcon = getBooleanSetting(QS_COLOR_ICON)
|
||||
qsColorEnabledUri -> overrideColor = getBooleanSetting(QS_COLOR_ENABLED)
|
||||
else -> {
|
||||
uri.path?.drop("/system/".length)?.let {
|
||||
val color = getColorFromSetting(
|
||||
Settings.System.getString(contentResolver, it) ?: "accent")
|
||||
val tileSpec = uri.lastPathSegment ?: ""
|
||||
modifyTileColor(tileSpec, color)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun modifyTileColor(spec: String, @QSTile.ColorTile color: Int) {
|
||||
Log.w(TAG, "Setting color of tile $spec to $color")
|
||||
colorCache.put(spec, color)
|
||||
tileHost.tiles.firstOrNull { it.tileSpec == spec }?.setColor(color)
|
||||
}
|
||||
|
||||
fun applyColorToTile(tile: QSTile) {
|
||||
colorCache.get(tile.tileSpec)?.let {
|
||||
modifyTileColor(tile.tileSpec, it)
|
||||
}
|
||||
}
|
||||
|
||||
fun applyColorToAllTiles() = tileHost.tiles.forEach(::applyColorToTile)
|
||||
|
||||
fun destroy() {
|
||||
contentResolver.unregisterContentObserver(settingsListener)
|
||||
}
|
||||
|
||||
class SettingBackedMap(
|
||||
private val contentResolver: ContentResolver,
|
||||
private val map: MutableMap<String, Int>
|
||||
) : MutableMap<String, @QSTile.ColorTile Int> by map {
|
||||
override fun put(key: String, @QSTile.ColorTile value: Int): Int? {
|
||||
return map.put(key, value).also {
|
||||
Settings.System.putString(contentResolver, QS_COLOR_OVERRIDDEN_TILES,
|
||||
map.filterValues { it != QSTile.COLOR_TILE_ACCENT }
|
||||
.keys
|
||||
.joinToString(","))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fun overrideColor() = QSColorController.overrideColor()
|
||||
fun colorIcon() = QSColorController.colorIcon()
|
||||
@@ -88,6 +88,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
|
||||
private int mCurrentUser;
|
||||
private StatusBar mStatusBar;
|
||||
|
||||
private QSColorController mQSColorController = QSColorController.Companion.getInstance();
|
||||
|
||||
@Inject
|
||||
public QSTileHost(Context context,
|
||||
StatusBarIconController iconController,
|
||||
@@ -119,6 +121,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
|
||||
// AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
|
||||
mAutoTiles = autoTiles.get();
|
||||
});
|
||||
|
||||
mQSColorController.initQSTileHost(this);
|
||||
}
|
||||
|
||||
public StatusBarIconController getIconController() {
|
||||
@@ -132,6 +136,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
|
||||
mServices.destroy();
|
||||
mPluginManager.removePluginListener(this);
|
||||
mDumpController.unregisterDumpable(this);
|
||||
|
||||
mQSColorController.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -275,6 +281,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
|
||||
mCallbacks.get(i).onTilesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
mQSColorController.applyColorToAllTiles();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
|
||||
package com.android.systemui.qs.tileimpl;
|
||||
|
||||
import static com.android.systemui.qs.QSColorControllerKt.colorIcon;
|
||||
import static com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
@@ -148,10 +150,15 @@ public class QSIconViewImpl extends QSIconView {
|
||||
iv.clearColorFilter();
|
||||
}
|
||||
if (state.state != mState) {
|
||||
int color = getColor(state.state);
|
||||
int color = getColor(state.state, state.colorActive);
|
||||
mState = state.state;
|
||||
if (mTint != 0 && allowAnimations && shouldAnimate(iv)) {
|
||||
animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
|
||||
if (colorIcon()) {
|
||||
animateColor(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
|
||||
} else {
|
||||
animateGrayScale(mTint, color, iv,
|
||||
() -> updateIcon(iv, state, allowAnimations));
|
||||
}
|
||||
mTint = color;
|
||||
} else {
|
||||
if (iv instanceof AlphaControlledSlashImageView) {
|
||||
@@ -168,8 +175,12 @@ public class QSIconViewImpl extends QSIconView {
|
||||
}
|
||||
}
|
||||
|
||||
protected int getColor(int state, int colorActive) {
|
||||
return getColorForState(getContext(), state, colorActive);
|
||||
}
|
||||
|
||||
protected int getColor(int state) {
|
||||
return getColorForState(getContext(), state);
|
||||
return getColor(state, -1);
|
||||
}
|
||||
|
||||
private void animateGrayScale(int fromColor, int toColor, ImageView iv,
|
||||
@@ -206,6 +217,37 @@ public class QSIconViewImpl extends QSIconView {
|
||||
}
|
||||
}
|
||||
|
||||
private void animateColor(int fromColor, int toColor, ImageView iv,
|
||||
final Runnable endRunnable) {
|
||||
if (iv instanceof AlphaControlledSlashImageView) {
|
||||
((AlphaControlledSlashImageView) iv)
|
||||
.setFinalImageTintList(ColorStateList.valueOf(toColor));
|
||||
}
|
||||
if (mAnimationEnabled && ValueAnimator.areAnimatorsEnabled()) {
|
||||
|
||||
|
||||
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
|
||||
anim.setDuration(QS_ANIM_LENGTH);
|
||||
anim.addUpdateListener(animation -> {
|
||||
float fraction = animation.getAnimatedFraction();
|
||||
int color = (int) ArgbEvaluator.getInstance().evaluate(fraction, fromColor,
|
||||
toColor);
|
||||
|
||||
setTint(iv, color);
|
||||
});
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
endRunnable.run();
|
||||
}
|
||||
});
|
||||
anim.start();
|
||||
} else {
|
||||
setTint(iv, toColor);
|
||||
endRunnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setTint(ImageView iv, int color) {
|
||||
iv.setImageTintList(ColorStateList.valueOf(color));
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
*/
|
||||
package com.android.systemui.qs.tileimpl;
|
||||
|
||||
import static com.android.systemui.qs.QSColorControllerKt.colorIcon;
|
||||
import static com.android.systemui.qs.QSColorControllerKt.overrideColor;
|
||||
import static com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
@@ -71,6 +73,9 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
|
||||
private int mCircleColor;
|
||||
private int mBgSize;
|
||||
|
||||
private final boolean mQsColors = overrideColor();
|
||||
private final boolean mQSIcons = colorIcon();
|
||||
|
||||
public QSTileBaseView(Context context, QSIconView icon) {
|
||||
this(context, icon, false);
|
||||
}
|
||||
@@ -194,7 +199,7 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
|
||||
}
|
||||
|
||||
protected void handleStateChanged(QSTile.State state) {
|
||||
int circleColor = getCircleColor(state.state);
|
||||
int circleColor = getCircleColor(state.state, mQsColors ? state.colorActive : -1);
|
||||
boolean allowAnimations = animationsEnabled();
|
||||
if (circleColor != mCircleColor) {
|
||||
if (allowAnimations) {
|
||||
@@ -239,10 +244,11 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
|
||||
return mLocInScreen[1] >= -getHeight();
|
||||
}
|
||||
|
||||
private int getCircleColor(int state) {
|
||||
private int getCircleColor(int state, int colorActive) {
|
||||
switch (state) {
|
||||
case Tile.STATE_ACTIVE:
|
||||
return mColorActive;
|
||||
int color = (colorActive == -1) ? mColorActive : colorActive;
|
||||
return mQsColors && mQSIcons ? Utils.applyAlpha(0.5f, color) : color;
|
||||
case Tile.STATE_INACTIVE:
|
||||
case Tile.STATE_UNAVAILABLE:
|
||||
return mColorDisabled;
|
||||
@@ -252,6 +258,10 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
|
||||
}
|
||||
}
|
||||
|
||||
private int getCircleColor(int state) {
|
||||
return getCircleColor(state, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClickable(boolean clickable) {
|
||||
super.setClickable(clickable);
|
||||
|
||||
@@ -26,6 +26,7 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_
|
||||
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_BAR_STATE;
|
||||
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
|
||||
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||
import static com.android.systemui.qs.QSColorControllerKt.colorIcon;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
@@ -54,6 +55,7 @@ import com.android.settingslib.Utils;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.Dumpable;
|
||||
import com.android.systemui.Prefs;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.qs.DetailAdapter;
|
||||
import com.android.systemui.plugins.qs.QSIconView;
|
||||
@@ -98,8 +100,8 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
|
||||
|
||||
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
|
||||
private final Object mStaleListener = new Object();
|
||||
protected TState mState = newTileState();
|
||||
private TState mTmpState = newTileState();
|
||||
protected TState mState;
|
||||
private TState mTmpState;
|
||||
private boolean mAnnounceNextStateChange;
|
||||
|
||||
private String mTileSpec;
|
||||
@@ -132,6 +134,8 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
|
||||
protected QSTileImpl(QSHost host) {
|
||||
mHost = host;
|
||||
mContext = host.getContext();
|
||||
mState = newTileState();
|
||||
mTmpState = newTileState();
|
||||
mQSSettingsPanelOption = QSSettingsControllerKt.getQSSettingsPanelOption();
|
||||
}
|
||||
|
||||
@@ -422,6 +426,10 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
|
||||
public abstract CharSequence getTileLabel();
|
||||
|
||||
public static int getColorForState(Context context, int state) {
|
||||
return getColorForState(context, state, -1);
|
||||
}
|
||||
|
||||
public static int getColorForState(Context context, int state, int colorActive) {
|
||||
switch (state) {
|
||||
case Tile.STATE_UNAVAILABLE:
|
||||
return Utils.getDisabled(context,
|
||||
@@ -429,13 +437,25 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
|
||||
case Tile.STATE_INACTIVE:
|
||||
return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary);
|
||||
case Tile.STATE_ACTIVE:
|
||||
return Utils.getColorAttrDefaultColor(context, android.R.attr.colorPrimary);
|
||||
return getActiveColor(context, colorActive);
|
||||
default:
|
||||
Log.e("QSTile", "Invalid state " + state);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getActiveColor(Context context, int colorActive) {
|
||||
if (colorIcon()) {
|
||||
if (colorActive == -1) {
|
||||
return Utils.getColorAccentDefaultColor(context);
|
||||
} else {
|
||||
return colorActive;
|
||||
}
|
||||
} else {
|
||||
return Utils.getColorAttrDefaultColor(context, android.R.attr.colorPrimary);
|
||||
}
|
||||
}
|
||||
|
||||
protected final class H extends Handler {
|
||||
private static final int ADD_CALLBACK = 1;
|
||||
private static final int CLICK = 2;
|
||||
@@ -614,4 +634,27 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
|
||||
pw.println(this.getClass().getSimpleName() + ":");
|
||||
pw.print(" "); pw.println(getState().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColor(@ColorTile int color) {
|
||||
int resId;
|
||||
switch(color) {
|
||||
case COLOR_TILE_RED:
|
||||
resId = R.color.GM2_red_500;
|
||||
break;
|
||||
case COLOR_TILE_BLUE:
|
||||
resId = R.color.GM2_blue_500;
|
||||
break;
|
||||
case COLOR_TILE_GREEN:
|
||||
resId = R.color.GM2_green_500;
|
||||
break;
|
||||
case COLOR_TILE_YELLOW:
|
||||
resId = R.color.GM2_yellow_500;
|
||||
break;
|
||||
default:
|
||||
resId = -1;
|
||||
}
|
||||
mTmpState.colorActive = resId == -1 ? -1 : mContext.getColor(resId);
|
||||
refreshState();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user