Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt +124 −22 Original line number Diff line number Diff line Loading @@ -20,8 +20,6 @@ import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.TelephonyIcons.G import com.android.settingslib.mobile.TelephonyIcons.THREE_G Loading @@ -40,12 +38,15 @@ import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesF import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository.Companion.DEFAULT_NETWORK_NAME import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.mobile.ui.model.MobileContentDescription import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot Loading Loading @@ -255,59 +256,146 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun contentDescription_notInService_usesNoPhone() = testScope.runTest { var latest: ContentDescription? = null val job = underTest.contentDescription.onEach { latest = it }.launchIn(this) val latest by collectLastValue(underTest.contentDescription) repository.isInService.value = false assertThat((latest as ContentDescription.Resource).res) .isEqualTo(PHONE_SIGNAL_STRENGTH_NONE) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) } job.cancel() @Test fun contentDescription_includesNetworkName() = testScope.runTest { val latest by collectLastValue(underTest.contentDescription) repository.isInService.value = true repository.networkName.value = NetworkNameModel.SubscriptionDerived("Test Network Name") repository.numberOfLevels.value = 5 repository.setAllLevels(3) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular("Test Network Name", THREE_BARS)) } @Test fun contentDescription_inService_usesLevel() = testScope.runTest { var latest: ContentDescription? = null val job = underTest.contentDescription.onEach { latest = it }.launchIn(this) val latest by collectLastValue(underTest.contentDescription) repository.setAllLevels(2) assertThat((latest as ContentDescription.Resource).res) .isEqualTo(PHONE_SIGNAL_STRENGTH[2]) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS)) repository.setAllLevels(0) assertThat((latest as ContentDescription.Resource).res) .isEqualTo(PHONE_SIGNAL_STRENGTH[0]) job.cancel() assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) } @Test fun contentDescription_nonInflated_invalidLevelIsNull() = fun contentDescription_nonInflated_invalidLevelUsesNoSignalText() = testScope.runTest { val latest by collectLastValue(underTest.contentDescription) repository.inflateSignalStrength.value = false repository.setAllLevels(-1) assertThat(latest).isNull() assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) repository.setAllLevels(100) assertThat(latest).isNull() assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) } @Test fun contentDescription_nonInflated_levelStrings() = testScope.runTest { val latest by collectLastValue(underTest.contentDescription) repository.inflateSignalStrength.value = false repository.setAllLevels(0) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) repository.setAllLevels(1) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, ONE_BAR)) repository.setAllLevels(2) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS)) repository.setAllLevels(3) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, THREE_BARS)) repository.setAllLevels(4) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FULL_BARS)) } @Test fun contentDescription_inflated_invalidLevelIsNull() = fun contentDescription_inflated_invalidLevelUsesNoSignalText() = testScope.runTest { val latest by collectLastValue(underTest.contentDescription) repository.inflateSignalStrength.value = true repository.numberOfLevels.value = 6 repository.setAllLevels(-2) assertThat(latest).isNull() assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) repository.setAllLevels(100) assertThat(latest).isNull() assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) } @Test fun contentDescription_inflated_levelStrings() = testScope.runTest { val latest by collectLastValue(underTest.contentDescription) repository.inflateSignalStrength.value = true repository.numberOfLevels.value = 6 // Note that the _repo_ level is 1 lower than the reported level through the interactor repository.setAllLevels(0) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, ONE_BAR)) repository.setAllLevels(1) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS)) repository.setAllLevels(2) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, THREE_BARS)) repository.setAllLevels(3) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FOUR_BARS)) repository.setAllLevels(4) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FULL_BARS)) } @Test Loading @@ -323,7 +411,10 @@ class MobileIconViewModelTest : SysuiTestCase() { repository.setAllLevels(i) when (i) { -1, 5 -> assertWithMessage("Level $i is expected to be null").that(latest).isNull() 5 -> assertWithMessage("Level $i is expected to be 'no signal'") .that((latest as MobileContentDescription.Cellular).levelDescriptionRes) .isEqualTo(NO_SIGNAL) else -> assertWithMessage("Level $i is expected not to be null") .that(latest) Loading @@ -344,7 +435,10 @@ class MobileIconViewModelTest : SysuiTestCase() { repository.setAllLevels(i) when (i) { -2, 5 -> assertWithMessage("Level $i is expected to be null").that(latest).isNull() 5 -> assertWithMessage("Level $i is expected to be 'no signal'") .that((latest as MobileContentDescription.Cellular).levelDescriptionRes) .isEqualTo(NO_SIGNAL) else -> assertWithMessage("Level $i is not expected to be null") .that(latest) Loading Loading @@ -967,5 +1061,13 @@ class MobileIconViewModelTest : SysuiTestCase() { companion object { private const val SUB_1_ID = 1 // For convenience, just define these as constants private val NO_SIGNAL = R.string.accessibility_no_signal private val ONE_BAR = R.string.accessibility_one_bar private val TWO_BARS = R.string.accessibility_two_bars private val THREE_BARS = R.string.accessibility_three_bars private val FOUR_BARS = R.string.accessibility_four_bars private val FULL_BARS = R.string.accessibility_signal_full } } packages/SystemUI/res/values/strings.xml +15 −0 Original line number Diff line number Diff line Loading @@ -1950,6 +1950,21 @@ <!-- Text displayed indicating that the user might be able to use satellite SOS. --> <string name="satellite_emergency_only_carrier_text">Emergency calls or SOS</string> <!-- Content description skeleton. Input strings should be carrier name and signal bar description [CHAR LIMIT=NONE]--> <string name="accessibility_phone_string_format"><xliff:g id="carrier_name" example="Carrier Name">%1$s</xliff:g>, <xliff:g id="signal_strength_description" example="two bars">%2$s</xliff:g>.</string> <!-- Content description describing 0 signal bars. [CHAR LIMIT=NONE] --> <string name="accessibility_no_signal">no signal</string> <!-- Content description describing 1 signal bar. [CHAR LIMIT=NONE] --> <string name="accessibility_one_bar">one bar</string> <!-- Content description describing 2 signal bars. [CHAR LIMIT=NONE] --> <string name="accessibility_two_bars">two bars</string> <!-- Content description describing 3 signal bars. [CHAR LIMIT=NONE] --> <string name="accessibility_three_bars">three bars</string> <!-- Content description describing 4 signal bars. [CHAR LIMIT=NONE] --> <string name="accessibility_four_bars">four bars</string> <!-- Content description describing full signal bars. [CHAR LIMIT=NONE] --> <string name="accessibility_signal_full">signal full</string> <!-- Accessibility label for managed profile icon (not shown on screen) [CHAR LIMIT=NONE] --> <string name="accessibility_managed_profile">Work profile</string> Loading packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileContentDescriptionViewBinder.kt 0 → 100644 +30 −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.statusbar.pipeline.mobile.ui.binder import android.view.View import com.android.systemui.statusbar.pipeline.mobile.ui.model.MobileContentDescription object MobileContentDescriptionViewBinder { fun bind(contentDescription: MobileContentDescription?, view: View) { view.contentDescription = when (contentDescription) { null -> null else -> contentDescription.loadContentDescription(view.context) } } } packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt +5 −9 Original line number Diff line number Diff line Loading @@ -29,9 +29,9 @@ import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launchTraced as launch import com.android.settingslib.graph.SignalDrawable import com.android.systemui.Flags.statusBarStaticInoutIndicators import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.DarkIconDispatcher Loading @@ -48,12 +48,8 @@ import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarViewBin import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import com.android.app.tracing.coroutines.launchTraced as launch private data class Colors( @ColorInt val tint: Int, @ColorInt val contrast: Int, ) private data class Colors(@ColorInt val tint: Int, @ColorInt val contrast: Int) object MobileIconBinder { /** Binds the view to the view-model, continuing to update the former based on the latter */ Loading Loading @@ -87,7 +83,7 @@ object MobileIconBinder { MutableStateFlow( Colors( tint = DarkIconDispatcher.DEFAULT_ICON_TINT, contrast = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT contrast = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT, ) ) val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) Loading @@ -105,7 +101,7 @@ object MobileIconBinder { viewModel.verboseLogger?.logBinderReceivedVisibility( view, viewModel.subscriptionId, isVisible isVisible, ) view.isVisible = isVisible // [StatusIconContainer] can get out of sync sometimes. Make sure to Loading Loading @@ -152,7 +148,7 @@ object MobileIconBinder { launch { viewModel.contentDescription.distinctUntilChanged().collect { ContentDescriptionViewBinder.bind(it, view) MobileContentDescriptionViewBinder.bind(it, view) } } Loading packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/MobileContentDescription.kt 0 → 100644 +43 −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.statusbar.pipeline.mobile.ui.model import android.annotation.StringRes import android.content.Context import com.android.systemui.res.R sealed interface MobileContentDescription { fun loadContentDescription(context: Context): String /** * Content description for cellular parameterizes the [networkName] which comes from the system */ data class Cellular(val networkName: String, @StringRes val levelDescriptionRes: Int) : MobileContentDescription { override fun loadContentDescription(context: Context): String = context.getString( R.string.accessibility_phone_string_format, networkName, context.getString(levelDescriptionRes), ) } data class SatelliteContentDescription(@StringRes val resId: Int) : MobileContentDescription { override fun loadContentDescription(context: Context): String = context.getString(this.resId) } } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt +124 −22 Original line number Diff line number Diff line Loading @@ -20,8 +20,6 @@ import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.TelephonyIcons.G import com.android.settingslib.mobile.TelephonyIcons.THREE_G Loading @@ -40,12 +38,15 @@ import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesF import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository.Companion.DEFAULT_NETWORK_NAME import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.mobile.ui.model.MobileContentDescription import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot Loading Loading @@ -255,59 +256,146 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun contentDescription_notInService_usesNoPhone() = testScope.runTest { var latest: ContentDescription? = null val job = underTest.contentDescription.onEach { latest = it }.launchIn(this) val latest by collectLastValue(underTest.contentDescription) repository.isInService.value = false assertThat((latest as ContentDescription.Resource).res) .isEqualTo(PHONE_SIGNAL_STRENGTH_NONE) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) } job.cancel() @Test fun contentDescription_includesNetworkName() = testScope.runTest { val latest by collectLastValue(underTest.contentDescription) repository.isInService.value = true repository.networkName.value = NetworkNameModel.SubscriptionDerived("Test Network Name") repository.numberOfLevels.value = 5 repository.setAllLevels(3) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular("Test Network Name", THREE_BARS)) } @Test fun contentDescription_inService_usesLevel() = testScope.runTest { var latest: ContentDescription? = null val job = underTest.contentDescription.onEach { latest = it }.launchIn(this) val latest by collectLastValue(underTest.contentDescription) repository.setAllLevels(2) assertThat((latest as ContentDescription.Resource).res) .isEqualTo(PHONE_SIGNAL_STRENGTH[2]) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS)) repository.setAllLevels(0) assertThat((latest as ContentDescription.Resource).res) .isEqualTo(PHONE_SIGNAL_STRENGTH[0]) job.cancel() assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) } @Test fun contentDescription_nonInflated_invalidLevelIsNull() = fun contentDescription_nonInflated_invalidLevelUsesNoSignalText() = testScope.runTest { val latest by collectLastValue(underTest.contentDescription) repository.inflateSignalStrength.value = false repository.setAllLevels(-1) assertThat(latest).isNull() assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) repository.setAllLevels(100) assertThat(latest).isNull() assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) } @Test fun contentDescription_nonInflated_levelStrings() = testScope.runTest { val latest by collectLastValue(underTest.contentDescription) repository.inflateSignalStrength.value = false repository.setAllLevels(0) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) repository.setAllLevels(1) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, ONE_BAR)) repository.setAllLevels(2) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS)) repository.setAllLevels(3) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, THREE_BARS)) repository.setAllLevels(4) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FULL_BARS)) } @Test fun contentDescription_inflated_invalidLevelIsNull() = fun contentDescription_inflated_invalidLevelUsesNoSignalText() = testScope.runTest { val latest by collectLastValue(underTest.contentDescription) repository.inflateSignalStrength.value = true repository.numberOfLevels.value = 6 repository.setAllLevels(-2) assertThat(latest).isNull() assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) repository.setAllLevels(100) assertThat(latest).isNull() assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) } @Test fun contentDescription_inflated_levelStrings() = testScope.runTest { val latest by collectLastValue(underTest.contentDescription) repository.inflateSignalStrength.value = true repository.numberOfLevels.value = 6 // Note that the _repo_ level is 1 lower than the reported level through the interactor repository.setAllLevels(0) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, ONE_BAR)) repository.setAllLevels(1) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS)) repository.setAllLevels(2) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, THREE_BARS)) repository.setAllLevels(3) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FOUR_BARS)) repository.setAllLevels(4) assertThat(latest as MobileContentDescription.Cellular) .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FULL_BARS)) } @Test Loading @@ -323,7 +411,10 @@ class MobileIconViewModelTest : SysuiTestCase() { repository.setAllLevels(i) when (i) { -1, 5 -> assertWithMessage("Level $i is expected to be null").that(latest).isNull() 5 -> assertWithMessage("Level $i is expected to be 'no signal'") .that((latest as MobileContentDescription.Cellular).levelDescriptionRes) .isEqualTo(NO_SIGNAL) else -> assertWithMessage("Level $i is expected not to be null") .that(latest) Loading @@ -344,7 +435,10 @@ class MobileIconViewModelTest : SysuiTestCase() { repository.setAllLevels(i) when (i) { -2, 5 -> assertWithMessage("Level $i is expected to be null").that(latest).isNull() 5 -> assertWithMessage("Level $i is expected to be 'no signal'") .that((latest as MobileContentDescription.Cellular).levelDescriptionRes) .isEqualTo(NO_SIGNAL) else -> assertWithMessage("Level $i is not expected to be null") .that(latest) Loading Loading @@ -967,5 +1061,13 @@ class MobileIconViewModelTest : SysuiTestCase() { companion object { private const val SUB_1_ID = 1 // For convenience, just define these as constants private val NO_SIGNAL = R.string.accessibility_no_signal private val ONE_BAR = R.string.accessibility_one_bar private val TWO_BARS = R.string.accessibility_two_bars private val THREE_BARS = R.string.accessibility_three_bars private val FOUR_BARS = R.string.accessibility_four_bars private val FULL_BARS = R.string.accessibility_signal_full } }
packages/SystemUI/res/values/strings.xml +15 −0 Original line number Diff line number Diff line Loading @@ -1950,6 +1950,21 @@ <!-- Text displayed indicating that the user might be able to use satellite SOS. --> <string name="satellite_emergency_only_carrier_text">Emergency calls or SOS</string> <!-- Content description skeleton. Input strings should be carrier name and signal bar description [CHAR LIMIT=NONE]--> <string name="accessibility_phone_string_format"><xliff:g id="carrier_name" example="Carrier Name">%1$s</xliff:g>, <xliff:g id="signal_strength_description" example="two bars">%2$s</xliff:g>.</string> <!-- Content description describing 0 signal bars. [CHAR LIMIT=NONE] --> <string name="accessibility_no_signal">no signal</string> <!-- Content description describing 1 signal bar. [CHAR LIMIT=NONE] --> <string name="accessibility_one_bar">one bar</string> <!-- Content description describing 2 signal bars. [CHAR LIMIT=NONE] --> <string name="accessibility_two_bars">two bars</string> <!-- Content description describing 3 signal bars. [CHAR LIMIT=NONE] --> <string name="accessibility_three_bars">three bars</string> <!-- Content description describing 4 signal bars. [CHAR LIMIT=NONE] --> <string name="accessibility_four_bars">four bars</string> <!-- Content description describing full signal bars. [CHAR LIMIT=NONE] --> <string name="accessibility_signal_full">signal full</string> <!-- Accessibility label for managed profile icon (not shown on screen) [CHAR LIMIT=NONE] --> <string name="accessibility_managed_profile">Work profile</string> Loading
packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileContentDescriptionViewBinder.kt 0 → 100644 +30 −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.statusbar.pipeline.mobile.ui.binder import android.view.View import com.android.systemui.statusbar.pipeline.mobile.ui.model.MobileContentDescription object MobileContentDescriptionViewBinder { fun bind(contentDescription: MobileContentDescription?, view: View) { view.contentDescription = when (contentDescription) { null -> null else -> contentDescription.loadContentDescription(view.context) } } }
packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt +5 −9 Original line number Diff line number Diff line Loading @@ -29,9 +29,9 @@ import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launchTraced as launch import com.android.settingslib.graph.SignalDrawable import com.android.systemui.Flags.statusBarStaticInoutIndicators import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.DarkIconDispatcher Loading @@ -48,12 +48,8 @@ import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarViewBin import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import com.android.app.tracing.coroutines.launchTraced as launch private data class Colors( @ColorInt val tint: Int, @ColorInt val contrast: Int, ) private data class Colors(@ColorInt val tint: Int, @ColorInt val contrast: Int) object MobileIconBinder { /** Binds the view to the view-model, continuing to update the former based on the latter */ Loading Loading @@ -87,7 +83,7 @@ object MobileIconBinder { MutableStateFlow( Colors( tint = DarkIconDispatcher.DEFAULT_ICON_TINT, contrast = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT contrast = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT, ) ) val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) Loading @@ -105,7 +101,7 @@ object MobileIconBinder { viewModel.verboseLogger?.logBinderReceivedVisibility( view, viewModel.subscriptionId, isVisible isVisible, ) view.isVisible = isVisible // [StatusIconContainer] can get out of sync sometimes. Make sure to Loading Loading @@ -152,7 +148,7 @@ object MobileIconBinder { launch { viewModel.contentDescription.distinctUntilChanged().collect { ContentDescriptionViewBinder.bind(it, view) MobileContentDescriptionViewBinder.bind(it, view) } } Loading
packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/MobileContentDescription.kt 0 → 100644 +43 −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.statusbar.pipeline.mobile.ui.model import android.annotation.StringRes import android.content.Context import com.android.systemui.res.R sealed interface MobileContentDescription { fun loadContentDescription(context: Context): String /** * Content description for cellular parameterizes the [networkName] which comes from the system */ data class Cellular(val networkName: String, @StringRes val levelDescriptionRes: Int) : MobileContentDescription { override fun loadContentDescription(context: Context): String = context.getString( R.string.accessibility_phone_string_format, networkName, context.getString(levelDescriptionRes), ) } data class SatelliteContentDescription(@StringRes val resId: Int) : MobileContentDescription { override fun loadContentDescription(context: Context): String = context.getString(this.resId) } }