diff options
| author | 2024-01-23 22:18:18 +0000 | |
|---|---|---|
| committer | 2024-01-23 22:18:18 +0000 | |
| commit | 41db632a8f82c66f06fecd298f81d68691d11732 (patch) | |
| tree | ee8ac34a38fbbb9f0daa4a3cf1587d29a5397fea | |
| parent | 9bbc5ba475110013dda6edb93a189e929d8eac02 (diff) | |
| parent | 7b7b46bc14c65bd75a24af393678ad85bc9dc827 (diff) | |
Merge "[Sat] Carrier-based view model + signal" into main
12 files changed, 420 insertions, 104 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt index fe49c0791370..6b303263d4b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.content.Context +import com.android.internal.telephony.flags.Flags import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.graph.SignalDrawable import com.android.settingslib.mobile.MobileIconCarrierIdOverrides @@ -32,14 +33,18 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIc import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.DefaultIcon import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.OverriddenIcon import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel +import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -79,6 +84,9 @@ interface MobileIconInteractor { /** Whether or not to show the slice attribution */ val showSliceAttribution: StateFlow<Boolean> + /** True if this connection is satellite-based */ + val isNonTerrestrial: StateFlow<Boolean> + /** * Provider name for this network connection. The name can be one of 3 values: * 1. The default network name, if one is configured @@ -244,6 +252,13 @@ class MobileIconInteractorImpl( override val showSliceAttribution: StateFlow<Boolean> = connectionRepository.hasPrioritizedNetworkCapabilities + override val isNonTerrestrial: StateFlow<Boolean> = + if (Flags.carrierEnabledSatelliteFlag()) { + connectionRepository.isNonTerrestrial + } else { + MutableStateFlow(false).asStateFlow() + } + override val isRoaming: StateFlow<Boolean> = combine( connectionRepository.carrierNetworkChangeActive, @@ -313,26 +328,45 @@ class MobileIconInteractorImpl( } .stateIn(scope, SharingStarted.WhileSubscribed(), 0) - override val signalLevelIcon: StateFlow<SignalIconModel> = run { - val initial = - SignalIconModel( - level = shownLevel.value, - numberOfLevels = numberOfLevels.value, - showExclamationMark = showExclamationMark.value, - carrierNetworkChange = carrierNetworkChangeActive.value, - ) + private val cellularIcon: Flow<SignalIconModel.Cellular> = combine( + shownLevel, + numberOfLevels, + showExclamationMark, + carrierNetworkChangeActive, + ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange -> + SignalIconModel.Cellular( shownLevel, numberOfLevels, showExclamationMark, - carrierNetworkChangeActive, - ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange -> - SignalIconModel( - shownLevel, - numberOfLevels, - showExclamationMark, - carrierNetworkChange, - ) + carrierNetworkChange, + ) + } + + private val satelliteIcon: Flow<SignalIconModel.Satellite> = + shownLevel.map { + SignalIconModel.Satellite( + level = it, + icon = SatelliteIconModel.fromSignalStrength(it) + ?: SatelliteIconModel.fromSignalStrength(0)!! + ) + } + + override val signalLevelIcon: StateFlow<SignalIconModel> = run { + val initial = + SignalIconModel.Cellular( + shownLevel.value, + numberOfLevels.value, + showExclamationMark.value, + carrierNetworkChangeActive.value, + ) + isNonTerrestrial + .flatMapLatest { ntn -> + if (ntn) { + satelliteIcon + } else { + cellularIcon + } } .distinctUntilChanged() .logDiffsForTable( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt index e58f08183157..d6b8fd4fec87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt @@ -17,51 +17,94 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.model import com.android.settingslib.graph.SignalDrawable +import com.android.systemui.common.shared.model.Icon import com.android.systemui.log.table.Diffable import com.android.systemui.log.table.TableRowLogger -/** A model that will be consumed by [SignalDrawable] to show the mobile triangle icon. */ -data class SignalIconModel( - val level: Int, - val numberOfLevels: Int, - val showExclamationMark: Boolean, - val carrierNetworkChange: Boolean, -) : Diffable<SignalIconModel> { - // TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes? +sealed interface SignalIconModel : Diffable<SignalIconModel> { + val level: Int + override fun logDiffs(prevVal: SignalIconModel, row: TableRowLogger) { - if (prevVal.level != level) { - row.logChange(COL_LEVEL, level) + logPartial(prevVal, row) + } + + override fun logFull(row: TableRowLogger) = logFully(row) + + fun logFully(row: TableRowLogger) + + fun logPartial(prevVal: SignalIconModel, row: TableRowLogger) + + /** A model that will be consumed by [SignalDrawable] to show the mobile triangle icon. */ + data class Cellular( + override val level: Int, + val numberOfLevels: Int, + val showExclamationMark: Boolean, + val carrierNetworkChange: Boolean, + ) : SignalIconModel { + override fun logPartial(prevVal: SignalIconModel, row: TableRowLogger) { + if (prevVal !is Cellular) { + logFull(row) + } else { + if (prevVal.level != level) { + row.logChange(COL_LEVEL, level) + } + if (prevVal.numberOfLevels != numberOfLevels) { + row.logChange(COL_NUM_LEVELS, numberOfLevels) + } + if (prevVal.showExclamationMark != showExclamationMark) { + row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark) + } + if (prevVal.carrierNetworkChange != carrierNetworkChange) { + row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange) + } + } } - if (prevVal.numberOfLevels != numberOfLevels) { + + override fun logFully(row: TableRowLogger) { + row.logChange(COL_TYPE, "c") + row.logChange(COL_LEVEL, level) row.logChange(COL_NUM_LEVELS, numberOfLevels) - } - if (prevVal.showExclamationMark != showExclamationMark) { row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark) - } - if (prevVal.carrierNetworkChange != carrierNetworkChange) { row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange) } - } - override fun logFull(row: TableRowLogger) { - row.logChange(COL_LEVEL, level) - row.logChange(COL_NUM_LEVELS, numberOfLevels) - row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark) - row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange) + /** Convert this model to an [Int] consumable by [SignalDrawable]. */ + fun toSignalDrawableState(): Int = + if (carrierNetworkChange) { + SignalDrawable.getCarrierChangeState(numberOfLevels) + } else { + SignalDrawable.getState(level, numberOfLevels, showExclamationMark) + } } - /** Convert this model to an [Int] consumable by [SignalDrawable]. */ - fun toSignalDrawableState(): Int = - if (carrierNetworkChange) { - SignalDrawable.getCarrierChangeState(numberOfLevels) - } else { - SignalDrawable.getState(level, numberOfLevels, showExclamationMark) + /** + * For non-terrestrial networks, we can use a resource-backed icon instead of the + * [SignalDrawable]-backed version above + */ + data class Satellite( + override val level: Int, + val icon: Icon.Resource, + ) : SignalIconModel { + override fun logPartial(prevVal: SignalIconModel, row: TableRowLogger) { + if (prevVal !is Satellite) { + logFull(row) + } else { + if (prevVal.level != level) row.logChange(COL_LEVEL, level) + } } + override fun logFully(row: TableRowLogger) { + row.logChange("numLevels", "HELLO") + row.logChange(COL_TYPE, "s") + row.logChange(COL_LEVEL, level) + } + } + companion object { private const val COL_LEVEL = "level" private const val COL_NUM_LEVELS = "numLevels" private const val COL_SHOW_EXCLAMATION = "showExclamation" private const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChange" + private const val COL_TYPE = "type" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt index a1a5370819f8..43cb38ff0108 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt @@ -59,7 +59,7 @@ constructor( str1 = parentView.getIdForLogging() int1 = subId int2 = icon.level - bool1 = icon.showExclamationMark + bool1 = if (icon is SignalIconModel.Cellular) icon.showExclamationMark else false }, { "Binder[subId=$int1, viewId=$str1] received new signal icon: " + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt index 5475528152ed..a0c5618041f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt @@ -38,6 +38,7 @@ import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding @@ -70,7 +71,7 @@ object MobileIconBinder { val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type) val networkTypeContainer = view.requireViewById<FrameLayout>(R.id.mobile_type_container) val iconView = view.requireViewById<ImageView>(R.id.mobile_signal) - val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) } + val mobileDrawable = SignalDrawable(view.context) val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming) val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space) val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot) @@ -138,7 +139,12 @@ object MobileIconBinder { viewModel.subscriptionId, icon, ) - mobileDrawable.level = icon.toSignalDrawableState() + if (icon is SignalIconModel.Cellular) { + iconView.setImageDrawable(mobileDrawable) + mobileDrawable.level = icon.toSignalDrawableState() + } else if (icon is SignalIconModel.Satellite) { + IconViewBinder.bind(icon.icon, iconView) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt index 60c662da6aec..eda5c44b5c2f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt @@ -33,12 +33,15 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityMod import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn /** Common interface for all of the location-based mobile icon view models. */ @@ -71,7 +74,6 @@ interface MobileIconViewModelCommon { * model gets the exact same information, as well as allows us to log that unified state only once * per icon. */ -@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) class MobileIconViewModel( override val subscriptionId: Int, @@ -81,6 +83,100 @@ class MobileIconViewModel( flags: FeatureFlagsClassic, scope: CoroutineScope, ) : MobileIconViewModelCommon { + private val cellProvider by lazy { + CellularIconViewModel( + subscriptionId, + iconInteractor, + airplaneModeInteractor, + constants, + flags, + scope, + ) + } + + private val satelliteProvider by lazy { + CarrierBasedSatelliteViewModelImpl( + subscriptionId, + iconInteractor, + ) + } + + /** + * Similar to repository switching, this allows us to split up the logic of satellite/cellular + * states, since they are different by nature + */ + private val vmProvider: Flow<MobileIconViewModelCommon> = + iconInteractor.isNonTerrestrial + .mapLatest { nonTerrestrial -> + if (nonTerrestrial) { + satelliteProvider + } else { + cellProvider + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), cellProvider) + + override val isVisible: StateFlow<Boolean> = + vmProvider + .flatMapLatest { it.isVisible } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val icon: Flow<SignalIconModel> = vmProvider.flatMapLatest { it.icon } + + override val contentDescription: Flow<ContentDescription> = + vmProvider.flatMapLatest { it.contentDescription } + + override val roaming: Flow<Boolean> = vmProvider.flatMapLatest { it.roaming } + + override val networkTypeIcon: Flow<Icon.Resource?> = + vmProvider.flatMapLatest { it.networkTypeIcon } + + override val networkTypeBackground: StateFlow<Icon.Resource?> = + vmProvider + .flatMapLatest { it.networkTypeBackground } + .stateIn(scope, SharingStarted.WhileSubscribed(), null) + + override val activityInVisible: Flow<Boolean> = + vmProvider.flatMapLatest { it.activityInVisible } + + override val activityOutVisible: Flow<Boolean> = + vmProvider.flatMapLatest { it.activityOutVisible } + + override val activityContainerVisible: Flow<Boolean> = + vmProvider.flatMapLatest { it.activityContainerVisible } +} + +/** Representation of this network when it is non-terrestrial (e.g., satellite) */ +private class CarrierBasedSatelliteViewModelImpl( + override val subscriptionId: Int, + interactor: MobileIconInteractor, +) : MobileIconViewModelCommon { + override val isVisible: StateFlow<Boolean> = MutableStateFlow(true) + override val icon: Flow<SignalIconModel> = interactor.signalLevelIcon + + override val contentDescription: Flow<ContentDescription> = + MutableStateFlow(ContentDescription.Loaded("")) + + /** These fields are not used for satellite icons currently */ + override val roaming: Flow<Boolean> = flowOf(false) + override val networkTypeIcon: Flow<Icon.Resource?> = flowOf(null) + override val networkTypeBackground: StateFlow<Icon.Resource?> = MutableStateFlow(null) + override val activityInVisible: Flow<Boolean> = flowOf(false) + override val activityOutVisible: Flow<Boolean> = flowOf(false) + override val activityContainerVisible: Flow<Boolean> = flowOf(false) +} + +/** Terrestrial (cellular) icon. */ +@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +@OptIn(ExperimentalCoroutinesApi::class) +private class CellularIconViewModel( + override val subscriptionId: Int, + iconInteractor: MobileIconInteractor, + airplaneModeInteractor: AirplaneModeInteractor, + constants: ConnectivityConstants, + flags: FeatureFlagsClassic, + scope: CoroutineScope, +) : MobileIconViewModelCommon { override val isVisible: StateFlow<Boolean> = if (!constants.hasDataCapabilities) { flowOf(false) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt index 6938d667ca81..63566ee814ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt @@ -28,7 +28,7 @@ object SatelliteIconModel { fun fromConnectionState( connectionState: SatelliteConnectionState, signalStrength: Int, - ): Icon? = + ): Icon.Resource? = when (connectionState) { // TODO(b/316635648): check if this should be null SatelliteConnectionState.Unknown, @@ -41,9 +41,13 @@ object SatelliteIconModel { SatelliteConnectionState.Connected -> fromSignalStrength(signalStrength) } - private fun fromSignalStrength( + /** + * Satellite icon appropriate for when we are connected. Use [fromConnectionState] for a more + * generally correct representation. + */ + fun fromSignalStrength( signalStrength: Int, - ): Icon? = + ): Icon.Resource? = // TODO(b/316634365): these need content descriptions when (signalStrength) { // No signal diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt index ae58398753e8..352413ee568a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel import android.content.Context import android.text.Html import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Text import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -28,6 +29,7 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileModel import com.android.systemui.statusbar.pipeline.shared.ui.model.SignalIcon @@ -116,14 +118,31 @@ constructor( it.signalLevelIcon, mobileDataContentName, ) { networkNameModel, signalIcon, dataContentDescription -> - val secondary = - mobileDataContentConcat(networkNameModel.name, dataContentDescription) - InternetTileModel.Active( - secondaryTitle = secondary, - icon = SignalIcon(signalIcon.toSignalDrawableState()), - stateDescription = ContentDescription.Loaded(secondary.toString()), - contentDescription = ContentDescription.Loaded(internetLabel), - ) + when (signalIcon) { + is SignalIconModel.Cellular -> { + val secondary = + mobileDataContentConcat( + networkNameModel.name, + dataContentDescription + ) + InternetTileModel.Active( + secondaryTitle = secondary, + icon = SignalIcon(signalIcon.toSignalDrawableState()), + stateDescription = ContentDescription.Loaded(secondary.toString()), + contentDescription = ContentDescription.Loaded(internetLabel), + ) + } + is SignalIconModel.Satellite -> { + val secondary = + signalIcon.icon.contentDescription.loadContentDescription(context) + InternetTileModel.Active( + secondaryTitle = secondary, + iconId = signalIcon.icon.res, + stateDescription = ContentDescription.Loaded(secondary), + contentDescription = ContentDescription.Loaded(internetLabel), + ) + } + } } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt index 20d5c5de3ffa..49953a1176fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor +import android.platform.test.annotations.EnableFlags import android.telephony.CellSignalStrength import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN @@ -159,10 +160,13 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun numberOfLevels_comesFromRepo() = + fun numberOfLevels_comesFromRepo_whenApplicable() = testScope.runTest { var latest: Int? = null - val job = underTest.signalLevelIcon.onEach { latest = it.numberOfLevels }.launchIn(this) + val job = + underTest.signalLevelIcon + .onEach { latest = (it as? SignalIconModel.Cellular)?.numberOfLevels } + .launchIn(this) connectionRepository.numberOfLevels.value = 5 assertThat(latest).isEqualTo(5) @@ -491,14 +495,19 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun iconId_correctLevel_notCutout() = + fun cellBasedIconId_correctLevel_notCutout() = testScope.runTest { + connectionRepository.isNonTerrestrial.value = false connectionRepository.isInService.value = true connectionRepository.primaryLevel.value = 1 connectionRepository.setDataEnabled(false) + connectionRepository.isNonTerrestrial.value = false - var latest: SignalIconModel? = null - val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + var latest: SignalIconModel.Cellular? = null + val job = + underTest.signalLevelIcon + .onEach { latest = it as? SignalIconModel.Cellular } + .launchIn(this) assertThat(latest?.level).isEqualTo(1) assertThat(latest?.showExclamationMark).isFalse() @@ -509,6 +518,7 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun icon_usesLevelFromInteractor() = testScope.runTest { + connectionRepository.isNonTerrestrial.value = false connectionRepository.isInService.value = true var latest: SignalIconModel? = null @@ -524,10 +534,15 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun icon_usesNumberOfLevelsFromInteractor() = + fun cellBasedIcon_usesNumberOfLevelsFromInteractor() = testScope.runTest { - var latest: SignalIconModel? = null - val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + connectionRepository.isNonTerrestrial.value = false + + var latest: SignalIconModel.Cellular? = null + val job = + underTest.signalLevelIcon + .onEach { latest = it as? SignalIconModel.Cellular } + .launchIn(this) connectionRepository.numberOfLevels.value = 5 assertThat(latest!!.numberOfLevels).isEqualTo(5) @@ -539,12 +554,16 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun icon_defaultDataDisabled_showExclamationTrue() = + fun cellBasedIcon_defaultDataDisabled_showExclamationTrue() = testScope.runTest { + connectionRepository.isNonTerrestrial.value = false mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false - var latest: SignalIconModel? = null - val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + var latest: SignalIconModel.Cellular? = null + val job = + underTest.signalLevelIcon + .onEach { latest = it as? SignalIconModel.Cellular } + .launchIn(this) assertThat(latest!!.showExclamationMark).isTrue() @@ -552,12 +571,16 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun icon_defaultConnectionFailed_showExclamationTrue() = + fun cellBasedIcon_defaultConnectionFailed_showExclamationTrue() = testScope.runTest { + connectionRepository.isNonTerrestrial.value = false mobileIconsInteractor.isDefaultConnectionFailed.value = true - var latest: SignalIconModel? = null - val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + var latest: SignalIconModel.Cellular? = null + val job = + underTest.signalLevelIcon + .onEach { latest = it as? SignalIconModel.Cellular } + .launchIn(this) assertThat(latest!!.showExclamationMark).isTrue() @@ -565,14 +588,18 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun icon_enabledAndNotFailed_showExclamationFalse() = + fun cellBasedIcon_enabledAndNotFailed_showExclamationFalse() = testScope.runTest { + connectionRepository.isNonTerrestrial.value = false connectionRepository.isInService.value = true mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true mobileIconsInteractor.isDefaultConnectionFailed.value = false - var latest: SignalIconModel? = null - val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + var latest: SignalIconModel.Cellular? = null + val job = + underTest.signalLevelIcon + .onEach { latest = it as? SignalIconModel.Cellular } + .launchIn(this) assertThat(latest!!.showExclamationMark).isFalse() @@ -580,11 +607,15 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun icon_usesEmptyState_whenNotInService() = + fun cellBasedIcon_usesEmptyState_whenNotInService() = testScope.runTest { - var latest: SignalIconModel? = null - val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + var latest: SignalIconModel.Cellular? = null + val job = + underTest.signalLevelIcon + .onEach { latest = it as? SignalIconModel.Cellular } + .launchIn(this) + connectionRepository.isNonTerrestrial.value = false connectionRepository.isInService.value = false assertThat(latest?.level).isEqualTo(0) @@ -604,11 +635,15 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun icon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() = + fun cellBasedIcon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() = testScope.runTest { - var latest: SignalIconModel? = null - val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + var latest: SignalIconModel.Cellular? = null + val job = + underTest.signalLevelIcon + .onEach { latest = it as? SignalIconModel.Cellular? } + .launchIn(this) + connectionRepository.isNonTerrestrial.value = false connectionRepository.isInService.value = true connectionRepository.carrierNetworkChangeActive.value = true connectionRepository.primaryLevel.value = 1 @@ -626,6 +661,20 @@ class MobileIconInteractorTest : SysuiTestCase() { job.cancel() } + @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + @Test + fun satBasedIcon_isUsedWhenNonTerrestrial() = + testScope.runTest { + val latest by collectLastValue(underTest.signalLevelIcon) + + // Start off using cellular + assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java) + + connectionRepository.isNonTerrestrial.value = true + + assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java) + } + private fun createInteractor( overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl() ) = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt index 90a894648c9f..ebec00380cf4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt @@ -32,7 +32,7 @@ internal class SignalIconModelParameterizedTest(private val testCase: TestCase) @Test fun drawableFromModel_level0_numLevels4_noExclamation_notCarrierNetworkChange() { val model = - SignalIconModel( + SignalIconModel.Cellular( level = 0, numberOfLevels = 4, showExclamationMark = false, @@ -59,7 +59,7 @@ internal class SignalIconModelParameterizedTest(private val testCase: TestCase) val expected: Int, ) { fun toSignalIconModel() = - SignalIconModel( + SignalIconModel.Cellular( level = level, numberOfLevels = numberOfLevels, showExclamationMark = showExclamation, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt index 889130f47820..deb9fcf4b56e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt @@ -190,7 +190,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { /** Convenience constructor for these tests */ fun defaultSignal(level: Int = 1): SignalIconModel { - return SignalIconModel( + return SignalIconModel.Cellular( level, NUM_LEVELS, showExclamationMark = false, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt index 147efcbd67c4..83d0fe8f9c4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt @@ -55,6 +55,7 @@ import com.android.systemui.util.CarrierConfigTracker import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope @@ -709,6 +710,87 @@ class MobileIconViewModelTest : SysuiTestCase() { .isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background, null)) } + @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + @Test + fun nonTerrestrial_defaultProperties() = + testScope.runTest { + repository.isNonTerrestrial.value = true + + val roaming by collectLastValue(underTest.roaming) + val networkTypeIcon by collectLastValue(underTest.networkTypeIcon) + val networkTypeBackground by collectLastValue(underTest.networkTypeBackground) + val activityInVisible by collectLastValue(underTest.activityInVisible) + val activityOutVisible by collectLastValue(underTest.activityOutVisible) + val activityContainerVisible by collectLastValue(underTest.activityContainerVisible) + + assertThat(roaming).isFalse() + assertThat(networkTypeIcon).isNull() + assertThat(networkTypeBackground).isNull() + assertThat(activityInVisible).isFalse() + assertThat(activityOutVisible).isFalse() + assertThat(activityContainerVisible).isFalse() + } + + @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + @Test + fun nonTerrestrial_ignoresDefaultProperties() = + testScope.runTest { + repository.isNonTerrestrial.value = true + + val roaming by collectLastValue(underTest.roaming) + val networkTypeIcon by collectLastValue(underTest.networkTypeIcon) + val networkTypeBackground by collectLastValue(underTest.networkTypeBackground) + val activityInVisible by collectLastValue(underTest.activityInVisible) + val activityOutVisible by collectLastValue(underTest.activityOutVisible) + val activityContainerVisible by collectLastValue(underTest.activityContainerVisible) + + repository.setAllRoaming(true) + repository.setNetworkTypeKey(connectionsRepository.LTE_KEY) + // sets the background on cellular + repository.hasPrioritizedNetworkCapabilities.value = true + repository.dataActivityDirection.value = + DataActivityModel( + hasActivityIn = true, + hasActivityOut = true, + ) + + assertThat(roaming).isFalse() + assertThat(networkTypeIcon).isNull() + assertThat(networkTypeBackground).isNull() + assertThat(activityInVisible).isFalse() + assertThat(activityOutVisible).isFalse() + assertThat(activityContainerVisible).isFalse() + } + + @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + @Test + fun nonTerrestrial_usesSatelliteIcon() = + testScope.runTest { + repository.isNonTerrestrial.value = true + repository.setAllLevels(0) + + val latest by + collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class)) + + // Level 0 -> no connection + assertThat(latest).isNotNull() + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0) + + // 1-2 -> 1 bar + repository.setAllLevels(1) + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1) + + repository.setAllLevels(2) + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1) + + // 3-4 -> 2 bars + repository.setAllLevels(3) + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2) + + repository.setAllLevels(4) + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2) + } + private fun createAndSetViewModel() { underTest = MobileIconViewModel( @@ -723,24 +805,5 @@ class MobileIconViewModelTest : SysuiTestCase() { companion object { private const val SUB_1_ID = 1 - private const val NUM_LEVELS = 4 - - /** Convenience constructor for these tests */ - fun defaultSignal(level: Int = 1): SignalIconModel { - return SignalIconModel( - level, - NUM_LEVELS, - showExclamationMark = false, - carrierNetworkChange = false, - ) - } - - fun emptySignal(): SignalIconModel = - SignalIconModel( - level = 0, - numberOfLevels = NUM_LEVELS, - showExclamationMark = true, - carrierNetworkChange = false, - ) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt index 5f4d7bf6f371..c01032757bb0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt @@ -60,6 +60,8 @@ class FakeMobileIconInteractor( override val isInService = MutableStateFlow(true) + override val isNonTerrestrial = MutableStateFlow(false) + private val _isDataEnabled = MutableStateFlow(true) override val isDataEnabled = _isDataEnabled @@ -69,7 +71,7 @@ class FakeMobileIconInteractor( override val signalLevelIcon: MutableStateFlow<SignalIconModel> = MutableStateFlow( - SignalIconModel( + SignalIconModel.Cellular( level = 0, numberOfLevels = 4, showExclamationMark = false, |