Merge "Implement the QS footer actions in Compose (1/2)" into tm-qpr-dev

This commit is contained in:
Jordan Demeulenaere
2022-12-20 12:34:34 +00:00
committed by Android (Google) Code Review
12 changed files with 528 additions and 23 deletions

View File

@@ -2331,6 +2331,7 @@
<java-symbol type="drawable" name="scrubber_control_selector_holo" />
<java-symbol type="drawable" name="scrubber_progress_horizontal_holo_dark" />
<java-symbol type="drawable" name="progress_small_material" />
<java-symbol type="drawable" name="ic_chevron_end" />
<java-symbol type="string" name="chooseUsbActivity" />
<java-symbol type="string" name="ext_media_badremoval_notification_message" />
<java-symbol type="string" name="ext_media_badremoval_notification_title" />

View File

@@ -24,6 +24,7 @@ import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
@@ -62,6 +63,7 @@ import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.layout.findRootCoordinates
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.ComposeView
@@ -118,12 +120,14 @@ fun Expandable(
contentColor: Color = contentColorFor(color),
borderStroke: BorderStroke? = null,
onClick: ((Expandable) -> Unit)? = null,
interactionSource: MutableInteractionSource? = null,
content: @Composable (Expandable) -> Unit,
) {
Expandable(
rememberExpandableController(color, shape, contentColor, borderStroke),
modifier,
onClick,
interactionSource,
content,
)
}
@@ -158,6 +162,7 @@ fun Expandable(
controller: ExpandableController,
modifier: Modifier = Modifier,
onClick: ((Expandable) -> Unit)? = null,
interactionSource: MutableInteractionSource? = null,
content: @Composable (Expandable) -> Unit,
) {
val controller = controller as ExpandableControllerImpl
@@ -190,6 +195,18 @@ fun Expandable(
var thisExpandableSize by remember { mutableStateOf(Size.Zero) }
/** Set the current element size as this Expandable size. */
fun Modifier.updateExpandableSize(): Modifier {
return this.onGloballyPositioned { coords ->
thisExpandableSize =
coords
.findRootCoordinates()
// Make sure that we report the actual size, and not the visual/clipped one.
.localBoundingBoxOf(coords, clipBounds = false)
.size
}
}
// Make sure we don't read animatorState directly here to avoid recomposition every time the
// state changes (i.e. every frame of the animation).
val isAnimating by remember {
@@ -247,7 +264,7 @@ fun Expandable(
controller.isDialogShowing.value -> {
Box(
modifier
.onGloballyPositioned { thisExpandableSize = it.boundsInRoot().size }
.updateExpandableSize()
.then(minInteractiveSizeModifier)
.drawWithContent { /* Don't draw anything when the dialog is shown. */}
.onGloballyPositioned {
@@ -258,18 +275,25 @@ fun Expandable(
else -> {
val clickModifier =
if (onClick != null) {
Modifier.clickable { onClick(controller.expandable) }
if (interactionSource != null) {
// If the caller provided an interaction source, then that means that they
// will draw the click indication themselves.
Modifier.clickable(interactionSource, indication = null) {
onClick(controller.expandable)
}
} else {
// If no interaction source is provided, we draw the default indication (a
// ripple) and make sure it's clipped by the expandable shape.
Modifier.clip(shape).clickable { onClick(controller.expandable) }
}
} else {
Modifier
}
Box(
modifier
.onGloballyPositioned { thisExpandableSize = it.boundsInRoot().size }
.updateExpandableSize()
.then(minInteractiveSizeModifier)
// Note that clip() *must* be above the clickModifier to properly clip the
// ripple.
.clip(shape)
.then(clickModifier)
.background(color, shape)
.border(controller)

View File

@@ -65,10 +65,12 @@ class AndroidColorScheme internal constructor(context: Context) {
val colorForeground = getColor(context, R.attr.colorForeground)
val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse)
private fun getColor(context: Context, attr: Int): Color {
val ta = context.obtainStyledAttributes(intArrayOf(attr))
@ColorInt val color = ta.getColor(0, 0)
ta.recycle()
return Color(color)
companion object {
fun getColor(context: Context, attr: Int): Color {
val ta = context.obtainStyledAttributes(intArrayOf(attr))
@ColorInt val color = ta.getColor(0, 0)
ta.recycle()
return Color(color)
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2022 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.compose.theme
import android.annotation.AttrRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
/** Read the [Color] from the given [attribute]. */
@Composable
@ReadOnlyComposable
fun colorAttr(@AttrRes attribute: Int): Color {
return AndroidColorScheme.getColor(LocalContext.current, attribute)
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2022 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.common.ui.compose
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.android.systemui.common.shared.model.ContentDescription
/** Returns the loaded [String] or `null` if there isn't one. */
@Composable
fun ContentDescription.load(): String? {
return when (this) {
is ContentDescription.Loaded -> description
is ContentDescription.Resource -> stringResource(res)
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2022 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.common.ui.compose
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource
import androidx.core.graphics.drawable.toBitmap
import com.android.systemui.common.shared.model.Icon
/**
* Icon composable that draws [icon] using [tint].
*
* Note: You can use [Color.Unspecified] to disable the tint and keep the original icon colors.
*/
@Composable
fun Icon(
icon: Icon,
modifier: Modifier = Modifier,
tint: Color = LocalContentColor.current,
) {
val contentDescription = icon.contentDescription?.load()
when (icon) {
is Icon.Loaded -> {
Icon(icon.drawable.toBitmap().asImageBitmap(), contentDescription, modifier, tint)
}
is Icon.Resource -> Icon(painterResource(icon.res), contentDescription, modifier, tint)
}
}

View File

@@ -53,7 +53,6 @@ import com.android.systemui.R
import com.android.systemui.compose.theme.LocalAndroidColorScheme
import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import kotlinx.coroutines.flow.collect
/**
* Compose the screen associated to a [PeopleViewModel].

View File

@@ -0,0 +1,361 @@
/*
* Copyright (C) 2022 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.footer.ui.compose
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.constrainHeight
import androidx.compose.ui.unit.constrainWidth
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.compose.animation.Expandable
import com.android.systemui.compose.modifiers.background
import com.android.systemui.compose.theme.LocalAndroidColorScheme
import com.android.systemui.compose.theme.colorAttr
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import kotlinx.coroutines.launch
/** The Quick Settings footer actions row. */
@Composable
fun FooterActions(
viewModel: FooterActionsViewModel,
qsVisibilityLifecycleOwner: LifecycleOwner,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
// Collect visibility and alphas as soon as we are composed, even when not visible.
val isVisible by viewModel.isVisible.collectAsState()
val alpha by viewModel.alpha.collectAsState()
val backgroundAlpha = viewModel.backgroundAlpha.collectAsState()
var security by remember { mutableStateOf<FooterActionsSecurityButtonViewModel?>(null) }
var foregroundServices by remember {
mutableStateOf<FooterActionsForegroundServicesButtonViewModel?>(null)
}
var userSwitcher by remember { mutableStateOf<FooterActionsButtonViewModel?>(null) }
LaunchedEffect(
context,
qsVisibilityLifecycleOwner,
viewModel,
viewModel.security,
viewModel.foregroundServices,
viewModel.userSwitcher,
) {
launch {
// Listen for dialog requests as soon as we are composed, even when not visible.
viewModel.observeDeviceMonitoringDialogRequests(context)
}
// Listen for model changes only when QS are visible.
qsVisibilityLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
launch { viewModel.security.collect { security = it } }
launch { viewModel.foregroundServices.collect { foregroundServices = it } }
launch { viewModel.userSwitcher.collect { userSwitcher = it } }
}
}
val backgroundColor = colorAttr(R.attr.underSurfaceColor)
val contentColor = LocalAndroidColorScheme.current.textColorPrimary
val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius)
val backgroundModifier =
remember(
backgroundColor,
backgroundAlpha,
backgroundTopRadius,
) {
Modifier.background(
backgroundColor,
backgroundAlpha::value,
RoundedCornerShape(topStart = backgroundTopRadius, topEnd = backgroundTopRadius),
)
}
Row(
modifier
.fillMaxWidth()
.graphicsLayer { this.alpha = alpha }
.drawWithContent {
if (isVisible) {
drawContent()
}
}
.then(backgroundModifier)
.padding(
top = dimensionResource(R.dimen.qs_footer_actions_top_padding),
bottom = dimensionResource(R.dimen.qs_footer_actions_bottom_padding),
)
.layout { measurable, constraints ->
// All buttons have a 4dp padding to increase their touch size. To be consistent
// with the View implementation, we want to left-most and right-most buttons to be
// visually aligned with the left and right sides of this row. So we let this
// component be 2*4dp wider and then offset it by -4dp to the start.
val inset = 4.dp.roundToPx()
val additionalWidth = inset * 2
val newConstraints =
if (constraints.hasBoundedWidth) {
constraints.copy(maxWidth = constraints.maxWidth + additionalWidth)
} else {
constraints
}
val placeable = measurable.measure(newConstraints)
val width = constraints.constrainWidth(placeable.width - additionalWidth)
val height = constraints.constrainHeight(placeable.height)
layout(width, height) { placeable.place(-inset, 0) }
},
verticalAlignment = Alignment.CenterVertically,
) {
CompositionLocalProvider(
LocalContentColor provides contentColor,
) {
if (security == null && foregroundServices == null) {
Spacer(Modifier.weight(1f))
}
security?.let { SecurityButton(it, Modifier.weight(1f)) }
foregroundServices?.let { ForegroundServicesButton(it) }
userSwitcher?.let { IconButton(it) }
IconButton(viewModel.settings)
viewModel.power?.let { IconButton(it) }
}
}
}
/** The security button. */
@Composable
private fun SecurityButton(
model: FooterActionsSecurityButtonViewModel,
modifier: Modifier = Modifier,
) {
val onClick: ((Expandable) -> Unit)? =
model.onClick?.let { onClick ->
val context = LocalContext.current
{ expandable -> onClick(context, expandable) }
}
TextButton(
model.icon,
model.text,
showNewDot = false,
onClick = onClick,
modifier,
)
}
/** The foreground services button. */
@Composable
private fun RowScope.ForegroundServicesButton(
model: FooterActionsForegroundServicesButtonViewModel,
) {
if (model.displayText) {
TextButton(
Icon.Resource(R.drawable.ic_info_outline, contentDescription = null),
model.text,
showNewDot = model.hasNewChanges,
onClick = model.onClick,
Modifier.weight(1f),
)
} else {
NumberButton(
model.foregroundServicesCount,
showNewDot = model.hasNewChanges,
onClick = model.onClick,
)
}
}
/** A button with an icon. */
@Composable
private fun IconButton(
model: FooterActionsButtonViewModel,
modifier: Modifier = Modifier,
) {
Expandable(
color = colorAttr(model.backgroundColor),
shape = CircleShape,
onClick = model.onClick,
modifier = modifier,
) {
val tint = model.iconTint?.let { Color(it) } ?: Color.Unspecified
Icon(
model.icon,
tint = tint,
modifier = Modifier.size(20.dp),
)
}
}
/** A button with a number an an optional dot (to indicate new changes). */
@Composable
private fun NumberButton(
number: Int,
showNewDot: Boolean,
onClick: (Expandable) -> Unit,
modifier: Modifier = Modifier,
) {
// By default Expandable will show a ripple above its content when clicked, and clip the content
// with the shape of the expandable. In this case we also want to show a "new changes dot"
// outside of the shape, so we can't clip. To work around that we can pass our own interaction
// source and draw the ripple indication ourselves above the text but below the "new changes
// dot".
val interactionSource = remember { MutableInteractionSource() }
Expandable(
color = colorAttr(R.attr.offStateColor),
shape = CircleShape,
onClick = onClick,
interactionSource = interactionSource,
modifier = modifier,
) {
Box(Modifier.size(40.dp)) {
Box(
Modifier.fillMaxSize()
.clip(CircleShape)
.indication(
interactionSource,
LocalIndication.current,
)
) {
Text(
number.toString(),
modifier = Modifier.align(Alignment.Center),
style = MaterialTheme.typography.bodyLarge,
color = LocalAndroidColorScheme.current.textColorPrimary,
// TODO(b/242040009): This should only use a standard text style instead and
// should not override the text size.
fontSize = 18.sp,
)
}
if (showNewDot) {
NewChangesDot(Modifier.align(Alignment.BottomEnd))
}
}
}
}
/** A dot that indicates new changes. */
@Composable
private fun NewChangesDot(modifier: Modifier = Modifier) {
val contentDescription = stringResource(R.string.fgs_dot_content_description)
val color = LocalAndroidColorScheme.current.colorAccentTertiary
Canvas(modifier.size(12.dp).semantics { this.contentDescription = contentDescription }) {
drawCircle(color)
}
}
/** A larger button with an icon, some text and an optional dot (to indicate new changes). */
@Composable
private fun TextButton(
icon: Icon,
text: String,
showNewDot: Boolean,
onClick: ((Expandable) -> Unit)?,
modifier: Modifier = Modifier,
) {
Expandable(
shape = CircleShape,
color = colorAttr(R.attr.underSurfaceColor),
contentColor = LocalAndroidColorScheme.current.textColorSecondary,
borderStroke = BorderStroke(1.dp, LocalAndroidColorScheme.current.colorBackground),
modifier = modifier.padding(horizontal = 4.dp),
onClick = onClick,
) {
Row(
Modifier.padding(horizontal = dimensionResource(R.dimen.qs_footer_padding)),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(icon, Modifier.padding(end = 12.dp).size(20.dp))
Text(
text,
Modifier.weight(1f),
style = MaterialTheme.typography.bodyMedium,
// TODO(b/242040009): Remove this letter spacing. We should only use the M3 text
// styles without modifying them.
letterSpacing = 0.01.em,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (showNewDot) {
NewChangesDot(Modifier.padding(start = 8.dp))
}
if (onClick != null) {
Icon(
painterResource(com.android.internal.R.drawable.ic_chevron_end),
contentDescription = null,
Modifier.padding(start = 8.dp).size(20.dp),
)
}
}
}
}

View File

@@ -237,7 +237,13 @@ object FooterActionsViewBinder {
return
}
buttonView.setBackgroundResource(model.background)
val backgroundResource =
when (model.backgroundColor) {
R.attr.offStateColor -> R.drawable.qs_footer_action_circle
com.android.internal.R.attr.colorAccent -> R.drawable.qs_footer_action_circle_color
else -> error("Unsupported icon background resource ${model.backgroundColor}")
}
buttonView.setBackgroundResource(backgroundResource)
buttonView.setOnClickListener { model.onClick(Expandable.fromView(buttonView)) }
val icon = model.icon

View File

@@ -16,7 +16,8 @@
package com.android.systemui.qs.footer.ui.viewmodel
import android.annotation.DrawableRes
import android.annotation.AttrRes
import android.annotation.ColorInt
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
@@ -27,7 +28,7 @@ import com.android.systemui.common.shared.model.Icon
data class FooterActionsButtonViewModel(
val id: Int,
val icon: Icon,
val iconTint: Int?,
@DrawableRes val background: Int,
@ColorInt val iconTint: Int?,
@AttrRes val backgroundColor: Int,
val onClick: (Expandable) -> Unit,
)

View File

@@ -18,6 +18,7 @@ package com.android.systemui.qs.footer.ui.viewmodel
import android.content.Context
import android.util.Log
import android.view.ContextThemeWrapper
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
@@ -49,12 +50,15 @@ import kotlinx.coroutines.flow.map
/** A ViewModel for the footer actions. */
class FooterActionsViewModel(
@Application private val context: Context,
@Application appContext: Context,
private val footerActionsInteractor: FooterActionsInteractor,
private val falsingManager: FalsingManager,
private val globalActionsDialogLite: GlobalActionsDialogLite,
showPowerButton: Boolean,
) {
/** The context themed with the Quick Settings colors. */
private val context = ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings)
/**
* Whether the UI rendering this ViewModel should be visible. Note that even when this is false,
* the UI should still participate to the layout it is included in (i.e. in the View world it
@@ -142,7 +146,7 @@ class FooterActionsViewModel(
ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
),
iconTint = null,
R.drawable.qs_footer_action_circle,
backgroundColor = R.attr.offStateColor,
this::onSettingsButtonClicked,
)
@@ -160,7 +164,7 @@ class FooterActionsViewModel(
context,
com.android.internal.R.attr.textColorOnAccent,
),
R.drawable.qs_footer_action_circle_color,
backgroundColor = com.android.internal.R.attr.colorAccent,
this::onPowerButtonClicked,
)
} else {
@@ -260,7 +264,7 @@ class FooterActionsViewModel(
),
),
iconTint = null,
background = R.drawable.qs_footer_action_circle,
backgroundColor = R.attr.offStateColor,
onClick = this::onUserSwitcherClicked,
)
}

View File

@@ -84,7 +84,7 @@ class FooterActionsViewModelTest : SysuiTestCase() {
ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
)
)
assertThat(settings.background).isEqualTo(R.drawable.qs_footer_action_circle)
assertThat(settings.backgroundColor).isEqualTo(R.attr.offStateColor)
assertThat(settings.iconTint).isNull()
}
@@ -105,7 +105,7 @@ class FooterActionsViewModelTest : SysuiTestCase() {
ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
)
)
assertThat(power.background).isEqualTo(R.drawable.qs_footer_action_circle_color)
assertThat(power.backgroundColor).isEqualTo(com.android.internal.R.attr.colorAccent)
assertThat(power.iconTint)
.isEqualTo(
Utils.getColorAttrDefaultColor(
@@ -170,7 +170,7 @@ class FooterActionsViewModelTest : SysuiTestCase() {
assertThat(userSwitcher).isNotNull()
assertThat(userSwitcher!!.icon)
.isEqualTo(Icon.Loaded(picture, ContentDescription.Loaded("Signed in as foo")))
assertThat(userSwitcher.background).isEqualTo(R.drawable.qs_footer_action_circle)
assertThat(userSwitcher.backgroundColor).isEqualTo(R.attr.offStateColor)
// Change the current user name.
userSwitcherControllerWrapper.currentUserName = "bar"