Add captioning button to the Volume Panel

Plus a few changes to the infra:
 - fix volume panel styling;
 - rework ComponentsLayout to accomodate a separated buttons collection;
 - move FakeCaptioningRepository from SettingsLib to SystemUI to use it
   in the tests;

Flag: aconfig new_volume_panel DISABLED
Test: atest CaptioningViewModelTest
Test: atest DefaultComponentsLayoutManagerTest
Test: atest VolumePanelViewModelTest
Fixes: 324241246
Change-Id: I5277e92fe01b9e0ee65755e19f3c65ff7a710ed2
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/FakeCaptioningRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/FakeCaptioningRepository.kt
deleted file mode 100644
index fd253c6..0000000
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/FakeCaptioningRepository.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.settingslib.view.accessibility.data.repository
-
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-class FakeCaptioningRepository : CaptioningRepository {
-
-    private val mutableIsSystemAudioCaptioningEnabled = MutableStateFlow(false)
-    override val isSystemAudioCaptioningEnabled: StateFlow<Boolean>
-        get() = mutableIsSystemAudioCaptioningEnabled.asStateFlow()
-
-    private val mutableIsSystemAudioCaptioningUiEnabled = MutableStateFlow(false)
-    override val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean>
-        get() = mutableIsSystemAudioCaptioningUiEnabled.asStateFlow()
-
-    override suspend fun setIsSystemAudioCaptioningEnabled(isEnabled: Boolean) {
-        mutableIsSystemAudioCaptioningEnabled.value = isEnabled
-    }
-
-    fun setIsSystemAudioCaptioningUiEnabled(isSystemAudioCaptioningUiEnabled: Boolean) {
-        mutableIsSystemAudioCaptioningUiEnabled.value = isSystemAudioCaptioningUiEnabled
-    }
-}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/captioning/CaptioningModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/captioning/CaptioningModule.kt
new file mode 100644
index 0000000..aeb5c5d
--- /dev/null
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/captioning/CaptioningModule.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.volume.panel.component.captioning
+
+import dagger.Module
+
+@Module interface CaptioningModule
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
new file mode 100644
index 0000000..228111d
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.volume.panel.component.button.ui.composable
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedIconToggleButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
+import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
+import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
+import kotlinx.coroutines.flow.StateFlow
+
+class ToggleButtonComponent(
+    private val viewModelFlow: StateFlow<ToggleButtonViewModel?>,
+    private val onCheckedChange: (isChecked: Boolean) -> Unit
+) : ComposeVolumePanelUiComponent {
+
+    @Composable
+    override fun VolumePanelComposeScope.Content(modifier: Modifier) {
+        val viewModelByState by viewModelFlow.collectAsState()
+        val viewModel = viewModelByState ?: return
+        Column(
+            modifier = modifier,
+            verticalArrangement = Arrangement.spacedBy(12.dp),
+            horizontalAlignment = Alignment.CenterHorizontally,
+        ) {
+            OutlinedIconToggleButton(
+                modifier = Modifier.height(64.dp).fillMaxWidth(),
+                checked = viewModel.isChecked,
+                onCheckedChange = onCheckedChange,
+                colors =
+                    IconButtonDefaults.outlinedIconToggleButtonColors(
+                        containerColor = MaterialTheme.colorScheme.surface,
+                        contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+                        checkedContainerColor = MaterialTheme.colorScheme.primaryContainer,
+                        checkedContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+                    ),
+                border = BorderStroke(8.dp, MaterialTheme.colorScheme.surface),
+            ) {
+                Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+            }
+            Text(
+                text = viewModel.label.toString(),
+                style = MaterialTheme.typography.labelMedium,
+                maxLines = 1,
+                overflow = TextOverflow.Ellipsis,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/captioning/CaptioningModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/captioning/CaptioningModule.kt
new file mode 100644
index 0000000..7d431d9
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/captioning/CaptioningModule.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.volume.panel.component.captioning
+
+import com.android.systemui.volume.panel.component.button.ui.composable.ToggleButtonComponent
+import com.android.systemui.volume.panel.component.captioning.domain.CaptioningAvailabilityCriteria
+import com.android.systemui.volume.panel.component.captioning.ui.viewmodel.CaptioningViewModel
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+/** Dagger module, that provides Captioning Volume Panel UI functionality. */
+@Module
+interface CaptioningModule {
+
+    @Binds
+    @IntoMap
+    @StringKey(VolumePanelComponents.CAPTIONING)
+    fun bindComponentAvailabilityCriteria(
+        criteria: CaptioningAvailabilityCriteria
+    ): ComponentAvailabilityCriteria
+
+    companion object {
+
+        @Provides
+        @IntoMap
+        @StringKey(VolumePanelComponents.CAPTIONING)
+        fun provideVolumePanelUiComponent(viewModel: CaptioningViewModel): VolumePanelUiComponent =
+            ToggleButtonComponent(
+                viewModel.buttonViewModel,
+                viewModel::setIsSystemAudioCaptioningEnabled,
+            )
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
index a7ec93f..e8d5966 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
@@ -19,26 +19,39 @@
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
-import androidx.compose.material3.Slider
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
-import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
 
 @Composable
 fun VolumePanelComposeScope.VerticalVolumePanelContent(
-    components: List<ComponentState>,
+    layout: ComponentsLayout,
     modifier: Modifier = Modifier,
 ) {
     Column(
         modifier = modifier,
         verticalArrangement = Arrangement.spacedBy(20.dp),
     ) {
-        Slider(0.5f, {})
-        for (component in components) {
+        for (component in layout.contentComponents) {
             AnimatedVisibility(component.isVisible) {
                 with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) }
             }
         }
+        if (layout.footerComponents.isNotEmpty()) {
+            Row(
+                modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+                horizontalArrangement = Arrangement.spacedBy(20.dp)
+            ) {
+                for (component in layout.footerComponents) {
+                    with(component.component as ComposeVolumePanelUiComponent) {
+                        Content(Modifier.weight(1f))
+                    }
+                }
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index 3487184..60d03fc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -21,14 +21,15 @@
 import androidx.compose.animation.core.MutableTransitionState
 import androidx.compose.animation.slideInVertically
 import androidx.compose.animation.slideOutVertically
+import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.navigationBarsPadding
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.statusBarsPadding
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Surface
@@ -37,7 +38,9 @@
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.res.dimensionResource
 import com.android.compose.theme.PlatformTheme
 import com.android.systemui.res.R
@@ -64,14 +67,17 @@
             }
         }
 
-        Column(
-            modifier =
-                modifier
-                    .fillMaxSize()
-                    .statusBarsPadding()
-                    .clickable(onClick = { viewModel.dismissPanel() }),
-            verticalArrangement = Arrangement.Bottom,
+        Box(
+            modifier = modifier.fillMaxSize(),
+            contentAlignment = Alignment.BottomCenter,
         ) {
+            Spacer(
+                modifier =
+                    Modifier.fillMaxSize()
+                        .alpha(0.32f)
+                        .background(MaterialTheme.colorScheme.scrim)
+                        .clickable(onClick = { viewModel.dismissPanel() })
+            )
             AnimatedVisibility(
                 visibleState = transitionState,
                 enter = slideInVertically { it },
@@ -80,7 +86,7 @@
                 val radius = dimensionResource(R.dimen.volume_panel_corner_radius)
                 Surface(
                     shape = RoundedCornerShape(topStart = radius, topEnd = radius),
-                    color = MaterialTheme.colorScheme.surfaceBright,
+                    color = MaterialTheme.colorScheme.surfaceContainer,
                 ) {
                     Column {
                         components?.let { componentsState ->
@@ -97,7 +103,7 @@
 private fun VolumePanelComposeScope.Components(state: ComponentsLayout) {
     if (orientation == Configuration.ORIENTATION_PORTRAIT) {
         VerticalVolumePanelContent(
-            components = state.contentComponents,
+            state,
             modifier = Modifier.padding(dimensionResource(R.dimen.volume_panel_content_padding)),
         )
     } else {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt
new file mode 100644
index 0000000..610195f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.volume.panel.component.captioning.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.view.accessibility.data.repository.captioningInteractor
+import com.android.systemui.view.accessibility.data.repository.captioningRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CaptioningViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private lateinit var underTest: CaptioningViewModel
+
+    @Before
+    fun setup() {
+        underTest =
+            with(kosmos) {
+                CaptioningViewModel(context, captioningInteractor, testScope.backgroundScope)
+            }
+    }
+
+    @Test
+    fun captioningDisabled_buttonViewModel_notChecked() {
+        with(kosmos) {
+            testScope.runTest {
+                captioningRepository.setIsSystemAudioCaptioningEnabled(false)
+
+                val buttonViewModel by collectLastValue(underTest.buttonViewModel)
+                runCurrent()
+
+                assertThat(buttonViewModel!!.isChecked).isFalse()
+            }
+        }
+    }
+
+    @Test
+    fun captioningDisabled_buttonViewModel_checked() {
+        with(kosmos) {
+            testScope.runTest {
+                captioningRepository.setIsSystemAudioCaptioningEnabled(true)
+
+                val buttonViewModel by collectLastValue(underTest.buttonViewModel)
+                runCurrent()
+
+                assertThat(buttonViewModel!!.isChecked).isTrue()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
index 35d9698..7c99360 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
@@ -33,43 +33,37 @@
 class DefaultComponentsLayoutManagerTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
-    private val underTest: ComponentsLayoutManager = DefaultComponentsLayoutManager()
+    private val underTest: ComponentsLayoutManager =
+        DefaultComponentsLayoutManager(
+            BOTTOM_BAR,
+            headerComponents = listOf(COMPONENT_1),
+            footerComponents = listOf(COMPONENT_2),
+        )
 
     @Test
-    fun bottomBar_isSet() {
+    fun correspondingComponents_areSet() {
         val bottomBarComponentState =
             ComponentState(BOTTOM_BAR, kosmos.mockVolumePanelUiComponent, false)
+        val component1 = ComponentState(COMPONENT_1, kosmos.mockVolumePanelUiComponent, false)
+        val component2 = ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false)
+        val component3 = ComponentState(COMPONENT_3, kosmos.mockVolumePanelUiComponent, false)
+        val component4 = ComponentState(COMPONENT_4, kosmos.mockVolumePanelUiComponent, false)
         val layout =
             underTest.layout(
                 VolumePanelState(0, false),
-                setOf(
-                    bottomBarComponentState,
-                    ComponentState(COMPONENT_1, kosmos.mockVolumePanelUiComponent, false),
-                    ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false),
-                )
+                setOf(bottomBarComponentState, component1, component2, component3, component4)
             )
 
         Truth.assertThat(layout.bottomBarComponent).isEqualTo(bottomBarComponentState)
-    }
-
-    @Test
-    fun componentsAreInOrder() {
-        val bottomBarComponentState =
-            ComponentState(BOTTOM_BAR, kosmos.mockVolumePanelUiComponent, false)
-        val component1State = ComponentState(COMPONENT_1, kosmos.mockVolumePanelUiComponent, false)
-        val component2State = ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false)
-        val layout =
-            underTest.layout(
-                VolumePanelState(0, false),
-                setOf(
-                    bottomBarComponentState,
-                    component1State,
-                    component2State,
-                )
-            )
-
-        Truth.assertThat(layout.contentComponents[0]).isEqualTo(component1State)
-        Truth.assertThat(layout.contentComponents[1]).isEqualTo(component2State)
+        Truth.assertThat(layout.headerComponents)
+            .containsExactlyElementsIn(listOf(component1))
+            .inOrder()
+        Truth.assertThat(layout.footerComponents)
+            .containsExactlyElementsIn(listOf(component2))
+            .inOrder()
+        Truth.assertThat(layout.contentComponents)
+            .containsExactlyElementsIn(listOf(component3, component4))
+            .inOrder()
     }
 
     @Test(expected = IllegalStateException::class)
@@ -89,5 +83,7 @@
         const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar"
         const val COMPONENT_1: VolumePanelComponentKey = "test_component:1"
         const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
+        const val COMPONENT_3: VolumePanelComponentKey = "test_component:3"
+        const val COMPONENT_4: VolumePanelComponentKey = "test_component:4"
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
index c4c9cc6..910f71e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.volume.panel.dagger.factory.KosmosVolumePanelComponentFactory
 import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider
 import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
-import com.android.systemui.volume.panel.ui.layout.FakeComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
 import com.android.systemui.volume.panel.unavailableCriteria
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,9 +45,7 @@
 class VolumePanelViewModelTest : SysuiTestCase() {
 
     private val kosmos =
-        testKosmos().apply {
-            componentsLayoutManager = FakeComponentsLayoutManager { it.key == BOTTOM_BAR }
-        }
+        testKosmos().apply { componentsLayoutManager = DefaultComponentsLayoutManager(BOTTOM_BAR) }
 
     private val testableResources = context.orCreateTestableResources
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 15688c5..6d765f4 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1437,6 +1437,8 @@
     <!-- Label for button to go to sound settings screen [CHAR_LIMIT=30] -->
     <string name="volume_panel_dialog_settings_button">Settings</string>
 
+    <string name="volume_panel_captioning_title">Live Caption</string>
+
     <!-- Title for notification after audio lowers -->
     <string name="csd_lowered_title" product="default">Volume lowered to safer level</string>
     <!-- Message shown in notification after system lowers audio -->
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
new file mode 100644
index 0000000..8ab563a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.volume.panel.component.button.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+
+data class ToggleButtonViewModel(
+    val isChecked: Boolean,
+    val icon: Icon,
+    val label: CharSequence,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
new file mode 100644
index 0000000..aab825f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.volume.panel.component.captioning.domain
+
+import com.android.settingslib.view.accessibility.domain.interactor.CaptioningInteractor
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@VolumePanelScope
+class CaptioningAvailabilityCriteria
+@Inject
+constructor(private val captioningInteractor: CaptioningInteractor) :
+    ComponentAvailabilityCriteria {
+
+    override fun isAvailable(): Flow<Boolean> =
+        captioningInteractor.isSystemAudioCaptioningUiEnabled
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt
new file mode 100644
index 0000000..92f8f22
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.volume.panel.component.captioning.ui.viewmodel
+
+import android.content.Context
+import com.android.settingslib.view.accessibility.domain.interactor.CaptioningInteractor
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Volume Panel captioning UI model. */
+@VolumePanelScope
+class CaptioningViewModel
+@Inject
+constructor(
+    private val context: Context,
+    private val captioningInteractor: CaptioningInteractor,
+    @VolumePanelScope private val coroutineScope: CoroutineScope,
+) {
+
+    val buttonViewModel: StateFlow<ToggleButtonViewModel?> =
+        captioningInteractor.isSystemAudioCaptioningEnabled
+            .map { isEnabled ->
+                ToggleButtonViewModel(
+                    isChecked = isEnabled,
+                    icon =
+                        Icon.Resource(
+                            if (isEnabled) R.drawable.ic_volume_odi_captions
+                            else R.drawable.ic_volume_odi_captions_disabled,
+                            null
+                        ),
+                    label = context.getString(R.string.volume_panel_captioning_title),
+                )
+            }
+            .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+
+    fun setIsSystemAudioCaptioningEnabled(enabled: Boolean) {
+        coroutineScope.launch { captioningInteractor.setIsSystemAudioCaptioningEnabled(enabled) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
index 1a4174a..842c323 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
@@ -21,4 +21,5 @@
 object VolumePanelComponents {
 
     const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar"
+    const val CAPTIONING: VolumePanelComponentKey = "captioning"
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
index 0f19e9f..841daf8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.panel.dagger
 
 import com.android.systemui.volume.panel.component.bottombar.BottomBarModule
+import com.android.systemui.volume.panel.component.captioning.CaptioningModule
 import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import com.android.systemui.volume.panel.domain.DomainModule
@@ -44,6 +45,7 @@
             UiModule::class,
             // Components modules
             BottomBarModule::class,
+            CaptioningModule::class,
         ]
 )
 interface VolumePanelComponent {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
index f785eb7..defa92d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
@@ -50,6 +50,7 @@
         @VolumePanelScope
         fun provideEnabledComponents(): Collection<VolumePanelComponentKey> {
             return setOf(
+                VolumePanelComponents.CAPTIONING,
                 VolumePanelComponents.BOTTOM_BAR,
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/BottomBar.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/BottomBar.kt
new file mode 100644
index 0000000..3ea0eac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/BottomBar.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.volume.panel.ui
+
+import javax.inject.Qualifier
+
+/**
+ * Bottom bar component key.
+ *
+ * @see com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+ */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class BottomBar()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/FooterComponents.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/FooterComponents.kt
new file mode 100644
index 0000000..12a505d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/FooterComponents.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.volume.panel.ui
+
+import javax.inject.Qualifier
+
+/**
+ * [com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent] collection to be shown
+ * below the content.
+ *
+ * @see com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+ */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class FooterComponents()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/HeaderComponents.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/HeaderComponents.kt
new file mode 100644
index 0000000..95be84a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/HeaderComponents.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.volume.panel.ui
+
+import javax.inject.Qualifier
+
+/**
+ * [com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent] collection to be shown
+ * above the content.
+ *
+ * @see com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+ */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class HeaderComponents()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
index 1346c54..a3f052d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
@@ -16,10 +16,14 @@
 
 package com.android.systemui.volume.panel.ui
 
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
 import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
 import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 
 /** UI layer bindings module. */
 @Module
@@ -27,4 +31,26 @@
 
     @Binds
     fun bindComponentsLayoutManager(impl: DefaultComponentsLayoutManager): ComponentsLayoutManager
+
+    companion object {
+
+        @Provides
+        @VolumePanelScope
+        @HeaderComponents
+        fun provideHeaderComponents(): Collection<VolumePanelComponentKey> = setOf()
+
+        @Provides
+        @VolumePanelScope
+        @FooterComponents
+        fun provideFooterComponents(): Collection<VolumePanelComponentKey> {
+            return setOf(
+                VolumePanelComponents.CAPTIONING,
+            )
+        }
+
+        @Provides
+        @VolumePanelScope
+        @BottomBar
+        fun provideBottomBarKey(): VolumePanelComponentKey = VolumePanelComponents.BOTTOM_BAR
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
index 25a95d8..1c51236 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
@@ -20,6 +20,12 @@
 
 /** Represents components grouping into the layout. */
 data class ComponentsLayout(
+    /** Top section of the Volume Panel. It's typically shown above the [contentComponents]. */
+    val headerComponents: List<ComponentState>,
+    /** Main Volume Panel content. */
     val contentComponents: List<ComponentState>,
+    /** Bottom section of the Volume Panel. It's typically shown below the [contentComponents]. */
+    val footerComponents: List<ComponentState>,
+    /** This is a separated entity that is always visible on the bottom of the Volume Panel. */
     val bottomBarComponent: ComponentState,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
index ff485c2..7fd9c8a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
@@ -16,27 +16,47 @@
 
 package com.android.systemui.volume.panel.ui.layout
 
-import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.ui.BottomBar
+import com.android.systemui.volume.panel.ui.FooterComponents
+import com.android.systemui.volume.panel.ui.HeaderComponents
 import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
 import javax.inject.Inject
 
 @VolumePanelScope
-class DefaultComponentsLayoutManager @Inject constructor() : ComponentsLayoutManager {
+class DefaultComponentsLayoutManager
+@Inject
+constructor(
+    @BottomBar private val bottomBar: VolumePanelComponentKey,
+    @HeaderComponents
+    private val headerComponents: Collection<VolumePanelComponentKey> = emptyList(),
+    @FooterComponents
+    private val footerComponents: Collection<VolumePanelComponentKey> = emptyList(),
+) : ComponentsLayoutManager {
 
     override fun layout(
         volumePanelState: VolumePanelState,
         components: Collection<ComponentState>
     ): ComponentsLayout {
-        val bottomBarKey = VolumePanelComponents.BOTTOM_BAR
+        val contentComponents =
+            components.filter {
+                !headerComponents.contains(it.key) &&
+                    !footerComponents.contains(it.key) &&
+                    it.key != bottomBar
+            }
+        val headerComponents = components.filter { headerComponents.contains(it.key) }
+        val footerComponents = components.filter { footerComponents.contains(it.key) }
         return ComponentsLayout(
-            components.filter { it.key != bottomBarKey }.sortedBy { it.key },
-            components.find { it.key == bottomBarKey }
-                ?: error(
-                    "VolumePanelComponents.BOTTOM_BAR must be present in the default " +
-                        "components layout."
-                )
+            headerComponents = headerComponents.sortedBy { it.key },
+            contentComponents = contentComponents.sortedBy { it.key },
+            footerComponents = footerComponents.sortedBy { it.key },
+            bottomBarComponent = components.find { it.key == bottomBar }
+                    ?: error(
+                        "VolumePanelComponents.BOTTOM_BAR must be present in the default " +
+                            "components layout."
+                    )
         )
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/view/accessibility/data/repository/CaptioningKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/view/accessibility/data/repository/CaptioningKosmos.kt
new file mode 100644
index 0000000..0e978f2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/view/accessibility/data/repository/CaptioningKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.view.accessibility.data.repository
+
+import com.android.settingslib.view.accessibility.domain.interactor.CaptioningInteractor
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.captioningRepository by Kosmos.Fixture { FakeCaptioningRepository() }
+val Kosmos.captioningInteractor by Kosmos.Fixture { CaptioningInteractor(captioningRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/view/accessibility/data/repository/FakeCaptioningRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/view/accessibility/data/repository/FakeCaptioningRepository.kt
new file mode 100644
index 0000000..663aaf2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/view/accessibility/data/repository/FakeCaptioningRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.view.accessibility.data.repository
+
+import com.android.settingslib.view.accessibility.data.repository.CaptioningRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeCaptioningRepository : CaptioningRepository {
+
+    private val mutableIsSystemAudioCaptioningEnabled = MutableStateFlow(false)
+    override val isSystemAudioCaptioningEnabled: StateFlow<Boolean>
+        get() = mutableIsSystemAudioCaptioningEnabled.asStateFlow()
+
+    private val mutableIsSystemAudioCaptioningUiEnabled = MutableStateFlow(false)
+    override val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean>
+        get() = mutableIsSystemAudioCaptioningUiEnabled.asStateFlow()
+
+    override suspend fun setIsSystemAudioCaptioningEnabled(isEnabled: Boolean) {
+        mutableIsSystemAudioCaptioningEnabled.value = isEnabled
+    }
+
+    fun setIsSystemAudioCaptioningUiEnabled(isSystemAudioCaptioningUiEnabled: Boolean) {
+        mutableIsSystemAudioCaptioningUiEnabled.value = isSystemAudioCaptioningUiEnabled
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/FakeComponentsLayoutManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/FakeComponentsLayoutManager.kt
deleted file mode 100644
index 655d8f7..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/FakeComponentsLayoutManager.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.volume.panel.ui.layout
-
-import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
-import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
-
-class FakeComponentsLayoutManager(
-    private val isBottomBar: (components: ComponentState) -> Boolean
-) : ComponentsLayoutManager {
-
-    override fun layout(
-        volumePanelState: VolumePanelState,
-        components: Collection<ComponentState>
-    ): ComponentsLayout {
-        return ComponentsLayout(
-            components
-                .filter { componentState -> !isBottomBar(componentState) }
-                .sortedBy { it.key },
-            components.find(isBottomBar)!!,
-        )
-    }
-}