diff options
| author | 2024-03-06 13:17:30 -0500 | |
|---|---|---|
| committer | 2024-03-13 15:58:11 -0400 | |
| commit | 2fcb2e5cf14e9880dd74cb98c1b29ca940e83806 (patch) | |
| tree | ccc65def616fba63697d463be117781549035a2b | |
| parent | 0ddacc17d72c0fbc1040c30144bd15c41a2eff64 (diff) | |
Add hysteresis to satellite icon when losing connection.
The duration is taken from the carrier config value KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT.
Flag: none
Test: MobileIconInteractorTest
Fixes: 326441908
Change-Id: I0bacafe50e13d44f580ffef26c7547ae8eaed0bd
10 files changed, 136 insertions, 8 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt index f4e3eab8593d..3b2930f78d19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.model import android.os.PersistableBundle import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL +import android.telephony.CarrierConfigManager.KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL import androidx.annotation.VisibleForTesting import kotlinx.coroutines.flow.MutableStateFlow @@ -42,10 +43,11 @@ import kotlinx.coroutines.flow.asStateFlow * using the default config for logging purposes. * * NOTE to add new keys to be tracked: - * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig] - * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config] - * 3. Add the new [BooleanCarrierConfig] to the list of tracked configs, so they are properly - * updated when a new carrier config comes down + * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig] or [IntCarrierConfig] + * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config] or + * [IntCarrierConfig.config] + * 3. Add the new wrapped public flow to the list of tracked configs, so they are properly updated + * when a new carrier config comes down */ class SystemUiCarrierConfig internal constructor( @@ -66,10 +68,16 @@ internal constructor( /** Flow tracking the [KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL] config */ val showOperatorNameInStatusBar: StateFlow<Boolean> = showOperatorName.config + private val satelliteHysteresisSeconds = + IntCarrierConfig(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, defaultConfig) + /** Flow tracking the [KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT] config */ + val satelliteConnectionHysteresisSeconds: StateFlow<Int> = satelliteHysteresisSeconds.config + private val trackedConfigs = listOf( inflateSignalStrength, showOperatorName, + satelliteHysteresisSeconds, ) /** Ingest a new carrier config, and switch all of the tracked keys over to the new values */ @@ -90,15 +98,19 @@ internal constructor( override fun toString(): String = trackedConfigs.joinToString { it.toString() } } +interface CarrierConfig { + fun update(config: PersistableBundle) +} + /** Extracts [key] from the carrier config, and stores it in a flow */ private class BooleanCarrierConfig( val key: String, defaultConfig: PersistableBundle, -) { +) : CarrierConfig { private val _configValue = MutableStateFlow(defaultConfig.getBoolean(key)) val config = _configValue.asStateFlow() - fun update(config: PersistableBundle) { + override fun update(config: PersistableBundle) { _configValue.value = config.getBoolean(key) } @@ -106,3 +118,20 @@ private class BooleanCarrierConfig( return "$key=${config.value}" } } + +/** Extracts [key] from the carrier config, and stores it in a flow */ +private class IntCarrierConfig( + val key: String, + defaultConfig: PersistableBundle, +) : CarrierConfig { + private val _configValue = MutableStateFlow(defaultConfig.getInt(key)) + val config = _configValue.asStateFlow() + + override fun update(config: PersistableBundle) { + _configValue.value = config.getInt(key) + } + + override fun toString(): String { + return "$key=${config.value}" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt index 8f3b0e788dae..8f00b43e79f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt @@ -141,6 +141,9 @@ interface MobileConnectionRepository { */ val hasPrioritizedNetworkCapabilities: StateFlow<Boolean> + /** Duration in seconds of the hysteresis to use when losing satellite connection. */ + val satelliteConnectionHysteresisSeconds: StateFlow<Int> + /** * True if this connection is in emergency callback mode. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt index 3cb138bd75a9..af34a57c7242 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt @@ -206,6 +206,8 @@ class DemoMobileConnectionRepository( override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false) + override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0) + override suspend fun isInEcmMode(): Boolean = false /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt index f8858c5037ab..2bc3bcbc8bf5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt @@ -186,6 +186,9 @@ class CarrierMergedConnectionRepository( */ override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false).asStateFlow() + /** Non-applicable to carrier merged connections. */ + override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0).asStateFlow() + override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled override suspend fun isInEcmMode(): Boolean = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt index a1241965de7a..60b8599ecabd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt @@ -334,6 +334,15 @@ class FullMobileConnectionRepository( activeRepo.value.hasPrioritizedNetworkCapabilities.value, ) + override val satelliteConnectionHysteresisSeconds = + activeRepo + .flatMapLatest { it.satelliteConnectionHysteresisSeconds } + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + activeRepo.value.satelliteConnectionHysteresisSeconds.value + ) + override suspend fun isInEcmMode(): Boolean = activeRepo.value.isInEcmMode() class Factory diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index 77fd6bef8a33..f01ac0e0a677 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -442,6 +442,9 @@ class MobileConnectionRepositoryImpl( .flowOn(bgDispatcher) .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val satelliteConnectionHysteresisSeconds: StateFlow<Int> = + systemUiCarrierConfig.satelliteConnectionHysteresisSeconds + class Factory @Inject constructor( 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 adcf736bba01..9d194cfca350 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 @@ -35,18 +35,25 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIc 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 com.android.systemui.util.kotlin.pairwiseBy +import kotlin.time.DurationUnit +import kotlin.time.toDuration import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay 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.collect +import kotlinx.coroutines.flow.collectLatest 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 +import kotlinx.coroutines.launch interface MobileIconInteractor { /** The table log created for this connection */ @@ -262,6 +269,43 @@ class MobileIconInteractorImpl( MutableStateFlow(false).asStateFlow() } + private val hysteresisActive = MutableStateFlow(false) + + private val isNonTerrestrialWithHysteresis: StateFlow<Boolean> = + combine(isNonTerrestrial, hysteresisActive) { isNonTerrestrial, hysteresisActive -> + if (hysteresisActive) { + true + } else { + isNonTerrestrial + } + } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "isNonTerrestrialWithHysteresis", + columnPrefix = "", + initialValue = Flags.carrierEnabledSatelliteFlag(), + ) + .stateIn(scope, SharingStarted.Eagerly, Flags.carrierEnabledSatelliteFlag()) + + private val lostSatelliteConnection = + isNonTerrestrial.pairwiseBy { old, new -> hysteresisActive.value = old && !new } + + init { + scope.launch { lostSatelliteConnection.collect() } + scope.launch { + hysteresisActive.collectLatest { + if (it) { + delay( + connectionRepository.satelliteConnectionHysteresisSeconds.value.toDuration( + DurationUnit.SECONDS + ) + ) + hysteresisActive.value = false + } + } + } + } + override val isRoaming: StateFlow<Boolean> = combine( connectionRepository.carrierNetworkChangeActive, @@ -360,7 +404,7 @@ class MobileIconInteractorImpl( showExclamationMark.value, carrierNetworkChangeActive.value, ) - isNonTerrestrial + isNonTerrestrialWithHysteresis .flatMapLatest { ntn -> if (ntn) { satelliteIcon diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt index 1fb6e2c7a232..c13e830afac7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY @@ -679,6 +680,9 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { telephonyManager: TelephonyManager, ): MobileConnectionRepositoryImpl { whenever(telephonyManager.subscriptionId).thenReturn(SUB_ID) + val systemUiCarrierConfigMock: SystemUiCarrierConfig = mock() + whenever(systemUiCarrierConfigMock.satelliteConnectionHysteresisSeconds) + .thenReturn(MutableStateFlow(0)) val realRepo = MobileConnectionRepositoryImpl( @@ -689,7 +693,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { SEP, connectivityManager, telephonyManager, - systemUiCarrierConfig = mock(), + systemUiCarrierConfig = systemUiCarrierConfigMock, fakeBroadcastDispatcher, mobileMappingsProxy = mock(), testDispatcher, 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 49953a1176fd..c49fcf88ecaa 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 @@ -43,12 +43,15 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import kotlin.time.Duration.Companion.seconds +import kotlin.time.DurationUnit import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -675,6 +678,32 @@ class MobileIconInteractorTest : SysuiTestCase() { assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java) } + @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + @Test + fun satBasedIcon_hasHysteresisWhenDisabled() = + testScope.runTest { + val latest by collectLastValue(underTest.signalLevelIcon) + + val hysteresisDuration = 5.seconds + connectionRepository.satelliteConnectionHysteresisSeconds.value = + hysteresisDuration.toInt(DurationUnit.SECONDS) + + connectionRepository.isNonTerrestrial.value = true + + assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java) + + // Disable satellite + connectionRepository.isNonTerrestrial.value = false + + // Satellite icon should still be visible + assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java) + + // Wait for the icon to change + advanceTimeBy(hysteresisDuration) + + assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java) + } + private fun createInteractor( overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl() ) = diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt index 32d572ef9dee..2d5a3612ff6a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt @@ -63,6 +63,8 @@ class FakeMobileConnectionRepository( override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false) + override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0) + private var isInEcmMode: Boolean = false override suspend fun isInEcmMode(): Boolean = isInEcmMode |