Loading packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt 0 → 100644 +270 −0 Original line number Diff line number Diff line /* * 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.panels.ui.viewmodel import android.content.res.Resources import android.content.res.mainResources import android.service.quicksettings.Tile import android.widget.Button import android.widget.Switch import androidx.compose.ui.semantics.Role import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.qs.QSTile import com.android.systemui.res.R import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class TileUiStateTest : SysuiTestCase() { private val kosmos = testKosmos() private val resources: Resources get() = kosmos.mainResources @Test fun stateUnavailable_secondaryLabelNotmodified() { val testString = "TEST STRING" val state = QSTile.State().apply { state = Tile.STATE_UNAVAILABLE secondaryLabel = testString } val uiState = state.toUiState() assertThat(uiState.state).isEqualTo(Tile.STATE_UNAVAILABLE) } @Test fun accessibilityRole_switch() { val stateSwitch = QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name } val uiState = stateSwitch.toUiState() assertThat(uiState.accessibilityRole).isEqualTo(Role.Switch) } @Test fun accessibilityRole_button() { val stateButton = QSTile.State().apply { expandedAccessibilityClassName = Button::class.java.name } val uiState = stateButton.toUiState() assertThat(uiState.accessibilityRole).isEqualTo(Role.Button) } @Test fun accessibilityRole_switchWithSecondaryClick() { val stateSwitchWithSecondaryClick = QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name handlesSecondaryClick = true } val uiState = stateSwitchWithSecondaryClick.toUiState() assertThat(uiState.accessibilityRole).isEqualTo(Role.Button) } @Test fun switchInactive_secondaryLabelNotModified() { val testString = "TEST STRING" val state = QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name state = Tile.STATE_INACTIVE secondaryLabel = testString } val uiState = state.toUiState() assertThat(uiState.secondaryLabel).isEqualTo(testString) } @Test fun switchActive_secondaryLabelNotModified() { val testString = "TEST STRING" val state = QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name state = Tile.STATE_ACTIVE secondaryLabel = testString } val uiState = state.toUiState() assertThat(uiState.secondaryLabel).isEqualTo(testString) } @Test fun buttonInactive_secondaryLabelNotModifiedWhenEmpty() { val state = QSTile.State().apply { expandedAccessibilityClassName = Button::class.java.name state = Tile.STATE_INACTIVE secondaryLabel = "" } val uiState = state.toUiState() assertThat(uiState.secondaryLabel).isEmpty() } @Test fun buttonActive_secondaryLabelNotModifiedWhenEmpty() { val state = QSTile.State().apply { expandedAccessibilityClassName = Button::class.java.name state = Tile.STATE_ACTIVE secondaryLabel = "" } val uiState = state.toUiState() assertThat(uiState.secondaryLabel).isEmpty() } @Test fun buttonUnavailable_emptySecondaryLabel_default() { val state = QSTile.State().apply { expandedAccessibilityClassName = Button::class.java.name state = Tile.STATE_UNAVAILABLE secondaryLabel = "" } val uiState = state.toUiState() assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.tile_unavailable)) } @Test fun switchUnavailable_emptySecondaryLabel_defaultUnavailable() { val state = QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name state = Tile.STATE_UNAVAILABLE secondaryLabel = "" } val uiState = state.toUiState() assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.tile_unavailable)) } @Test fun switchInactive_emptySecondaryLabel_defaultOff() { val state = QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name state = Tile.STATE_INACTIVE secondaryLabel = "" } val uiState = state.toUiState() assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.switch_bar_off)) } @Test fun switchActive_emptySecondaryLabel_defaultOn() { val state = QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name state = Tile.STATE_ACTIVE secondaryLabel = "" } val uiState = state.toUiState() assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.switch_bar_on)) } @Test fun disabledByPolicy_inactive_appearsAsUnavailable() { val stateDisabledByPolicy = QSTile.State().apply { state = Tile.STATE_INACTIVE disabledByPolicy = true } val uiState = stateDisabledByPolicy.toUiState() assertThat(uiState.state).isEqualTo(Tile.STATE_UNAVAILABLE) } @Test fun disabledByPolicy_active_appearsAsUnavailable() { val stateDisabledByPolicy = QSTile.State().apply { state = Tile.STATE_ACTIVE disabledByPolicy = true } val uiState = stateDisabledByPolicy.toUiState() assertThat(uiState.state).isEqualTo(Tile.STATE_UNAVAILABLE) } @Test fun disabledByPolicy_clickLabel() { val stateDisabledByPolicy = QSTile.State().apply { state = Tile.STATE_INACTIVE disabledByPolicy = true } val uiState = stateDisabledByPolicy.toUiState() assertThat(uiState.accessibilityUiState.clickLabel) .isEqualTo( resources.getString( R.string.accessibility_tile_disabled_by_policy_action_description ) ) } @Test fun notDisabledByPolicy_clickLabel_null() { val stateDisabledByPolicy = QSTile.State().apply { state = Tile.STATE_INACTIVE disabledByPolicy = false } val uiState = stateDisabledByPolicy.toUiState() assertThat(uiState.accessibilityUiState.clickLabel).isNull() } @Test fun disabledByPolicy_unavailableInStateDescription() { val state = QSTile.State().apply { disabledByPolicy = true state = Tile.STATE_INACTIVE } val uiState = state.toUiState() assertThat(uiState.accessibilityUiState.stateDescription) .contains(resources.getString(R.string.tile_unavailable)) } private fun QSTile.State.toUiState() = toUiState(resources) } private val TileUiState.accessibilityRole: Role get() = accessibilityUiState.accessibilityRole packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +13 −12 Original line number Diff line number Diff line Loading @@ -158,7 +158,7 @@ constructor( override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? savedInstanceState: Bundle?, ): View { val context = inflater.context return ComposeView(context).apply { Loading @@ -181,7 +181,7 @@ constructor( notificationScrimClippingParams.bottom, notificationScrimClippingParams.radius, ) } }, ) { AnimatedContent(targetState = qsState) { when (it) { Loading Loading @@ -272,7 +272,7 @@ constructor( qsExpansionFraction: Float, panelExpansionFraction: Float, headerTranslation: Float, squishinessFraction: Float squishinessFraction: Float, ) { viewModel.qsExpansionValue = qsExpansionFraction viewModel.panelExpansionFractionValue = panelExpansionFraction Loading Loading @@ -318,12 +318,12 @@ constructor( override fun setTransitionToFullShadeProgress( isTransitioningToFullShade: Boolean, qsTransitionFraction: Float, qsSquishinessFraction: Float qsSquishinessFraction: Float, ) { super.setTransitionToFullShadeProgress( isTransitioningToFullShade, qsTransitionFraction, qsSquishinessFraction qsSquishinessFraction, ) } Loading @@ -334,7 +334,7 @@ constructor( bottom: Int, cornerRadius: Int, visible: Boolean, fullWidth: Boolean fullWidth: Boolean, ) { notificationScrimClippingParams.isEnabled = visible notificationScrimClippingParams.top = top Loading Loading @@ -402,7 +402,7 @@ constructor( launch { setListenerJob( heightListener, viewModel.containerViewModel.editModeViewModel.isEditing viewModel.containerViewModel.editModeViewModel.isEditing, ) { onQsHeightChanged() } Loading @@ -410,7 +410,7 @@ constructor( launch { setListenerJob( qsContainerController, viewModel.containerViewModel.editModeViewModel.isEditing viewModel.containerViewModel.editModeViewModel.isEditing, ) { setCustomizerShowing(it) } Loading @@ -422,6 +422,7 @@ constructor( @Composable private fun QuickQuickSettingsElement() { val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle() val bottomPadding = dimensionResource(id = R.dimen.qqs_layout_padding_bottom) DisposableEffect(Unit) { qqsVisible.value = true Loading @@ -441,7 +442,7 @@ constructor( ) } .onSizeChanged { size -> qqsHeight.value = size.height } .padding(top = { qqsPadding }) .padding(top = { qqsPadding }, bottom = { bottomPadding.roundToPx() }) ) { val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle() if (qsEnabled) { Loading @@ -450,7 +451,7 @@ constructor( modifier = Modifier.collapseExpandSemanticAction( stringResource(id = R.string.accessibility_quick_settings_expand) ) ), ) } } Loading Loading @@ -482,7 +483,7 @@ constructor( FooterActions( viewModel = viewModel.footerActionsViewModel, qsVisibilityLifecycleOwner = this@QSFragmentCompose, modifier = Modifier.sysuiResTag("qs_footer_actions") modifier = Modifier.sysuiResTag("qs_footer_actions"), ) } } Loading Loading @@ -562,7 +563,7 @@ private fun View.setBackPressedDispatcher() { private suspend inline fun <Listener : Any, Data> setListenerJob( listenerFlow: MutableStateFlow<Listener?>, dataFlow: Flow<Data>, crossinline onCollect: suspend Listener.(Data) -> Unit crossinline onCollect: suspend Listener.(Data) -> Unit, ) { coroutineScope { try { Loading packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +11 −9 Original line number Diff line number Diff line Loading @@ -22,18 +22,16 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.util.fastMap import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel @Composable fun QuickQuickSettings( viewModel: QuickQuickSettingsViewModel, modifier: Modifier = Modifier, ) { fun QuickQuickSettings(viewModel: QuickQuickSettingsViewModel, modifier: Modifier = Modifier) { val sizedTiles by viewModel.tileViewModels.collectAsStateWithLifecycle(initialValue = emptyList()) val tiles = sizedTiles.map { it.tile } val tiles = sizedTiles.fastMap { it.tile } DisposableEffect(tiles) { val token = Any() Loading @@ -44,14 +42,18 @@ fun QuickQuickSettings( TileLazyGrid( modifier = modifier.sysuiResTag("qqs_tile_layout"), columns = GridCells.Fixed(columns) columns = GridCells.Fixed(columns), ) { items( tiles.size, sizedTiles.size, key = { index -> sizedTiles[index].tile.spec.spec }, span = { index -> GridItemSpan(sizedTiles[index].width) } span = { index -> GridItemSpan(sizedTiles[index].width) }, ) { index -> Tile(tile = tiles[index], iconOnly = sizedTiles[index].isIcon, modifier = Modifier) Tile( tile = sizedTiles[index].tile, iconOnly = sizedTiles[index].isIcon, modifier = Modifier, ) } } } packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt +112 −46 File changed.Preview size limit exceeded, changes collapsed. Show changes packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt +78 −6 Original line number Diff line number Diff line Loading @@ -16,8 +16,16 @@ package com.android.systemui.qs.panels.ui.viewmodel import android.content.res.Resources import android.service.quicksettings.Tile import android.text.TextUtils import android.widget.Switch import androidx.compose.runtime.Immutable import androidx.compose.ui.semantics.Role import androidx.compose.ui.state.ToggleableState import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.tileimpl.SubtitleArrayMapping import com.android.systemui.res.R import java.util.function.Supplier @Immutable Loading @@ -27,14 +35,78 @@ data class TileUiState( val state: Int, val handlesSecondaryClick: Boolean, val icon: Supplier<QSTile.Icon?>, val accessibilityUiState: AccessibilityUiState, ) fun QSTile.State.toUiState(): TileUiState { data class AccessibilityUiState( val contentDescription: String, val stateDescription: String, val accessibilityRole: Role, val toggleableState: ToggleableState? = null, val clickLabel: String? = null, ) fun QSTile.State.toUiState(resources: Resources): TileUiState { val accessibilityRole = if (expandedAccessibilityClassName == Switch::class.java.name && !handlesSecondaryClick) { Role.Switch } else { Role.Button } // State handling and description val stateDescription = StringBuilder() val stateText = if (accessibilityRole == Role.Switch || state == Tile.STATE_UNAVAILABLE) { getStateText(resources) } else { "" } val secondaryLabel = getSecondaryLabel(stateText) if (!TextUtils.isEmpty(stateText)) { stateDescription.append(stateText) } if (disabledByPolicy && state != Tile.STATE_UNAVAILABLE) { stateDescription.append(", ") stateDescription.append(getUnavailableText(spec, resources)) } if ( !TextUtils.isEmpty(this.stateDescription) && !stateDescription.contains(this.stateDescription!!) ) { stateDescription.append(", ") stateDescription.append(this.stateDescription) } val toggleableState = if (accessibilityRole == Role.Switch || handlesSecondaryClick) { ToggleableState(state == Tile.STATE_ACTIVE) } else { null } return TileUiState( label?.toString() ?: "", secondaryLabel?.toString() ?: "", state, handlesSecondaryClick, icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null }, label = label?.toString() ?: "", secondaryLabel = secondaryLabel?.toString() ?: "", state = if (disabledByPolicy) Tile.STATE_UNAVAILABLE else state, handlesSecondaryClick = handlesSecondaryClick, icon = icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null }, AccessibilityUiState( contentDescription?.toString() ?: "", stateDescription.toString(), accessibilityRole, toggleableState, resources .getString(R.string.accessibility_tile_disabled_by_policy_action_description) .takeIf { disabledByPolicy }, ), ) } private fun QSTile.State.getStateText(resources: Resources): CharSequence { val arrayResId = SubtitleArrayMapping.getSubtitleId(spec) val array = resources.getStringArray(arrayResId) return array[state] } private fun getUnavailableText(spec: String?, resources: Resources): String { val arrayResId = SubtitleArrayMapping.getSubtitleId(spec) return resources.getStringArray(arrayResId)[Tile.STATE_UNAVAILABLE] } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt 0 → 100644 +270 −0 Original line number Diff line number Diff line /* * 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.panels.ui.viewmodel import android.content.res.Resources import android.content.res.mainResources import android.service.quicksettings.Tile import android.widget.Button import android.widget.Switch import androidx.compose.ui.semantics.Role import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.qs.QSTile import com.android.systemui.res.R import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class TileUiStateTest : SysuiTestCase() { private val kosmos = testKosmos() private val resources: Resources get() = kosmos.mainResources @Test fun stateUnavailable_secondaryLabelNotmodified() { val testString = "TEST STRING" val state = QSTile.State().apply { state = Tile.STATE_UNAVAILABLE secondaryLabel = testString } val uiState = state.toUiState() assertThat(uiState.state).isEqualTo(Tile.STATE_UNAVAILABLE) } @Test fun accessibilityRole_switch() { val stateSwitch = QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name } val uiState = stateSwitch.toUiState() assertThat(uiState.accessibilityRole).isEqualTo(Role.Switch) } @Test fun accessibilityRole_button() { val stateButton = QSTile.State().apply { expandedAccessibilityClassName = Button::class.java.name } val uiState = stateButton.toUiState() assertThat(uiState.accessibilityRole).isEqualTo(Role.Button) } @Test fun accessibilityRole_switchWithSecondaryClick() { val stateSwitchWithSecondaryClick = QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name handlesSecondaryClick = true } val uiState = stateSwitchWithSecondaryClick.toUiState() assertThat(uiState.accessibilityRole).isEqualTo(Role.Button) } @Test fun switchInactive_secondaryLabelNotModified() { val testString = "TEST STRING" val state = QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name state = Tile.STATE_INACTIVE secondaryLabel = testString } val uiState = state.toUiState() assertThat(uiState.secondaryLabel).isEqualTo(testString) } @Test fun switchActive_secondaryLabelNotModified() { val testString = "TEST STRING" val state = QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name state = Tile.STATE_ACTIVE secondaryLabel = testString } val uiState = state.toUiState() assertThat(uiState.secondaryLabel).isEqualTo(testString) } @Test fun buttonInactive_secondaryLabelNotModifiedWhenEmpty() { val state = QSTile.State().apply { expandedAccessibilityClassName = Button::class.java.name state = Tile.STATE_INACTIVE secondaryLabel = "" } val uiState = state.toUiState() assertThat(uiState.secondaryLabel).isEmpty() } @Test fun buttonActive_secondaryLabelNotModifiedWhenEmpty() { val state = QSTile.State().apply { expandedAccessibilityClassName = Button::class.java.name state = Tile.STATE_ACTIVE secondaryLabel = "" } val uiState = state.toUiState() assertThat(uiState.secondaryLabel).isEmpty() } @Test fun buttonUnavailable_emptySecondaryLabel_default() { val state = QSTile.State().apply { expandedAccessibilityClassName = Button::class.java.name state = Tile.STATE_UNAVAILABLE secondaryLabel = "" } val uiState = state.toUiState() assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.tile_unavailable)) } @Test fun switchUnavailable_emptySecondaryLabel_defaultUnavailable() { val state = QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name state = Tile.STATE_UNAVAILABLE secondaryLabel = "" } val uiState = state.toUiState() assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.tile_unavailable)) } @Test fun switchInactive_emptySecondaryLabel_defaultOff() { val state = QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name state = Tile.STATE_INACTIVE secondaryLabel = "" } val uiState = state.toUiState() assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.switch_bar_off)) } @Test fun switchActive_emptySecondaryLabel_defaultOn() { val state = QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name state = Tile.STATE_ACTIVE secondaryLabel = "" } val uiState = state.toUiState() assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.switch_bar_on)) } @Test fun disabledByPolicy_inactive_appearsAsUnavailable() { val stateDisabledByPolicy = QSTile.State().apply { state = Tile.STATE_INACTIVE disabledByPolicy = true } val uiState = stateDisabledByPolicy.toUiState() assertThat(uiState.state).isEqualTo(Tile.STATE_UNAVAILABLE) } @Test fun disabledByPolicy_active_appearsAsUnavailable() { val stateDisabledByPolicy = QSTile.State().apply { state = Tile.STATE_ACTIVE disabledByPolicy = true } val uiState = stateDisabledByPolicy.toUiState() assertThat(uiState.state).isEqualTo(Tile.STATE_UNAVAILABLE) } @Test fun disabledByPolicy_clickLabel() { val stateDisabledByPolicy = QSTile.State().apply { state = Tile.STATE_INACTIVE disabledByPolicy = true } val uiState = stateDisabledByPolicy.toUiState() assertThat(uiState.accessibilityUiState.clickLabel) .isEqualTo( resources.getString( R.string.accessibility_tile_disabled_by_policy_action_description ) ) } @Test fun notDisabledByPolicy_clickLabel_null() { val stateDisabledByPolicy = QSTile.State().apply { state = Tile.STATE_INACTIVE disabledByPolicy = false } val uiState = stateDisabledByPolicy.toUiState() assertThat(uiState.accessibilityUiState.clickLabel).isNull() } @Test fun disabledByPolicy_unavailableInStateDescription() { val state = QSTile.State().apply { disabledByPolicy = true state = Tile.STATE_INACTIVE } val uiState = state.toUiState() assertThat(uiState.accessibilityUiState.stateDescription) .contains(resources.getString(R.string.tile_unavailable)) } private fun QSTile.State.toUiState() = toUiState(resources) } private val TileUiState.accessibilityRole: Role get() = accessibilityUiState.accessibilityRole
packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +13 −12 Original line number Diff line number Diff line Loading @@ -158,7 +158,7 @@ constructor( override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? savedInstanceState: Bundle?, ): View { val context = inflater.context return ComposeView(context).apply { Loading @@ -181,7 +181,7 @@ constructor( notificationScrimClippingParams.bottom, notificationScrimClippingParams.radius, ) } }, ) { AnimatedContent(targetState = qsState) { when (it) { Loading Loading @@ -272,7 +272,7 @@ constructor( qsExpansionFraction: Float, panelExpansionFraction: Float, headerTranslation: Float, squishinessFraction: Float squishinessFraction: Float, ) { viewModel.qsExpansionValue = qsExpansionFraction viewModel.panelExpansionFractionValue = panelExpansionFraction Loading Loading @@ -318,12 +318,12 @@ constructor( override fun setTransitionToFullShadeProgress( isTransitioningToFullShade: Boolean, qsTransitionFraction: Float, qsSquishinessFraction: Float qsSquishinessFraction: Float, ) { super.setTransitionToFullShadeProgress( isTransitioningToFullShade, qsTransitionFraction, qsSquishinessFraction qsSquishinessFraction, ) } Loading @@ -334,7 +334,7 @@ constructor( bottom: Int, cornerRadius: Int, visible: Boolean, fullWidth: Boolean fullWidth: Boolean, ) { notificationScrimClippingParams.isEnabled = visible notificationScrimClippingParams.top = top Loading Loading @@ -402,7 +402,7 @@ constructor( launch { setListenerJob( heightListener, viewModel.containerViewModel.editModeViewModel.isEditing viewModel.containerViewModel.editModeViewModel.isEditing, ) { onQsHeightChanged() } Loading @@ -410,7 +410,7 @@ constructor( launch { setListenerJob( qsContainerController, viewModel.containerViewModel.editModeViewModel.isEditing viewModel.containerViewModel.editModeViewModel.isEditing, ) { setCustomizerShowing(it) } Loading @@ -422,6 +422,7 @@ constructor( @Composable private fun QuickQuickSettingsElement() { val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle() val bottomPadding = dimensionResource(id = R.dimen.qqs_layout_padding_bottom) DisposableEffect(Unit) { qqsVisible.value = true Loading @@ -441,7 +442,7 @@ constructor( ) } .onSizeChanged { size -> qqsHeight.value = size.height } .padding(top = { qqsPadding }) .padding(top = { qqsPadding }, bottom = { bottomPadding.roundToPx() }) ) { val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle() if (qsEnabled) { Loading @@ -450,7 +451,7 @@ constructor( modifier = Modifier.collapseExpandSemanticAction( stringResource(id = R.string.accessibility_quick_settings_expand) ) ), ) } } Loading Loading @@ -482,7 +483,7 @@ constructor( FooterActions( viewModel = viewModel.footerActionsViewModel, qsVisibilityLifecycleOwner = this@QSFragmentCompose, modifier = Modifier.sysuiResTag("qs_footer_actions") modifier = Modifier.sysuiResTag("qs_footer_actions"), ) } } Loading Loading @@ -562,7 +563,7 @@ private fun View.setBackPressedDispatcher() { private suspend inline fun <Listener : Any, Data> setListenerJob( listenerFlow: MutableStateFlow<Listener?>, dataFlow: Flow<Data>, crossinline onCollect: suspend Listener.(Data) -> Unit crossinline onCollect: suspend Listener.(Data) -> Unit, ) { coroutineScope { try { Loading
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +11 −9 Original line number Diff line number Diff line Loading @@ -22,18 +22,16 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.util.fastMap import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel @Composable fun QuickQuickSettings( viewModel: QuickQuickSettingsViewModel, modifier: Modifier = Modifier, ) { fun QuickQuickSettings(viewModel: QuickQuickSettingsViewModel, modifier: Modifier = Modifier) { val sizedTiles by viewModel.tileViewModels.collectAsStateWithLifecycle(initialValue = emptyList()) val tiles = sizedTiles.map { it.tile } val tiles = sizedTiles.fastMap { it.tile } DisposableEffect(tiles) { val token = Any() Loading @@ -44,14 +42,18 @@ fun QuickQuickSettings( TileLazyGrid( modifier = modifier.sysuiResTag("qqs_tile_layout"), columns = GridCells.Fixed(columns) columns = GridCells.Fixed(columns), ) { items( tiles.size, sizedTiles.size, key = { index -> sizedTiles[index].tile.spec.spec }, span = { index -> GridItemSpan(sizedTiles[index].width) } span = { index -> GridItemSpan(sizedTiles[index].width) }, ) { index -> Tile(tile = tiles[index], iconOnly = sizedTiles[index].isIcon, modifier = Modifier) Tile( tile = sizedTiles[index].tile, iconOnly = sizedTiles[index].isIcon, modifier = Modifier, ) } } }
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt +112 −46 File changed.Preview size limit exceeded, changes collapsed. Show changes
packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt +78 −6 Original line number Diff line number Diff line Loading @@ -16,8 +16,16 @@ package com.android.systemui.qs.panels.ui.viewmodel import android.content.res.Resources import android.service.quicksettings.Tile import android.text.TextUtils import android.widget.Switch import androidx.compose.runtime.Immutable import androidx.compose.ui.semantics.Role import androidx.compose.ui.state.ToggleableState import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.tileimpl.SubtitleArrayMapping import com.android.systemui.res.R import java.util.function.Supplier @Immutable Loading @@ -27,14 +35,78 @@ data class TileUiState( val state: Int, val handlesSecondaryClick: Boolean, val icon: Supplier<QSTile.Icon?>, val accessibilityUiState: AccessibilityUiState, ) fun QSTile.State.toUiState(): TileUiState { data class AccessibilityUiState( val contentDescription: String, val stateDescription: String, val accessibilityRole: Role, val toggleableState: ToggleableState? = null, val clickLabel: String? = null, ) fun QSTile.State.toUiState(resources: Resources): TileUiState { val accessibilityRole = if (expandedAccessibilityClassName == Switch::class.java.name && !handlesSecondaryClick) { Role.Switch } else { Role.Button } // State handling and description val stateDescription = StringBuilder() val stateText = if (accessibilityRole == Role.Switch || state == Tile.STATE_UNAVAILABLE) { getStateText(resources) } else { "" } val secondaryLabel = getSecondaryLabel(stateText) if (!TextUtils.isEmpty(stateText)) { stateDescription.append(stateText) } if (disabledByPolicy && state != Tile.STATE_UNAVAILABLE) { stateDescription.append(", ") stateDescription.append(getUnavailableText(spec, resources)) } if ( !TextUtils.isEmpty(this.stateDescription) && !stateDescription.contains(this.stateDescription!!) ) { stateDescription.append(", ") stateDescription.append(this.stateDescription) } val toggleableState = if (accessibilityRole == Role.Switch || handlesSecondaryClick) { ToggleableState(state == Tile.STATE_ACTIVE) } else { null } return TileUiState( label?.toString() ?: "", secondaryLabel?.toString() ?: "", state, handlesSecondaryClick, icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null }, label = label?.toString() ?: "", secondaryLabel = secondaryLabel?.toString() ?: "", state = if (disabledByPolicy) Tile.STATE_UNAVAILABLE else state, handlesSecondaryClick = handlesSecondaryClick, icon = icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null }, AccessibilityUiState( contentDescription?.toString() ?: "", stateDescription.toString(), accessibilityRole, toggleableState, resources .getString(R.string.accessibility_tile_disabled_by_policy_action_description) .takeIf { disabledByPolicy }, ), ) } private fun QSTile.State.getStateText(resources: Resources): CharSequence { val arrayResId = SubtitleArrayMapping.getSubtitleId(spec) val array = resources.getStringArray(arrayResId) return array[state] } private fun getUnavailableText(spec: String?, resources: Resources): String { val arrayResId = SubtitleArrayMapping.getSubtitleId(spec) return resources.getStringArray(arrayResId)[Tile.STATE_UNAVAILABLE] }