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(); + } }