Merge changes Id57cc404,Ieaf30071,Idb2b182c,I5de259d8,I5d7762b7 into tm-qpr-dev

* changes:
  [Sb refactor] Fix missed telephony callback events
  [Sb refactor] Handle telephony display info with only an override
  [Sb refactor][cleanup] Move testing to testScope
  [Sb refactor][cleanup] remove spaces from tests
  [Sb refactor] Filter out -1 values for ERI
This commit is contained in:
Evan Laird
2023-03-13 22:41:27 +00:00
committed by Android (Google) Code Review
4 changed files with 568 additions and 142 deletions

View File

@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.content.Context import android.content.Context
import android.content.IntentFilter import android.content.IntentFilter
import android.telephony.CellSignalStrength
import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
import android.telephony.CellSignalStrengthCdma import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState import android.telephony.ServiceState
@@ -28,12 +27,12 @@ import android.telephony.TelephonyCallback
import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.ERI_OFF import android.telephony.TelephonyManager.ERI_FLASH
import android.telephony.TelephonyManager.ERI_ON
import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import com.android.settingslib.Utils import com.android.settingslib.Utils
import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBuffer
@@ -59,16 +58,14 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.asExecutor import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
/** /**
@@ -100,8 +97,6 @@ class MobileConnectionRepositoryImpl(
} }
} }
private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
/** /**
* This flow defines the single shared connection to system_server via TelephonyCallback. Any * This flow defines the single shared connection to system_server via TelephonyCallback. Any
* new callback should be added to this listener and funneled through callbackEvents via a data * new callback should be added to this listener and funneled through callbackEvents via a data
@@ -109,9 +104,15 @@ class MobileConnectionRepositoryImpl(
* *
* The reason we need to do this is because TelephonyManager limits the number of registered * The reason we need to do this is because TelephonyManager limits the number of registered
* listeners per-process, so we don't want to create a new listener for every callback. * listeners per-process, so we don't want to create a new listener for every callback.
*
* A note on the design for back pressure here: We use the [coalesce] operator here to change
* the backpressure strategy to store exactly the last callback event of _each type_ here, as
* opposed to the default strategy which is to drop the oldest event (regardless of type). This
* means that we should never miss any single event as long as the flow has been started.
*/ */
private val callbackEvents: SharedFlow<CallbackEvent> = private val callbackEvents: StateFlow<TelephonyCallbackState> = run {
conflatedCallbackFlow { val initial = TelephonyCallbackState()
callbackFlow {
val callback = val callback =
object : object :
TelephonyCallback(), TelephonyCallback(),
@@ -165,48 +166,50 @@ class MobileConnectionRepositoryImpl(
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback) telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) } awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
} }
.shareIn(scope, SharingStarted.WhileSubscribed()) .scan(initial = initial) { state, event -> state.applyEvent(event) }
.stateIn(scope = scope, started = SharingStarted.WhileSubscribed(), initial)
}
override val isEmergencyOnly = override val isEmergencyOnly =
callbackEvents callbackEvents
.filterIsInstance<CallbackEvent.OnServiceStateChanged>() .mapNotNull { it.onServiceStateChanged }
.map { it.serviceState.isEmergencyOnly } .map { it.serviceState.isEmergencyOnly }
.stateIn(scope, SharingStarted.WhileSubscribed(), false) .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val isRoaming = override val isRoaming =
callbackEvents callbackEvents
.filterIsInstance<CallbackEvent.OnServiceStateChanged>() .mapNotNull { it.onServiceStateChanged }
.map { it.serviceState.roaming } .map { it.serviceState.roaming }
.stateIn(scope, SharingStarted.WhileSubscribed(), false) .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val operatorAlphaShort = override val operatorAlphaShort =
callbackEvents callbackEvents
.filterIsInstance<CallbackEvent.OnServiceStateChanged>() .mapNotNull { it.onServiceStateChanged }
.map { it.serviceState.operatorAlphaShort } .map { it.serviceState.operatorAlphaShort }
.stateIn(scope, SharingStarted.WhileSubscribed(), null) .stateIn(scope, SharingStarted.WhileSubscribed(), null)
override val isInService = override val isInService =
callbackEvents callbackEvents
.filterIsInstance<CallbackEvent.OnServiceStateChanged>() .mapNotNull { it.onServiceStateChanged }
.map { Utils.isInService(it.serviceState) } .map { Utils.isInService(it.serviceState) }
.stateIn(scope, SharingStarted.WhileSubscribed(), false) .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val isGsm = override val isGsm =
callbackEvents callbackEvents
.filterIsInstance<CallbackEvent.OnSignalStrengthChanged>() .mapNotNull { it.onSignalStrengthChanged }
.map { it.signalStrength.isGsm } .map { it.signalStrength.isGsm }
.stateIn(scope, SharingStarted.WhileSubscribed(), false) .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val cdmaLevel = override val cdmaLevel =
callbackEvents callbackEvents
.filterIsInstance<CallbackEvent.OnSignalStrengthChanged>() .mapNotNull { it.onSignalStrengthChanged }
.map { .map {
it.signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java).let { it.signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java).let {
strengths -> strengths ->
if (strengths.isNotEmpty()) { if (strengths.isNotEmpty()) {
strengths[0].level strengths[0].level
} else { } else {
CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN SIGNAL_STRENGTH_NONE_OR_UNKNOWN
} }
} }
} }
@@ -214,19 +217,19 @@ class MobileConnectionRepositoryImpl(
override val primaryLevel = override val primaryLevel =
callbackEvents callbackEvents
.filterIsInstance<CallbackEvent.OnSignalStrengthChanged>() .mapNotNull { it.onSignalStrengthChanged }
.map { it.signalStrength.level } .map { it.signalStrength.level }
.stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN) .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
override val dataConnectionState = override val dataConnectionState =
callbackEvents callbackEvents
.filterIsInstance<CallbackEvent.OnDataConnectionStateChanged>() .mapNotNull { it.onDataConnectionStateChanged }
.map { it.dataState.toDataConnectionType() } .map { it.dataState.toDataConnectionType() }
.stateIn(scope, SharingStarted.WhileSubscribed(), Disconnected) .stateIn(scope, SharingStarted.WhileSubscribed(), Disconnected)
override val dataActivityDirection = override val dataActivityDirection =
callbackEvents callbackEvents
.filterIsInstance<CallbackEvent.OnDataActivity>() .mapNotNull { it.onDataActivity }
.map { it.direction.toMobileDataActivityModel() } .map { it.direction.toMobileDataActivityModel() }
.stateIn( .stateIn(
scope, scope,
@@ -236,28 +239,26 @@ class MobileConnectionRepositoryImpl(
override val carrierNetworkChangeActive = override val carrierNetworkChangeActive =
callbackEvents callbackEvents
.filterIsInstance<CallbackEvent.OnCarrierNetworkChange>() .mapNotNull { it.onCarrierNetworkChange }
.map { it.active } .map { it.active }
.stateIn(scope, SharingStarted.WhileSubscribed(), false) .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val resolvedNetworkType = override val resolvedNetworkType =
callbackEvents callbackEvents
.filterIsInstance<CallbackEvent.OnDisplayInfoChanged>() .mapNotNull { it.onDisplayInfoChanged }
.map { .map {
if (it.telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) { if (it.telephonyDisplayInfo.overrideNetworkType != OVERRIDE_NETWORK_TYPE_NONE) {
UnknownNetworkType
} else if (
it.telephonyDisplayInfo.overrideNetworkType == OVERRIDE_NETWORK_TYPE_NONE
) {
DefaultNetworkType(
mobileMappingsProxy.toIconKey(it.telephonyDisplayInfo.networkType)
)
} else {
OverrideNetworkType( OverrideNetworkType(
mobileMappingsProxy.toIconKeyOverride( mobileMappingsProxy.toIconKeyOverride(
it.telephonyDisplayInfo.overrideNetworkType it.telephonyDisplayInfo.overrideNetworkType
) )
) )
} else if (it.telephonyDisplayInfo.networkType != NETWORK_TYPE_UNKNOWN) {
DefaultNetworkType(
mobileMappingsProxy.toIconKey(it.telephonyDisplayInfo.networkType)
)
} else {
UnknownNetworkType
} }
} }
.stateIn(scope, SharingStarted.WhileSubscribed(), UnknownNetworkType) .stateIn(scope, SharingStarted.WhileSubscribed(), UnknownNetworkType)
@@ -282,7 +283,10 @@ class MobileConnectionRepositoryImpl(
override val cdmaRoaming: StateFlow<Boolean> = override val cdmaRoaming: StateFlow<Boolean> =
telephonyPollingEvent telephonyPollingEvent
.mapLatest { telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber != ERI_OFF } .mapLatest {
val cdmaEri = telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber
cdmaEri == ERI_ON || cdmaEri == ERI_FLASH
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false) .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val networkName: StateFlow<NetworkNameModel> = override val networkName: StateFlow<NetworkNameModel> =
@@ -300,7 +304,8 @@ class MobileConnectionRepositoryImpl(
override val dataEnabled = run { override val dataEnabled = run {
val initial = telephonyManager.isDataConnectionAllowed val initial = telephonyManager.isDataConnectionAllowed
callbackEvents callbackEvents
.mapNotNull { (it as? CallbackEvent.OnDataEnabledChanged)?.enabled } .mapNotNull { it.onDataEnabledChanged }
.map { it.enabled }
.stateIn(scope, SharingStarted.WhileSubscribed(), initial) .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
} }
@@ -344,12 +349,41 @@ class MobileConnectionRepositoryImpl(
* Wrap every [TelephonyCallback] we care about in a data class so we can accept them in a single * Wrap every [TelephonyCallback] we care about in a data class so we can accept them in a single
* shared flow and then split them back out into other flows. * shared flow and then split them back out into other flows.
*/ */
private sealed interface CallbackEvent { sealed interface CallbackEvent {
data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent
data class OnDataActivity(val direction: Int) : CallbackEvent
data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent
data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent
data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent
data class OnServiceStateChanged(val serviceState: ServiceState) : CallbackEvent data class OnServiceStateChanged(val serviceState: ServiceState) : CallbackEvent
data class OnSignalStrengthChanged(val signalStrength: SignalStrength) : CallbackEvent data class OnSignalStrengthChanged(val signalStrength: SignalStrength) : CallbackEvent
data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent }
data class OnDataActivity(val direction: Int) : CallbackEvent
data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent /**
data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent * A simple box type for 1-to-1 mapping of [CallbackEvent] to the batched event. Used in conjunction
data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent * with [scan] to make sure we don't drop important callbacks due to late subscribers
*/
data class TelephonyCallbackState(
val onDataActivity: CallbackEvent.OnDataActivity? = null,
val onCarrierNetworkChange: CallbackEvent.OnCarrierNetworkChange? = null,
val onDataConnectionStateChanged: CallbackEvent.OnDataConnectionStateChanged? = null,
val onDataEnabledChanged: CallbackEvent.OnDataEnabledChanged? = null,
val onDisplayInfoChanged: CallbackEvent.OnDisplayInfoChanged? = null,
val onServiceStateChanged: CallbackEvent.OnServiceStateChanged? = null,
val onSignalStrengthChanged: CallbackEvent.OnSignalStrengthChanged? = null,
) {
fun applyEvent(event: CallbackEvent): TelephonyCallbackState {
return when (event) {
is CallbackEvent.OnCarrierNetworkChange -> copy(onCarrierNetworkChange = event)
is CallbackEvent.OnDataActivity -> copy(onDataActivity = event)
is CallbackEvent.OnDataConnectionStateChanged ->
copy(onDataConnectionStateChanged = event)
is CallbackEvent.OnDataEnabledChanged -> copy(onDataEnabledChanged = event)
is CallbackEvent.OnDisplayInfoChanged -> copy(onDisplayInfoChanged = event)
is CallbackEvent.OnServiceStateChanged -> {
copy(onServiceStateChanged = event)
}
is CallbackEvent.OnSignalStrengthChanged -> copy(onSignalStrengthChanged = event)
}
}
} }

View File

@@ -18,17 +18,16 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.content.Intent import android.content.Intent
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
import android.telephony.CellSignalStrengthCdma
import android.telephony.NetworkRegistrationInfo import android.telephony.NetworkRegistrationInfo
import android.telephony.ServiceState import android.telephony.ServiceState
import android.telephony.ServiceState.STATE_IN_SERVICE import android.telephony.ServiceState.STATE_IN_SERVICE
import android.telephony.ServiceState.STATE_OUT_OF_SERVICE import android.telephony.ServiceState.STATE_OUT_OF_SERVICE
import android.telephony.SignalStrength
import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.DataActivityListener import android.telephony.TelephonyCallback.DataActivityListener
import android.telephony.TelephonyCallback.ServiceStateListener import android.telephony.TelephonyCallback.ServiceStateListener
import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.DATA_ACTIVITY_DORMANT import android.telephony.TelephonyManager.DATA_ACTIVITY_DORMANT
import android.telephony.TelephonyManager.DATA_ACTIVITY_IN import android.telephony.TelephonyManager.DATA_ACTIVITY_IN
@@ -68,6 +67,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrier
import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
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.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
@@ -75,14 +75,12 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestScope
import org.junit.After import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.Mock import org.mockito.Mock
@@ -99,7 +97,6 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Mock private lateinit var logger: MobileInputLogger @Mock private lateinit var logger: MobileInputLogger
@Mock private lateinit var tableLogger: TableLogBuffer @Mock private lateinit var tableLogger: TableLogBuffer
private val scope = CoroutineScope(IMMEDIATE)
private val mobileMappings = FakeMobileMappingsProxy() private val mobileMappings = FakeMobileMappingsProxy()
private val systemUiCarrierConfig = private val systemUiCarrierConfig =
SystemUiCarrierConfig( SystemUiCarrierConfig(
@@ -107,6 +104,9 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
createTestConfig(), createTestConfig(),
) )
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
@@ -124,21 +124,16 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
systemUiCarrierConfig, systemUiCarrierConfig,
fakeBroadcastDispatcher, fakeBroadcastDispatcher,
mobileMappings, mobileMappings,
IMMEDIATE, testDispatcher,
logger, logger,
tableLogger, tableLogger,
scope, testScope.backgroundScope,
) )
} }
@After
fun tearDown() {
scope.cancel()
}
@Test @Test
fun emergencyOnly() = fun emergencyOnly() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this) val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
@@ -154,18 +149,15 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun emergencyOnly_toggles() = fun emergencyOnly_toggles() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this) val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<ServiceStateListener>() val callback = getTelephonyCallbackForType<ServiceStateListener>()
val serviceState = ServiceState() callback.onServiceStateChanged(ServiceState().also { it.isEmergencyOnly = true })
serviceState.isEmergencyOnly = true
callback.onServiceStateChanged(serviceState)
assertThat(latest).isTrue() assertThat(latest).isTrue()
serviceState.isEmergencyOnly = false callback.onServiceStateChanged(ServiceState().also { it.isEmergencyOnly = false })
callback.onServiceStateChanged(serviceState)
assertThat(latest).isFalse() assertThat(latest).isFalse()
@@ -174,7 +166,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun cdmaLevelUpdates() = fun cdmaLevelUpdates() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Int? = null var latest: Int? = null
val job = underTest.cdmaLevel.onEach { latest = it }.launchIn(this) val job = underTest.cdmaLevel.onEach { latest = it }.launchIn(this)
@@ -194,7 +186,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun gsmLevelUpdates() = fun gsmLevelUpdates() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Int? = null var latest: Int? = null
val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this) val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
@@ -214,7 +206,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun isGsm() = fun isGsm() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.isGsm.onEach { latest = it }.launchIn(this) val job = underTest.isGsm.onEach { latest = it }.launchIn(this)
@@ -234,7 +226,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun dataConnectionState_connected() = fun dataConnectionState_connected() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: DataConnectionState? = null var latest: DataConnectionState? = null
val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this) val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -249,7 +241,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun dataConnectionState_connecting() = fun dataConnectionState_connecting() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: DataConnectionState? = null var latest: DataConnectionState? = null
val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this) val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -264,7 +256,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun dataConnectionState_disconnected() = fun dataConnectionState_disconnected() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: DataConnectionState? = null var latest: DataConnectionState? = null
val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this) val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -279,7 +271,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun dataConnectionState_disconnecting() = fun dataConnectionState_disconnecting() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: DataConnectionState? = null var latest: DataConnectionState? = null
val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this) val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -294,7 +286,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun dataConnectionState_suspended() = fun dataConnectionState_suspended() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: DataConnectionState? = null var latest: DataConnectionState? = null
val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this) val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -309,7 +301,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun dataConnectionState_handoverInProgress() = fun dataConnectionState_handoverInProgress() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: DataConnectionState? = null var latest: DataConnectionState? = null
val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this) val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -324,7 +316,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun dataConnectionState_unknown() = fun dataConnectionState_unknown() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: DataConnectionState? = null var latest: DataConnectionState? = null
val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this) val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -339,7 +331,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun dataConnectionState_invalid() = fun dataConnectionState_invalid() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: DataConnectionState? = null var latest: DataConnectionState? = null
val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this) val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -354,7 +346,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun dataActivity() = fun dataActivity() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: DataActivityModel? = null var latest: DataActivityModel? = null
val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this) val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
@@ -368,7 +360,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun carrierNetworkChange() = fun carrierNetworkChange() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.carrierNetworkChangeActive.onEach { latest = it }.launchIn(this) val job = underTest.carrierNetworkChangeActive.onEach { latest = it }.launchIn(this)
@@ -382,7 +374,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun networkType_default() = fun networkType_default() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: ResolvedNetworkType? = null var latest: ResolvedNetworkType? = null
val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this) val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
@@ -395,7 +387,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun networkType_unknown_hasCorrectKey() = fun networkType_unknown_hasCorrectKey() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: ResolvedNetworkType? = null var latest: ResolvedNetworkType? = null
val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this) val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
@@ -413,14 +405,19 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun networkType_updatesUsingDefault() = fun networkType_updatesUsingDefault() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: ResolvedNetworkType? = null var latest: ResolvedNetworkType? = null
val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this) val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>() val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val overrideType = OVERRIDE_NETWORK_TYPE_NONE
val type = NETWORK_TYPE_LTE val type = NETWORK_TYPE_LTE
val expected = DefaultNetworkType(mobileMappings.toIconKey(type)) val expected = DefaultNetworkType(mobileMappings.toIconKey(type))
val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) } val ti =
mock<TelephonyDisplayInfo>().also {
whenever(it.overrideNetworkType).thenReturn(overrideType)
whenever(it.networkType).thenReturn(type)
}
callback.onDisplayInfoChanged(ti) callback.onDisplayInfoChanged(ti)
assertThat(latest).isEqualTo(expected) assertThat(latest).isEqualTo(expected)
@@ -430,7 +427,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun networkType_updatesUsingOverride() = fun networkType_updatesUsingOverride() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: ResolvedNetworkType? = null var latest: ResolvedNetworkType? = null
val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this) val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
@@ -449,17 +446,39 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
job.cancel() job.cancel()
} }
@Test
fun networkType_unknownNetworkWithOverride_usesOverrideKey() =
testScope.runTest {
var latest: ResolvedNetworkType? = null
val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val unknown = NETWORK_TYPE_UNKNOWN
val type = OVERRIDE_NETWORK_TYPE_LTE_CA
val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
val ti =
mock<TelephonyDisplayInfo>().also {
whenever(it.networkType).thenReturn(unknown)
whenever(it.overrideNetworkType).thenReturn(type)
}
callback.onDisplayInfoChanged(ti)
assertThat(latest).isEqualTo(expected)
job.cancel()
}
@Test @Test
fun dataEnabled_initial_false() = fun dataEnabled_initial_false() =
runBlocking(IMMEDIATE) { testScope.runTest {
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false) whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
assertThat(underTest.dataEnabled.value).isFalse() assertThat(underTest.dataEnabled.value).isFalse()
} }
@Test @Test
fun `is data enabled - tracks telephony callback`() = fun isDataEnabled_tracksTelephonyCallback() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this) val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
@@ -479,7 +498,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test @Test
fun numberOfLevels_isDefault() = fun numberOfLevels_isDefault() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Int? = null var latest: Int? = null
val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this) val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
@@ -489,51 +508,68 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
} }
@Test @Test
fun `roaming - cdma - queries telephony manager`() = fun roaming_cdma_queriesTelephonyManager() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.cdmaRoaming.onEach { latest = it }.launchIn(this) val job = underTest.cdmaRoaming.onEach { latest = it }.launchIn(this)
val cb = getTelephonyCallbackForType<ServiceStateListener>() val cb = getTelephonyCallbackForType<ServiceStateListener>()
val serviceState = ServiceState() // CDMA roaming is off, GSM roaming is on
serviceState.roaming = false
// CDMA roaming is off, GSM roaming is off
whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF) whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
cb.onServiceStateChanged(serviceState) cb.onServiceStateChanged(ServiceState().also { it.roaming = true })
assertThat(latest).isFalse() assertThat(latest).isFalse()
// CDMA roaming is off, GSM roaming is on // CDMA roaming is on, GSM roaming is off
whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_ON) whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_ON)
cb.onServiceStateChanged(serviceState) cb.onServiceStateChanged(ServiceState().also { it.roaming = false })
assertThat(latest).isTrue() assertThat(latest).isTrue()
job.cancel() job.cancel()
} }
/**
* [TelephonyManager.getCdmaEnhancedRoamingIndicatorDisplayNumber] returns -1 if the service is
* not running or if there is an error while retrieving the cdma ERI
*/
@Test @Test
fun `roaming - gsm - queries service state`() = fun cdmaRoaming_ignoresNegativeOne() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this) val job = underTest.cdmaRoaming.onEach { latest = it }.launchIn(this)
val serviceState = ServiceState() val serviceState = ServiceState()
serviceState.roaming = false serviceState.roaming = false
val cb = getTelephonyCallbackForType<ServiceStateListener>() val cb = getTelephonyCallbackForType<ServiceStateListener>()
// CDMA roaming is off, GSM roaming is off // CDMA roaming is unavailable (-1), GSM roaming is off
whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF) whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(-1)
cb.onServiceStateChanged(serviceState) cb.onServiceStateChanged(serviceState)
assertThat(latest).isFalse() assertThat(latest).isFalse()
job.cancel()
}
@Test
fun roaming_gsm_queriesServiceState() =
testScope.runTest {
var latest: Boolean? = null
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
val cb = getTelephonyCallbackForType<ServiceStateListener>()
// CDMA roaming is off, GSM roaming is off
whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
cb.onServiceStateChanged(ServiceState().also { it.roaming = false })
assertThat(latest).isFalse()
// CDMA roaming is off, GSM roaming is on // CDMA roaming is off, GSM roaming is on
serviceState.roaming = true cb.onServiceStateChanged(ServiceState().also { it.roaming = true })
cb.onServiceStateChanged(serviceState)
assertThat(latest).isTrue() assertThat(latest).isTrue()
@@ -541,8 +577,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
} }
@Test @Test
fun `activity - updates from callback`() = fun activity_updatesFromCallback() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: DataActivityModel? = null var latest: DataActivityModel? = null
val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this) val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
@@ -578,8 +614,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
} }
@Test @Test
fun `network name - default`() = fun networkName_default() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: NetworkNameModel? = null var latest: NetworkNameModel? = null
val job = underTest.networkName.onEach { latest = it }.launchIn(this) val job = underTest.networkName.onEach { latest = it }.launchIn(this)
@@ -589,8 +625,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
} }
@Test @Test
fun `network name - uses broadcast info - returns derived`() = fun networkName_usesBroadcastInfo_returnsDerived() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: NetworkNameModel? = null var latest: NetworkNameModel? = null
val job = underTest.networkName.onEach { latest = it }.launchIn(this) val job = underTest.networkName.onEach { latest = it }.launchIn(this)
@@ -606,8 +642,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
} }
@Test @Test
fun `network name - broadcast not for this sub id - keeps old value`() = fun networkName_broadcastNotForThisSubId_keepsOldValue() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: NetworkNameModel? = null var latest: NetworkNameModel? = null
val job = underTest.networkName.onEach { latest = it }.launchIn(this) val job = underTest.networkName.onEach { latest = it }.launchIn(this)
@@ -631,8 +667,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
} }
@Test @Test
fun `network name - broadcast has no data - updates to default`() = fun networkName_broadcastHasNoData_updatesToDefault() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: NetworkNameModel? = null var latest: NetworkNameModel? = null
val job = underTest.networkName.onEach { latest = it }.launchIn(this) val job = underTest.networkName.onEach { latest = it }.launchIn(this)
@@ -658,8 +694,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
} }
@Test @Test
fun `operatorAlphaShort - tracked`() = fun operatorAlphaShort_tracked() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: String? = null var latest: String? = null
val job = underTest.operatorAlphaShort.onEach { latest = it }.launchIn(this) val job = underTest.operatorAlphaShort.onEach { latest = it }.launchIn(this)
@@ -680,33 +716,45 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
} }
@Test @Test
fun `connection model - isInService - not iwlan`() = fun isInService_notIwlan() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.isInService.onEach { latest = it }.launchIn(this) val job = underTest.isInService.onEach { latest = it }.launchIn(this)
val serviceState = ServiceState() getTelephonyCallbackForType<ServiceStateListener>()
serviceState.voiceRegState = STATE_IN_SERVICE .onServiceStateChanged(
serviceState.dataRegState = STATE_IN_SERVICE ServiceState().also {
it.voiceRegState = STATE_IN_SERVICE
getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState) it.dataRegState = STATE_IN_SERVICE
}
)
assertThat(latest).isTrue() assertThat(latest).isTrue()
serviceState.voiceRegState = STATE_OUT_OF_SERVICE getTelephonyCallbackForType<ServiceStateListener>()
getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState) .onServiceStateChanged(
ServiceState().also {
it.dataRegState = STATE_IN_SERVICE
it.voiceRegState = STATE_OUT_OF_SERVICE
}
)
assertThat(latest).isTrue() assertThat(latest).isTrue()
serviceState.dataRegState = STATE_OUT_OF_SERVICE getTelephonyCallbackForType<ServiceStateListener>()
getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState) .onServiceStateChanged(
ServiceState().also {
it.voiceRegState = STATE_OUT_OF_SERVICE
it.dataRegState = STATE_OUT_OF_SERVICE
}
)
assertThat(latest).isFalse() assertThat(latest).isFalse()
job.cancel() job.cancel()
} }
@Test @Test
fun `connection model - isInService - is iwlan - voice out of service - data in service`() = fun isInService_isIwlan_voiceOutOfService_dataInService() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.isInService.onEach { latest = it }.launchIn(this) val job = underTest.isInService.onEach { latest = it }.launchIn(this)
@@ -730,8 +778,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
} }
@Test @Test
fun `number of levels - uses carrier config`() = fun numberOfLevels_usesCarrierConfig() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Int? = null var latest: Int? = null
val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this) val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
@@ -756,19 +804,6 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager) return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
} }
/** Convenience constructor for SignalStrength */
private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
val signalStrength = mock<SignalStrength>()
whenever(signalStrength.isGsm).thenReturn(isGsm)
whenever(signalStrength.level).thenReturn(gsmLevel)
val cdmaStrength =
mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
.thenReturn(listOf(cdmaStrength))
return signalStrength
}
private fun spnIntent( private fun spnIntent(
subId: Int = SUB_1_ID, subId: Int = SUB_1_ID,
showSpn: Boolean = true, showSpn: Boolean = true,
@@ -785,7 +820,6 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
} }
companion object { companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
private const val SUB_1_ID = 1 private const val SUB_1_ID = 1
private val DEFAULT_NAME = NetworkNameModel.Default("default name") private val DEFAULT_NAME = NetworkNameModel.Default("default name")

View File

@@ -0,0 +1,341 @@
/*
* Copyright (C) 2023 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.data.repository.prod
import android.telephony.ServiceState
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.CarrierNetworkListener
import android.telephony.TelephonyCallback.DataActivityListener
import android.telephony.TelephonyCallback.DataConnectionStateListener
import android.telephony.TelephonyCallback.DataEnabledListener
import android.telephony.TelephonyCallback.DisplayInfoListener
import android.telephony.TelephonyCallback.ServiceStateListener
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
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.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
/**
* Test class to stress test the TelephonyCallbacks that we listen to. In particular, the callbacks
* all come back in on a single listener (for reasons defined in the system). This test is built to
* ensure that we don't miss any important callbacks.
*
* Kind of like an interaction test case build just for [TelephonyCallback]
*
* The list of telephony callbacks we use is: [TelephonyCallback.CarrierNetworkListener]
* [TelephonyCallback.DataActivityListener] [TelephonyCallback.DataConnectionStateListener]
* [TelephonyCallback.DataEnabledListener] [TelephonyCallback.DisplayInfoListener]
* [TelephonyCallback.ServiceStateListener] [TelephonyCallback.SignalStrengthsListener]
*
* Because each of these callbacks comes in on the same callbackFlow, collecting on a field backed
* by only a single callback can immediately create backpressure on the other fields related to a
* mobile connection.
*
* This test should be designed to test _at least_ each individual callback in a smoke-test fashion.
* The way we will achieve this is as follows:
* 1. Start up a listener (A) collecting on a field which is _not under test_
* 2. Send a single event to a telephony callback which supports the field under test (B)
* 3. Send many (may be as few as 2) events to the callback backing A to ensure we start seeing
* backpressure on other fields NOTE: poor handling of backpressure here would normally cause B
* to get dropped
* 4. Start up a new collector for B
* 5. Assert that B has the state sent in step #2
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
private lateinit var underTest: MobileConnectionRepositoryImpl
private lateinit var connectionsRepo: FakeMobileConnectionsRepository
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: MobileInputLogger
@Mock private lateinit var tableLogger: TableLogBuffer
private val mobileMappings = FakeMobileMappingsProxy()
private val systemUiCarrierConfig =
SystemUiCarrierConfig(
SUB_1_ID,
SystemUiCarrierConfigTest.createTestConfig(),
)
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogger)
underTest =
MobileConnectionRepositoryImpl(
context,
SUB_1_ID,
DEFAULT_NAME,
SEP,
telephonyManager,
systemUiCarrierConfig,
fakeBroadcastDispatcher,
mobileMappings,
testDispatcher,
logger,
tableLogger,
testScope.backgroundScope,
)
}
@Test
fun carrierNetworkChangeListener_noisyActivity() =
testScope.runTest {
var latest: Boolean? = null
// Start collecting data activity; don't care about the result
val activityJob = underTest.dataActivityDirection.launchIn(this)
val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
val callback = getTelephonyCallbackForType<CarrierNetworkListener>()
callback.onCarrierNetworkChange(true)
flipActivity(100, activityCallback)
val job = underTest.carrierNetworkChangeActive.onEach { latest = it }.launchIn(this)
assertThat(latest).isTrue()
activityJob.cancel()
job.cancel()
}
@Test
fun dataActivityLate_noisyDisplayInfo() =
testScope.runTest {
var latest: DataActivityModel? = null
// start collecting displayInfo; don't care about the result
val displayInfoJob = underTest.resolvedNetworkType.launchIn(this)
val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
activityCallback.onDataActivity(DATA_ACTIVITY_INOUT)
val displayInfoCallback = getTelephonyCallbackForType<DisplayInfoListener>()
val type1 = NETWORK_TYPE_UNKNOWN
val type2 = NETWORK_TYPE_LTE
val t1 =
mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type1) }
val t2 =
mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type2) }
flipDisplayInfo(100, listOf(t1, t2), displayInfoCallback)
val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
assertThat(latest)
.isEqualTo(
DataActivityModel(
hasActivityIn = true,
hasActivityOut = true,
)
)
displayInfoJob.cancel()
job.cancel()
}
@Test
fun dataConnectionStateListener_noisyActivity() =
testScope.runTest {
var latest: DataConnectionState? = null
// Start collecting data activity; don't care about the result
val activityJob = underTest.dataActivityDirection.launchIn(this)
val connectionCallback = getTelephonyCallbackForType<DataConnectionStateListener>()
val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
connectionCallback.onDataConnectionStateChanged(
TelephonyManager.DATA_CONNECTED,
200 /* unused */
)
// Send a bunch of events that we don't care about, to overrun the replay buffer
flipActivity(100, activityCallback)
val connectionJob = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
assertThat(latest).isEqualTo(DataConnectionState.Connected)
activityJob.cancel()
connectionJob.cancel()
}
@Test
fun dataEnabledLate_noisyActivity() =
testScope.runTest {
var latest: Boolean? = null
// Start collecting data activity; don't care about the result
val activityJob = underTest.dataActivityDirection.launchIn(this)
val enabledCallback = getTelephonyCallbackForType<DataEnabledListener>()
val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
enabledCallback.onDataEnabledChanged(true, 1 /* unused */)
// Send a bunch of events that we don't care about, to overrun the replay buffer
flipActivity(100, activityCallback)
val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
assertThat(latest).isTrue()
activityJob.cancel()
job.cancel()
}
@Test
fun displayInfoLate_noisyActivity() =
testScope.runTest {
var latest: ResolvedNetworkType? = null
// Start collecting data activity; don't care about the result
val activityJob = underTest.dataActivityDirection.launchIn(this)
val displayInfoCallback = getTelephonyCallbackForType<DisplayInfoListener>()
val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
val type = NETWORK_TYPE_LTE
val expected = ResolvedNetworkType.DefaultNetworkType(mobileMappings.toIconKey(type))
val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
displayInfoCallback.onDisplayInfoChanged(ti)
// Send a bunch of events that we don't care about, to overrun the replay buffer
flipActivity(100, activityCallback)
val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
assertThat(latest).isEqualTo(expected)
activityJob.cancel()
job.cancel()
}
@Test
fun serviceStateListener_noisyActivity() =
testScope.runTest {
var latest: Boolean? = null
// Start collecting data activity; don't care about the result
val activityJob = underTest.dataActivityDirection.launchIn(this)
val serviceStateCallback = getTelephonyCallbackForType<ServiceStateListener>()
val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
// isEmergencyOnly comes in
val serviceState = ServiceState()
serviceState.isEmergencyOnly = true
serviceStateCallback.onServiceStateChanged(serviceState)
flipActivity(100, activityCallback)
val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
assertThat(latest).isTrue()
activityJob.cancel()
job.cancel()
}
@Test
fun signalStrengthsListenerLate_noisyActivity() =
testScope.runTest {
var latest: Int? = null
// Start collecting data activity; don't care about the result
val activityJob = underTest.dataActivityDirection.launchIn(this)
val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
callback.onSignalStrengthsChanged(strength)
flipActivity(100, activityCallback)
val job = underTest.cdmaLevel.onEach { latest = it }.launchIn(this)
assertThat(latest).isEqualTo(2)
activityJob.cancel()
job.cancel()
}
private fun flipActivity(
times: Int,
callback: DataActivityListener,
) {
repeat(times) { index -> callback.onDataActivity(index % 4) }
}
private fun flipDisplayInfo(
times: Int,
infos: List<TelephonyDisplayInfo>,
callback: DisplayInfoListener,
) {
val len = infos.size
repeat(times) { index -> callback.onDisplayInfoChanged(infos[index % len]) }
}
private inline fun <reified T> getTelephonyCallbackForType(): T {
return getTelephonyCallbackForType(telephonyManager)
}
companion object {
private const val SUB_1_ID = 1
private val DEFAULT_NAME = NetworkNameModel.Default("default name")
private const val SEP = "-"
}
}

View File

@@ -16,10 +16,14 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.telephony.CellSignalStrengthCdma
import android.telephony.SignalStrength
import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
@@ -31,6 +35,19 @@ object MobileTelephonyHelpers {
return callbackCaptor.allValues return callbackCaptor.allValues
} }
/** Convenience constructor for SignalStrength */
fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
val signalStrength = mock<SignalStrength>()
whenever(signalStrength.isGsm).thenReturn(isGsm)
whenever(signalStrength.level).thenReturn(gsmLevel)
val cdmaStrength =
mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
.thenReturn(listOf(cdmaStrength))
return signalStrength
}
inline fun <reified T> getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T { inline fun <reified T> getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T {
val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance<T>() val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance<T>()
assertThat(cbs.size).isEqualTo(1) assertThat(cbs.size).isEqualTo(1)