summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Evan Laird <evanlaird@google.com> 2024-01-23 22:18:18 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-01-23 22:18:18 +0000
commit41db632a8f82c66f06fecd298f81d68691d11732 (patch)
treeee8ac34a38fbbb9f0daa4a3cf1587d29a5397fea
parent9bbc5ba475110013dda6edb93a189e929d8eac02 (diff)
parent7b7b46bc14c65bd75a24af393678ad85bc9dc827 (diff)
Merge "[Sat] Carrier-based view model + signal" into main
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt97
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt98
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt95
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt101
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt4
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,