Merge changes I69e7ef22,I6ca5a886 into main

* changes:
  Enforce dark mode in QS
  Create a border for focus.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 20c8c6a3..2d32fd7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -36,6 +36,7 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.CornerSize
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.Icon
 import androidx.compose.material3.LocalContentColor
@@ -84,6 +85,7 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.qs.ui.composable.QuickSettingsTheme
+import com.android.systemui.qs.ui.compose.borderOnFocus
 import com.android.systemui.res.R
 import kotlinx.coroutines.launch
 
@@ -264,7 +266,11 @@
         color = colorAttr(model.backgroundColor),
         shape = CircleShape,
         onClick = model.onClick,
-        modifier = modifier,
+        modifier =
+            modifier.borderOnFocus(
+                color = MaterialTheme.colorScheme.secondary,
+                CornerSize(percent = 50),
+            ),
     ) {
         val tint = model.iconTint?.let { Color(it) } ?: Color.Unspecified
         Icon(model.icon, tint = tint, modifier = Modifier.size(20.dp))
@@ -291,7 +297,11 @@
         shape = CircleShape,
         onClick = onClick,
         interactionSource = interactionSource,
-        modifier = modifier,
+        modifier =
+            modifier.borderOnFocus(
+                color = MaterialTheme.colorScheme.secondary,
+                CornerSize(percent = 50),
+            ),
     ) {
         Box(Modifier.size(40.dp)) {
             Box(
@@ -342,7 +352,10 @@
         color = colorAttr(R.attr.underSurface),
         contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
         borderStroke = BorderStroke(1.dp, colorAttr(R.attr.shadeInactive)),
-        modifier = modifier.padding(horizontal = 4.dp),
+        modifier =
+            modifier
+                .padding(horizontal = 4.dp)
+                .borderOnFocus(color = MaterialTheme.colorScheme.secondary, CornerSize(50)),
         onClick = onClick,
     ) {
         Row(
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
index 917a4ff..ccd953d 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
@@ -24,6 +24,7 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CornerSize
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -31,7 +32,6 @@
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
@@ -61,6 +61,7 @@
 import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig
 import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
 import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.qs.ui.compose.borderOnFocus
 import com.android.systemui.res.R
 import com.android.systemui.utils.PolicyRestriction
 
@@ -102,11 +103,12 @@
             null
         }
 
-    val overriddenByAppState by if (Flags.showToastWhenAppControlBrightness()) {
-        viewModel.brightnessOverriddenByWindow.collectAsStateWithLifecycle()
-    } else {
-        mutableStateOf(false)
-    }
+    val overriddenByAppState =
+        if (Flags.showToastWhenAppControlBrightness()) {
+            viewModel.brightnessOverriddenByWindow.collectAsStateWithLifecycle().value
+        } else {
+            false
+        }
 
     PlatformSlider(
         value = animatedValue,
@@ -160,7 +162,7 @@
                 if (interaction is DragInteraction.Start && overriddenByAppState) {
                     viewModel.showToast(
                         context,
-                        R.string.quick_settings_brightness_unable_adjust_msg
+                        R.string.quick_settings_brightness_unable_adjust_msg,
                     )
                 }
             }
@@ -213,7 +215,11 @@
                 coroutineScope.launch { viewModel.onDrag(Drag.Stopped(GammaBrightness(it))) }
             },
             modifier =
-                Modifier.then(if (viewModel.showMirror) Modifier.drawInOverlay() else Modifier)
+                Modifier.borderOnFocus(
+                        color = MaterialTheme.colorScheme.secondary,
+                        cornerSize = CornerSize(32.dp),
+                    )
+                    .then(if (viewModel.showMirror) Modifier.drawInOverlay() else Modifier)
                     .sliderBackground(containerColor)
                     .fillMaxWidth(),
             formatter = viewModel::formatValue,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
index e761c73..58ce194 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.IndicationNodeFactory
+import androidx.compose.foundation.LocalIndication
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.clickable
@@ -126,8 +127,7 @@
                     .selectable(
                         selected = selected,
                         interactionSource = interactionSource,
-                        indication =
-                            ShortcutHelperIndication(interactionSource, interactionsConfig),
+                        indication = ShortcutHelperIndication(interactionsConfig),
                         enabled = enabled,
                         onClick = onClick,
                     )
@@ -181,8 +181,7 @@
                     )
                     .clickable(
                         interactionSource = interactionSource,
-                        indication =
-                            ShortcutHelperIndication(interactionSource, interactionsConfig),
+                        indication = ShortcutHelperIndication(interactionsConfig),
                         enabled = enabled,
                         onClick = onClick,
                     ),
@@ -507,10 +506,8 @@
     }
 }
 
-data class ShortcutHelperIndication(
-    private val interactionSource: InteractionSource,
-    private val interactionsConfig: InteractionsConfig,
-) : IndicationNodeFactory {
+data class ShortcutHelperIndication(private val interactionsConfig: InteractionsConfig) :
+    IndicationNodeFactory {
     override fun create(interactionSource: InteractionSource): DelegatableNode {
         return ShortcutHelperInteractionsNode(interactionSource, interactionsConfig)
     }
@@ -529,3 +526,15 @@
     val hoverPadding: Dp = 0.dp,
     val pressedPadding: Dp = hoverPadding,
 )
+
+@Composable
+fun ProvideShortcutHelperIndication(
+    interactionsConfig: InteractionsConfig,
+    content: @Composable () -> Unit,
+) {
+    CompositionLocalProvider(
+        LocalIndication provides ShortcutHelperIndication(interactionsConfig)
+    ) {
+        content()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index ec6a17b..21c45c5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -51,6 +51,7 @@
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
@@ -78,6 +79,7 @@
 import androidx.compose.ui.semantics.customActions
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.round
 import androidx.compose.ui.util.fastRoundToInt
 import androidx.compose.ui.viewinterop.AndroidView
@@ -102,6 +104,8 @@
 import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyboard.shortcut.ui.composable.InteractionsConfig
+import com.android.systemui.keyboard.shortcut.ui.composable.ProvideShortcutHelperIndication
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.lifecycle.setSnapshotBinding
 import com.android.systemui.media.controls.ui.view.MediaHost
@@ -240,51 +244,54 @@
 
     @Composable
     private fun Content() {
-        PlatformTheme {
-            AnimatedVisibility(
-                visible = viewModel.isQsVisible,
-                modifier =
-                    Modifier.graphicsLayer { alpha = viewModel.viewAlpha }
-                        // Clipping before translation to match QSContainerImpl.onDraw
-                        .offset {
-                            IntOffset(x = 0, y = viewModel.viewTranslationY.fastRoundToInt())
-                        }
-                        .thenIf(notificationScrimClippingParams.isEnabled) {
-                            Modifier.notificationScrimClip {
-                                notificationScrimClippingParams.params
+        PlatformTheme(isDarkTheme = true) {
+            ProvideShortcutHelperIndication(interactionsConfig = interactionsConfig()) {
+                AnimatedVisibility(
+                    visible = viewModel.isQsVisible,
+                    modifier =
+                        Modifier.graphicsLayer { alpha = viewModel.viewAlpha }
+                            // Clipping before translation to match QSContainerImpl.onDraw
+                            .offset {
+                                IntOffset(x = 0, y = viewModel.viewTranslationY.fastRoundToInt())
                             }
+                            .thenIf(notificationScrimClippingParams.isEnabled) {
+                                Modifier.notificationScrimClip {
+                                    notificationScrimClippingParams.params
+                                }
+                            }
+                            // Disable touches in the whole composable while the mirror is showing.
+                            // While the mirror is showing, an ancestor of the ComposeView is made
+                            // alpha 0, but touches are still being captured by the composables.
+                            .gesturesDisabled(viewModel.showingMirror),
+                ) {
+                    val isEditing by
+                        viewModel.containerViewModel.editModeViewModel.isEditing
+                            .collectAsStateWithLifecycle()
+                    val animationSpecEditMode = tween<Float>(EDIT_MODE_TIME_MILLIS)
+                    AnimatedContent(
+                        targetState = isEditing,
+                        transitionSpec = {
+                            fadeIn(animationSpecEditMode) togetherWith
+                                fadeOut(animationSpecEditMode)
+                        },
+                        label = "EditModeAnimatedContent",
+                    ) { editing ->
+                        if (editing) {
+                            val qqsPadding = viewModel.qqsHeaderHeight
+                            EditMode(
+                                viewModel = viewModel.containerViewModel.editModeViewModel,
+                                modifier =
+                                    Modifier.fillMaxWidth()
+                                        .padding(top = { qqsPadding })
+                                        .padding(
+                                            horizontal = {
+                                                QuickSettingsShade.Dimensions.Padding.roundToPx()
+                                            }
+                                        ),
+                            )
+                        } else {
+                            CollapsableQuickSettingsSTL()
                         }
-                        // Disable touches in the whole composable while the mirror is showing.
-                        // While the mirror is showing, an ancestor of the ComposeView is made
-                        // alpha 0, but touches are still being captured by the composables.
-                        .gesturesDisabled(viewModel.showingMirror),
-            ) {
-                val isEditing by
-                    viewModel.containerViewModel.editModeViewModel.isEditing
-                        .collectAsStateWithLifecycle()
-                val animationSpecEditMode = tween<Float>(EDIT_MODE_TIME_MILLIS)
-                AnimatedContent(
-                    targetState = isEditing,
-                    transitionSpec = {
-                        fadeIn(animationSpecEditMode) togetherWith fadeOut(animationSpecEditMode)
-                    },
-                    label = "EditModeAnimatedContent",
-                ) { editing ->
-                    if (editing) {
-                        val qqsPadding = viewModel.qqsHeaderHeight
-                        EditMode(
-                            viewModel = viewModel.containerViewModel.editModeViewModel,
-                            modifier =
-                                Modifier.fillMaxWidth()
-                                    .padding(top = { qqsPadding })
-                                    .padding(
-                                        horizontal = {
-                                            QuickSettingsShade.Dimensions.Padding.roundToPx()
-                                        }
-                                    ),
-                        )
-                    } else {
-                        CollapsableQuickSettingsSTL()
                     }
                 }
             }
@@ -1090,3 +1097,14 @@
     const val qsScroll = "expanded_qs_scroll_view"
     const val qsFooterActions = "qs_footer_actions"
 }
+
+@Composable
+private fun interactionsConfig() =
+    InteractionsConfig(
+        hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
+        hoverOverlayAlpha = 0.11f,
+        pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
+        pressedOverlayAlpha = 0.15f,
+        // we are OK using this as our content is clipped and all corner radius are larger than this
+        surfaceCornerRadius = 28.dp,
+    )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
index 2efe500..4e094cc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
@@ -18,10 +18,13 @@
 
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.pager.HorizontalPager
 import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Edit
 import androidx.compose.material3.Icon
@@ -32,7 +35,6 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
@@ -41,6 +43,7 @@
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType
@@ -48,6 +51,7 @@
 import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.InterPageSpacing
 import com.android.systemui.qs.panels.ui.viewmodel.PaginatedGridViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.ui.compose.borderOnFocus
 import com.android.systemui.res.R
 import javax.inject.Inject
 
@@ -89,9 +93,24 @@
         }
 
         Column {
+            val contentPaddingValue =
+                if (pages.size > 1) {
+                    InterPageSpacing
+                } else {
+                    0.dp
+                }
+            val contentPadding = PaddingValues(horizontal = contentPaddingValue)
+
+            /* Use negative padding equal with value equal to content padding. That way, each page
+             * layout extends to the sides, but the content is as if there was no padding. That
+             * way, the clipping bounds of the HorizontalPager extend beyond the tiles in each page.
+             */
             HorizontalPager(
                 state = pagerState,
-                modifier = Modifier.sysuiResTag("qs_pager"),
+                modifier =
+                    Modifier.sysuiResTag("qs_pager")
+                        .padding(horizontal = { -contentPaddingValue.roundToPx() }),
+                contentPadding = contentPadding,
                 pageSpacing = if (pages.size > 1) InterPageSpacing else 0.dp,
                 beyondViewportPageCount = 1,
                 verticalAlignment = Alignment.Top,
@@ -114,7 +133,13 @@
                 CompositionLocalProvider(value = LocalContentColor provides Color.White) {
                     IconButton(
                         onClick = editModeStart,
-                        modifier = Modifier.align(Alignment.CenterEnd),
+                        shape = RoundedCornerShape(CornerSize(28.dp)),
+                        modifier =
+                            Modifier.align(Alignment.CenterEnd)
+                                .borderOnFocus(
+                                    color = MaterialTheme.colorScheme.secondary,
+                                    cornerSize = CornerSize(FooterHeight / 2),
+                                ),
                     ) {
                         Icon(
                             imageVector = Icons.Default.Edit,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 177a5be..0e09ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -50,7 +50,6 @@
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.Role
@@ -75,6 +74,7 @@
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.SideIconWidth
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel
 import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState
+import com.android.systemui.qs.ui.compose.borderOnFocus
 import com.android.systemui.res.R
 
 private const val TEST_TAG_TOGGLE = "qs_tile_toggle_target"
@@ -88,7 +88,7 @@
     colors: TileColors,
     squishiness: () -> Float,
     accessibilityUiState: AccessibilityUiState? = null,
-    iconShape: Shape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius),
+    iconShape: RoundedCornerShape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius),
     toggleClick: (() -> Unit)? = null,
     onLongClick: (() -> Unit)? = null,
 ) {
@@ -100,10 +100,12 @@
         val longPressLabel = longPressLabel().takeIf { onLongClick != null }
         val animatedBackgroundColor by
             animateColorAsState(colors.iconBackground, label = "QSTileDualTargetBackgroundColor")
+        val focusBorderColor = MaterialTheme.colorScheme.secondary
         Box(
             modifier =
                 Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClick != null) {
-                    Modifier.clip(iconShape)
+                    Modifier.borderOnFocus(color = focusBorderColor, iconShape.topEnd)
+                        .clip(iconShape)
                         .verticalSquish(squishiness)
                         .drawBehind { drawRect(animatedBackgroundColor) }
                         .combinedClickable(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index fe59c4d3..cb57c67 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -39,6 +39,7 @@
 import androidx.compose.foundation.lazy.grid.LazyGridState
 import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
 import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.shape.CornerSize
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
@@ -49,6 +50,7 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.platform.LocalConfiguration
@@ -59,6 +61,7 @@
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.stateDescription
 import androidx.compose.ui.semantics.toggleableState
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -82,6 +85,7 @@
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.toUiState
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.ui.compose.borderOnFocus
 import com.android.systemui.res.R
 import java.util.function.Supplier
 import kotlinx.coroutines.CoroutineScope
@@ -139,6 +143,7 @@
         hapticsViewModel = hapticsViewModel,
         modifier =
             modifier
+                .borderOnFocus(color = MaterialTheme.colorScheme.secondary, tileShape.topEnd)
                 .fillMaxWidth()
                 .bounceable(
                     bounceable = currentBounceableInfo.bounceable,
@@ -381,7 +386,7 @@
     }
 
     @Composable
-    fun animateIconShape(state: Int): Shape {
+    fun animateIconShape(state: Int): RoundedCornerShape {
         return animateShape(
             state = state,
             activeCornerRadius = ActiveIconCornerRadius,
@@ -390,7 +395,7 @@
     }
 
     @Composable
-    fun animateTileShape(state: Int): Shape {
+    fun animateTileShape(state: Int): RoundedCornerShape {
         return animateShape(
             state = state,
             activeCornerRadius = ActiveTileCornerRadius,
@@ -399,7 +404,7 @@
     }
 
     @Composable
-    fun animateShape(state: Int, activeCornerRadius: Dp, label: String): Shape {
+    fun animateShape(state: Int, activeCornerRadius: Dp, label: String): RoundedCornerShape {
         val animatedCornerRadius by
             animateDpAsState(
                 targetValue =
@@ -410,7 +415,15 @@
                     },
                 label = label,
             )
-        return RoundedCornerShape(animatedCornerRadius)
+
+        val corner = remember {
+            object : CornerSize {
+                override fun toPx(shapeSize: Size, density: Density): Float {
+                    return with(density) { animatedCornerRadius.toPx() }
+                }
+            }
+        }
+        return RoundedCornerShape(corner)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/compose/BorderOnFocus.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/compose/BorderOnFocus.kt
new file mode 100644
index 0000000..e6caa0d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/compose/BorderOnFocus.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2024 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.ui.compose
+
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusEventModifierNode
+import androidx.compose.ui.focus.FocusState
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+/**
+ * Provides a rounded rect border when the element is focused.
+ *
+ * This should be used for elements that are themselves rounded rects.
+ */
+fun Modifier.borderOnFocus(
+    color: Color,
+    cornerSize: CornerSize,
+    strokeWidth: Dp = 3.dp,
+    padding: Dp = 2.dp,
+) = this then BorderOnFocusElement(color, cornerSize, strokeWidth, padding)
+
+private class BorderOnFocusNode(
+    var color: Color,
+    var cornerSize: CornerSize,
+    var strokeWidth: Dp,
+    var padding: Dp,
+) : FocusEventModifierNode, DrawModifierNode, Modifier.Node() {
+
+    private var focused by mutableStateOf(false)
+
+    override fun onFocusEvent(focusState: FocusState) {
+        focused = focusState.isFocused
+    }
+
+    override fun ContentDrawScope.draw() {
+        drawContent()
+        val focusOutline = Rect(Offset.Zero, size).inflate(padding.toPx())
+        if (focused) {
+            drawRoundRect(
+                color = color,
+                topLeft = focusOutline.topLeft,
+                size = focusOutline.size,
+                cornerRadius = CornerRadius(cornerSize.toPx(focusOutline.size, this)),
+                style = Stroke(strokeWidth.toPx()),
+            )
+        }
+    }
+}
+
+private data class BorderOnFocusElement(
+    val color: Color,
+    val cornerSize: CornerSize,
+    val strokeWidth: Dp,
+    val padding: Dp,
+) : ModifierNodeElement<BorderOnFocusNode>() {
+    override fun create(): BorderOnFocusNode {
+        return BorderOnFocusNode(color, cornerSize, strokeWidth, padding)
+    }
+
+    override fun update(node: BorderOnFocusNode) {
+        node.color = color
+        node.cornerSize = cornerSize
+        node.strokeWidth = strokeWidth
+        node.padding = padding
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "borderOnFocus"
+        properties["color"] = color
+        properties["cornerSize"] = cornerSize
+        properties["strokeWidth"] = strokeWidth
+        properties["padding"] = padding
+    }
+}