diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 834f4fc75dce2..638858a38335e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -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;
}
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 7b55d18676dae..0febc8ed42621 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -83,4 +83,9 @@
#ff80cbc4
#fff28b82
+ #FF41Af6A
+ #5195EA
+ #E25142
+ #F5C518
+
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 61816f60d0baf..bda1c52f27c0d 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -204,4 +204,6 @@
#FFFBBC04
#FF34A853
+ #FF4285F4
+
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSColorController.kt b/packages/SystemUI/src/com/android/systemui/qs/QSColorController.kt
new file mode 100644
index 0000000000000..3f0c5bb7f6209
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSColorController.kt
@@ -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
+ ) : MutableMap 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()
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index b27bf32b93824..61d7498ced946 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -88,6 +88,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener, 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, 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, D
mServices.destroy();
mPluginManager.removePluginListener(this);
mDumpController.unregisterDumpable(this);
+
+ mQSColorController.destroy();
}
@Override
@@ -275,6 +281,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener, D
mCallbacks.get(i).onTilesChanged();
}
}
+
+ mQSColorController.applyColorToAllTiles();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 31526bf8f5e12..88c7964144c16 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -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));
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index c186e59056aa2..f4a516261ade9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -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);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 3d6ee4cf96176..681de378ff573 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -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 implements QSTile, Lifecy
private final ArrayList 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 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 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 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 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();
+ }
}