Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7a6faa3a authored by Anton Potapov's avatar Anton Potapov
Browse files

Add custom a11y stateDescription for the Spatial Audio button in the Volume Panel

Flag: com.android.systemui.volume_redesign
Fixes: 420403859
Test: manual on foldable. Check voice over announcement for the button
when a compatible headset is connected.

Change-Id: Id489a089c5bf5b46c7d178fee99b4e54bd961e16
parent 7cfe707c
Loading
Loading
Loading
Loading
+4 −3
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.Expandable
@@ -64,7 +65,6 @@ class ButtonComponent(
    override fun VolumePanelComposeScope.Content(modifier: Modifier) {
        val viewModelByState by viewModelFlow.collectAsStateWithLifecycle()
        val viewModel = viewModelByState ?: return
        val label = viewModel.label.toString()

        val screenWidth: Float =
            with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp.dp.toPx() }
@@ -81,7 +81,8 @@ class ButtonComponent(
                    modifier =
                        Modifier.fillMaxSize().padding(8.dp).semantics {
                            role = Role.Button
                            contentDescription = label
                            contentDescription = viewModel.label
                            viewModel.stateDescription?.let { stateDescription = it }
                        },
                    color =
                        if (viewModel.isActive) {
@@ -117,7 +118,7 @@ class ButtonComponent(
            }
            Text(
                modifier = Modifier.clearAndSetSemantics {}.basicMarquee(),
                text = label,
                text = viewModel.label,
                style = MaterialTheme.typography.labelMedium,
                maxLines = 2,
            )
+13 −9
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.semantics.toggleableState
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
@@ -59,7 +60,6 @@ class ToggleButtonComponent(
    override fun VolumePanelComposeScope.Content(modifier: Modifier) {
        val viewModelByState by viewModelFlow.collectAsStateWithLifecycle()
        val viewModel = viewModelByState ?: return
        val label = viewModel.label.toString()

        Column(
            modifier = modifier,
@@ -97,13 +97,17 @@ class ToggleButtonComponent(
                    modifier =
                        Modifier.fillMaxSize().padding(8.dp).semantics {
                            role = Role.Switch
                            if (viewModel.stateDescription == null) {
                                toggleableState =
                                    if (viewModel.isActive) {
                                        ToggleableState.On
                                    } else {
                                        ToggleableState.Off
                                    }
                            contentDescription = label
                            } else {
                                stateDescription = viewModel.stateDescription
                            }
                            contentDescription = viewModel.label
                        },
                    onClick = { onCheckedChange(!viewModel.isActive) },
                    shape = RoundedCornerShape(20.dp),
@@ -116,7 +120,7 @@ class ToggleButtonComponent(

            Text(
                modifier = Modifier.clearAndSetSemantics {}.basicMarquee(),
                text = label,
                text = viewModel.label,
                style = MaterialTheme.typography.labelMedium,
                maxLines = 2,
            )
+2 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import com.android.systemui.common.shared.model.Icon
/** Models base buttons appearance. */
data class ButtonViewModel(
    val icon: Icon,
    val label: CharSequence,
    val label: String,
    val isActive: Boolean = true,
    val stateDescription: String? = null,
)
+14 −5
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.spatial.ui.viewmodel

import android.content.Context
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.logging.UiEventLogger
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
@@ -35,7 +36,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import com.android.app.tracing.coroutines.launchTraced as launch

@VolumePanelScope
class SpatialAudioViewModel
@@ -58,6 +58,7 @@ constructor(
                        isChecked = isEnabled is SpatialAudioEnabledModel.SpatialAudioEnabled,
                        isHeadTrackingAvailable =
                            isAvailable is SpatialAudioAvailabilityModel.HeadTracking,
                        shouldUseLabelInStateDescription = true,
                    )
                    .copy(label = context.getString(R.string.volume_panel_spatial_audio_title))
            }
@@ -94,6 +95,7 @@ constructor(
                                isChecked = isEnabled == currentIsEnabled,
                                isHeadTrackingAvailable =
                                    isAvailable is SpatialAudioAvailabilityModel.HeadTracking,
                                shouldUseLabelInStateDescription = false,
                            )
                        SpatialAudioButtonViewModel(button = buttonViewModel, model = isEnabled)
                    }
@@ -112,7 +114,7 @@ constructor(
                else -> {
                    -1
                }
            }
            },
        )
        scope.launch { interactor.setEnabled(model) }
    }
@@ -120,10 +122,12 @@ constructor(
    private fun SpatialAudioEnabledModel.toViewModel(
        isChecked: Boolean,
        isHeadTrackingAvailable: Boolean,
        shouldUseLabelInStateDescription: Boolean,
    ): ButtonViewModel {
        // This method deliberately uses the same icon for the case when head tracking is disabled
        // to show a toggle button with a non-changing icon
        if (this is SpatialAudioEnabledModel.HeadTrackingEnabled) {
            val label = context.getString(R.string.volume_panel_spatial_audio_tracking)
            return ButtonViewModel(
                isActive = isChecked,
                icon =
@@ -132,11 +136,13 @@ constructor(
                    } else {
                        spatialSpeakerIcon
                    },
                label = context.getString(R.string.volume_panel_spatial_audio_tracking)
                label = label,
                stateDescription = label.takeIf { shouldUseLabelInStateDescription },
            )
        }

        if (this is SpatialAudioEnabledModel.SpatialAudioEnabled) {
            val label = context.getString(R.string.volume_panel_spatial_audio_fixed)
            return ButtonViewModel(
                isActive = isChecked,
                icon =
@@ -145,11 +151,13 @@ constructor(
                    } else {
                        spatialSpeakerIcon
                    },
                label = context.getString(R.string.volume_panel_spatial_audio_fixed)
                label = label,
                stateDescription = label.takeIf { shouldUseLabelInStateDescription },
            )
        }

        if (this is SpatialAudioEnabledModel.Disabled) {
            val label = context.getString(R.string.volume_panel_spatial_audio_off)
            return ButtonViewModel(
                isActive = isChecked,
                icon =
@@ -158,7 +166,8 @@ constructor(
                    } else {
                        spatialSpeakerIcon
                    },
                label = context.getString(R.string.volume_panel_spatial_audio_off)
                label = label,
                stateDescription = label.takeIf { shouldUseLabelInStateDescription },
            )
        }