Merge "Make disabled by policy tiles look UNAVAILABLE" into tm-qpr-dev

This commit is contained in:
Fabian Kozynski
2022-09-06 16:20:27 +00:00
committed by Android (Google) Code Review
6 changed files with 178 additions and 53 deletions

View File

@@ -1644,8 +1644,9 @@
<!-- The tile in quick settings is unavailable. [CHAR LIMIT=32] --> <!-- The tile in quick settings is unavailable. [CHAR LIMIT=32] -->
<string name="tile_unavailable">Unavailable</string> <string name="tile_unavailable">Unavailable</string>
<!-- The tile in quick settings is disabled by a device administration policy [CHAR LIMIT=32] --> <!-- Accessibility text for the click action on a tile that is disabled by policy. This will
<string name="tile_disabled">Disabled</string> be used following "Double-tap to..." [CHAR LIMIT=NONE] -->
<string name="accessibility_tile_disabled_by_policy_action_description">learn more</string>
<!-- SysUI Tuner: Button that leads to the navigation bar customization screen [CHAR LIMIT=60] --> <!-- SysUI Tuner: Button that leads to the navigation bar customization screen [CHAR LIMIT=60] -->
<string name="nav_bar">Navigation bar</string> <string name="nav_bar">Navigation bar</string>

View File

@@ -165,7 +165,7 @@ public class QSIconViewImpl extends QSIconView {
iv.clearColorFilter(); iv.clearColorFilter();
} }
if (state.state != mState) { if (state.state != mState) {
int color = getColor(state.state); int color = getColor(state);
mState = state.state; mState = state.state;
if (mTint != 0 && allowAnimations && shouldAnimate(iv)) { if (mTint != 0 && allowAnimations && shouldAnimate(iv)) {
animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations)); animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
@@ -183,7 +183,7 @@ public class QSIconViewImpl extends QSIconView {
} }
} }
protected int getColor(int state) { protected int getColor(QSTile.State state) {
return getIconColorForState(getContext(), state); return getIconColorForState(getContext(), state);
} }
@@ -239,19 +239,18 @@ public class QSIconViewImpl extends QSIconView {
/** /**
* Color to tint the tile icon based on state * Color to tint the tile icon based on state
*/ */
public static int getIconColorForState(Context context, int state) { private static int getIconColorForState(Context context, QSTile.State state) {
switch (state) { if (state.disabledByPolicy || state.state == Tile.STATE_UNAVAILABLE) {
case Tile.STATE_UNAVAILABLE: return Utils.applyAlpha(QSTileViewImpl.UNAVAILABLE_ALPHA,
return Utils.applyAlpha(QSTileViewImpl.UNAVAILABLE_ALPHA, Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary));
Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)); } else if (state.state == Tile.STATE_INACTIVE) {
case Tile.STATE_INACTIVE: return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary);
return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary); } else if (state.state == Tile.STATE_ACTIVE) {
case Tile.STATE_ACTIVE: return Utils.getColorAttrDefaultColor(context,
return Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorOnAccent);
com.android.internal.R.attr.textColorOnAccent); } else {
default: Log.e("QSIconView", "Invalid state " + state);
Log.e("QSIconView", "Invalid state " + state); return 0;
return 0;
} }
} }

View File

@@ -35,6 +35,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo import android.view.accessibility.AccessibilityNodeInfo
import android.widget.Button
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Switch import android.widget.Switch
@@ -144,6 +145,7 @@ open class QSTileViewImpl @JvmOverloads constructor(
superSetVisibility = { super.setVisibility(it) }, superSetVisibility = { super.setVisibility(it) },
superSetTransitionVisibility = { super.setTransitionVisibility(it) }, superSetTransitionVisibility = { super.setTransitionVisibility(it) },
) )
private var lastDisabledByPolicy = false
private val locInScreen = IntArray(2) private val locInScreen = IntArray(2)
@@ -376,8 +378,22 @@ open class QSTileViewImpl @JvmOverloads constructor(
super.onInitializeAccessibilityNodeInfo(info) super.onInitializeAccessibilityNodeInfo(info)
// Clear selected state so it is not announce by talkback. // Clear selected state so it is not announce by talkback.
info.isSelected = false info.isSelected = false
if (lastDisabledByPolicy) {
info.addAction(
AccessibilityNodeInfo.AccessibilityAction(
AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
resources.getString(
R.string.accessibility_tile_disabled_by_policy_action_description
)
)
)
}
if (!TextUtils.isEmpty(accessibilityClass)) { if (!TextUtils.isEmpty(accessibilityClass)) {
info.className = accessibilityClass info.className = if (lastDisabledByPolicy) {
Button::class.java.name
} else {
accessibilityClass
}
if (Switch::class.java.name == accessibilityClass) { if (Switch::class.java.name == accessibilityClass) {
val label = resources.getString( val label = resources.getString(
if (tileState) R.string.switch_bar_on else R.string.switch_bar_off) if (tileState) R.string.switch_bar_on else R.string.switch_bar_off)
@@ -430,6 +446,10 @@ open class QSTileViewImpl @JvmOverloads constructor(
state.secondaryLabel = stateText state.secondaryLabel = stateText
} }
} }
if (state.disabledByPolicy && state.state != Tile.STATE_UNAVAILABLE) {
stateDescription.append(", ")
stateDescription.append(getUnavailableText(state.spec))
}
if (!TextUtils.isEmpty(state.stateDescription)) { if (!TextUtils.isEmpty(state.stateDescription)) {
stateDescription.append(", ") stateDescription.append(", ")
stateDescription.append(state.stateDescription) stateDescription.append(state.stateDescription)
@@ -470,38 +490,38 @@ open class QSTileViewImpl @JvmOverloads constructor(
} }
// Colors // Colors
if (state.state != lastState) { if (state.state != lastState || state.disabledByPolicy || lastDisabledByPolicy) {
singleAnimator.cancel() singleAnimator.cancel()
if (allowAnimations) { if (allowAnimations) {
singleAnimator.setValues( singleAnimator.setValues(
colorValuesHolder( colorValuesHolder(
BACKGROUND_NAME, BACKGROUND_NAME,
paintColor, paintColor,
getBackgroundColorForState(state.state) getBackgroundColorForState(state.state, state.disabledByPolicy)
), ),
colorValuesHolder( colorValuesHolder(
LABEL_NAME, LABEL_NAME,
label.currentTextColor, label.currentTextColor,
getLabelColorForState(state.state) getLabelColorForState(state.state, state.disabledByPolicy)
), ),
colorValuesHolder( colorValuesHolder(
SECONDARY_LABEL_NAME, SECONDARY_LABEL_NAME,
secondaryLabel.currentTextColor, secondaryLabel.currentTextColor,
getSecondaryLabelColorForState(state.state) getSecondaryLabelColorForState(state.state, state.disabledByPolicy)
), ),
colorValuesHolder( colorValuesHolder(
CHEVRON_NAME, CHEVRON_NAME,
chevronView.imageTintList?.defaultColor ?: 0, chevronView.imageTintList?.defaultColor ?: 0,
getChevronColorForState(state.state) getChevronColorForState(state.state, state.disabledByPolicy)
) )
) )
singleAnimator.start() singleAnimator.start()
} else { } else {
setAllColors( setAllColors(
getBackgroundColorForState(state.state), getBackgroundColorForState(state.state, state.disabledByPolicy),
getLabelColorForState(state.state), getLabelColorForState(state.state, state.disabledByPolicy),
getSecondaryLabelColorForState(state.state), getSecondaryLabelColorForState(state.state, state.disabledByPolicy),
getChevronColorForState(state.state) getChevronColorForState(state.state, state.disabledByPolicy)
) )
} }
} }
@@ -512,6 +532,7 @@ open class QSTileViewImpl @JvmOverloads constructor(
label.isEnabled = !state.disabledByPolicy label.isEnabled = !state.disabledByPolicy
lastState = state.state lastState = state.state
lastDisabledByPolicy = state.disabledByPolicy
} }
private fun setAllColors( private fun setAllColors(
@@ -559,13 +580,14 @@ open class QSTileViewImpl @JvmOverloads constructor(
} }
} }
private fun getStateText(state: QSTile.State): String { private fun getUnavailableText(spec: String?): String {
if (state.disabledByPolicy) { val arrayResId = SubtitleArrayMapping.getSubtitleId(spec)
return context.getString(R.string.tile_disabled) return resources.getStringArray(arrayResId)[Tile.STATE_UNAVAILABLE]
} }
private fun getStateText(state: QSTile.State): String {
return if (state.state == Tile.STATE_UNAVAILABLE || state is BooleanState) { return if (state.state == Tile.STATE_UNAVAILABLE || state is BooleanState) {
var arrayResId = SubtitleArrayMapping.getSubtitleId(state.spec) val arrayResId = SubtitleArrayMapping.getSubtitleId(state.spec)
val array = resources.getStringArray(arrayResId) val array = resources.getStringArray(arrayResId)
array[state.state] array[state.state]
} else { } else {
@@ -587,11 +609,11 @@ open class QSTileViewImpl @JvmOverloads constructor(
return locInScreen.get(1) >= -height return locInScreen.get(1) >= -height
} }
private fun getBackgroundColorForState(state: Int): Int { private fun getBackgroundColorForState(state: Int, disabledByPolicy: Boolean = false): Int {
return when (state) { return when {
Tile.STATE_ACTIVE -> colorActive state == Tile.STATE_UNAVAILABLE || disabledByPolicy -> colorUnavailable
Tile.STATE_INACTIVE -> colorInactive state == Tile.STATE_ACTIVE -> colorActive
Tile.STATE_UNAVAILABLE -> colorUnavailable state == Tile.STATE_INACTIVE -> colorInactive
else -> { else -> {
Log.e(TAG, "Invalid state $state") Log.e(TAG, "Invalid state $state")
0 0
@@ -599,11 +621,11 @@ open class QSTileViewImpl @JvmOverloads constructor(
} }
} }
private fun getLabelColorForState(state: Int): Int { private fun getLabelColorForState(state: Int, disabledByPolicy: Boolean = false): Int {
return when (state) { return when {
Tile.STATE_ACTIVE -> colorLabelActive state == Tile.STATE_UNAVAILABLE || disabledByPolicy -> colorLabelUnavailable
Tile.STATE_INACTIVE -> colorLabelInactive state == Tile.STATE_ACTIVE -> colorLabelActive
Tile.STATE_UNAVAILABLE -> colorLabelUnavailable state == Tile.STATE_INACTIVE -> colorLabelInactive
else -> { else -> {
Log.e(TAG, "Invalid state $state") Log.e(TAG, "Invalid state $state")
0 0
@@ -611,11 +633,11 @@ open class QSTileViewImpl @JvmOverloads constructor(
} }
} }
private fun getSecondaryLabelColorForState(state: Int): Int { private fun getSecondaryLabelColorForState(state: Int, disabledByPolicy: Boolean = false): Int {
return when (state) { return when {
Tile.STATE_ACTIVE -> colorSecondaryLabelActive state == Tile.STATE_UNAVAILABLE || disabledByPolicy -> colorSecondaryLabelUnavailable
Tile.STATE_INACTIVE -> colorSecondaryLabelInactive state == Tile.STATE_ACTIVE -> colorSecondaryLabelActive
Tile.STATE_UNAVAILABLE -> colorSecondaryLabelUnavailable state == Tile.STATE_INACTIVE -> colorSecondaryLabelInactive
else -> { else -> {
Log.e(TAG, "Invalid state $state") Log.e(TAG, "Invalid state $state")
0 0
@@ -623,7 +645,16 @@ open class QSTileViewImpl @JvmOverloads constructor(
} }
} }
private fun getChevronColorForState(state: Int): Int = getSecondaryLabelColorForState(state) private fun getChevronColorForState(state: Int, disabledByPolicy: Boolean = false): Int =
getSecondaryLabelColorForState(state, disabledByPolicy)
@VisibleForTesting
internal fun getCurrentColors(): List<Int> = listOf(
paintColor,
label.currentTextColor,
secondaryLabel.currentTextColor,
chevronView.imageTintList?.defaultColor ?: 0
)
} }
@VisibleForTesting @VisibleForTesting

View File

@@ -27,6 +27,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter; import android.graphics.ColorFilter;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
@@ -58,7 +59,6 @@ import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSIconViewImpl;
import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.MessageRouter; import com.android.systemui.util.concurrency.MessageRouter;
@@ -304,7 +304,7 @@ public class GarbageMonitor implements Dumpable {
MemoryIconDrawable(Context context) { MemoryIconDrawable(Context context) {
baseIcon = context.getDrawable(R.drawable.ic_memory).mutate(); baseIcon = context.getDrawable(R.drawable.ic_memory).mutate();
dp = context.getResources().getDisplayMetrics().density; dp = context.getResources().getDisplayMetrics().density;
paint.setColor(QSIconViewImpl.getIconColorForState(context, STATE_ACTIVE)); paint.setColor(Color.WHITE);
} }
public void setRss(long rss) { public void setRss(long rss) {

View File

@@ -97,7 +97,7 @@ public class QSIconViewImplTest extends SysuiTestCase {
ImageView iv = mock(ImageView.class); ImageView iv = mock(ImageView.class);
State s = new State(); State s = new State();
s.state = Tile.STATE_ACTIVE; s.state = Tile.STATE_ACTIVE;
int desiredColor = mIconView.getColor(s.state); int desiredColor = mIconView.getColor(s);
when(iv.isShown()).thenReturn(true); when(iv.isShown()).thenReturn(true);
mIconView.setIcon(iv, s, true); mIconView.setIcon(iv, s, true);
@@ -109,7 +109,7 @@ public class QSIconViewImplTest extends SysuiTestCase {
ImageView iv = mock(ImageView.class); ImageView iv = mock(ImageView.class);
State s = new State(); State s = new State();
s.state = Tile.STATE_ACTIVE; s.state = Tile.STATE_ACTIVE;
int desiredColor = mIconView.getColor(s.state); int desiredColor = mIconView.getColor(s);
Icon i = mock(Icon.class); Icon i = mock(Icon.class);
s.icon = i; s.icon = i;
when(i.toString()).thenReturn("MOCK ICON"); when(i.toString()).thenReturn("MOCK ICON");
@@ -124,6 +124,18 @@ public class QSIconViewImplTest extends SysuiTestCase {
assertFalse(mIconView.toString().contains("lastIcon")); assertFalse(mIconView.toString().contains("lastIcon"));
} }
@Test
public void testIconColorDisabledByPolicy_sameAsUnavailable() {
State s1 = new State();
s1.state = Tile.STATE_INACTIVE;
s1.disabledByPolicy = true;
State s2 = new State();
s2.state = Tile.STATE_UNAVAILABLE;
assertEquals(mIconView.getColor(s1), mIconView.getColor(s2));
}
private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) { private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
return new Drawable.ConstantState() { return new Drawable.ConstantState() {
@Override @Override

View File

@@ -280,6 +280,88 @@ class QSTileViewImplTest : SysuiTestCase() {
assertThat(info.collectionItemInfo).isNull() assertThat(info.collectionItemInfo).isNull()
} }
@Test
fun testDisabledByPolicyInactive_usesUnavailableColors() {
val stateDisabledByPolicy = QSTile.State()
stateDisabledByPolicy.state = Tile.STATE_INACTIVE
stateDisabledByPolicy.disabledByPolicy = true
val stateUnavailable = QSTile.State()
stateUnavailable.state = Tile.STATE_UNAVAILABLE
tileView.changeState(stateDisabledByPolicy)
val colorsDisabledByPolicy = tileView.getCurrentColors()
tileView.changeState(stateUnavailable)
val colorsUnavailable = tileView.getCurrentColors()
assertThat(colorsDisabledByPolicy).containsExactlyElementsIn(colorsUnavailable)
}
@Test
fun testDisabledByPolicyActive_usesUnavailableColors() {
val stateDisabledByPolicy = QSTile.State()
stateDisabledByPolicy.state = Tile.STATE_ACTIVE
stateDisabledByPolicy.disabledByPolicy = true
val stateUnavailable = QSTile.State()
stateUnavailable.state = Tile.STATE_UNAVAILABLE
tileView.changeState(stateDisabledByPolicy)
val colorsDisabledByPolicy = tileView.getCurrentColors()
tileView.changeState(stateUnavailable)
val colorsUnavailable = tileView.getCurrentColors()
assertThat(colorsDisabledByPolicy).containsExactlyElementsIn(colorsUnavailable)
}
@Test
fun testDisabledByPolicy_secondaryLabelText() {
val testA11yLabel = "TEST_LABEL"
context.orCreateTestableResources
.addOverride(
R.string.accessibility_tile_disabled_by_policy_action_description,
testA11yLabel
)
val stateDisabledByPolicy = QSTile.State()
stateDisabledByPolicy.state = Tile.STATE_INACTIVE
stateDisabledByPolicy.disabledByPolicy = true
tileView.changeState(stateDisabledByPolicy)
val info = AccessibilityNodeInfo(tileView)
tileView.onInitializeAccessibilityNodeInfo(info)
assertThat(
info.actionList.find {
it.id == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id
}?.label
).isEqualTo(testA11yLabel)
}
@Test
fun testDisabledByPolicy_unavailableInStateDescription() {
val state = QSTile.BooleanState()
val spec = "internet"
state.spec = spec
state.disabledByPolicy = true
state.state = Tile.STATE_INACTIVE
val unavailableString = "${spec}_unavailable"
val offString = "${spec}_off"
val onString = "${spec}_on"
context.orCreateTestableResources.addOverride(R.array.tile_states_internet, arrayOf(
unavailableString,
offString,
onString
))
tileView.changeState(state)
assertThat(tileView.stateDescription?.contains(unavailableString)).isTrue()
}
class FakeTileView( class FakeTileView(
context: Context, context: Context,
icon: QSIconView, icon: QSIconView,
@@ -289,4 +371,4 @@ class QSTileViewImplTest : SysuiTestCase() {
handleStateChanged(state) handleStateChanged(state)
} }
} }
} }