[Sb refactor] Move subIdFlow into its own repository

This CL keeps the pattern of "MobileIcons/MobileIcon" to distinguish 2
layers of MobileConnection repositories here:
`MobileConnectionsRepository` and `MobileConnectionRepository`. This
allows us to use ad-hoc repository classes to track state from
`TelephonyManager` objects created using
`TelephonyManager#createForSubscriptionId`.

The intention now is that the top-level repository will track and cache
the child repos as needed, and remove its cache when the subscription
list changes such that they no longer track valid subIds. Downstream
observers will still have to be cleaned up via the UiAdapter, which will
close the observed data streams.

Test: atest MobileConnectionRepositoryTest
Bug: 240492102
Change-Id: Id53733ac8a84869a57133b2e0055105a716a219d
This commit is contained in:
Evan Laird
2022-10-11 18:03:55 -04:00
parent 4c52a3c37b
commit e6fd49588f
11 changed files with 564 additions and 353 deletions

View File

@@ -18,8 +18,8 @@ package com.android.systemui.statusbar.pipeline.dagger
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
@@ -45,9 +45,9 @@ abstract class StatusBarPipelineModule {
abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
@Binds
abstract fun mobileSubscriptionRepository(
impl: MobileSubscriptionRepositoryImpl
): MobileSubscriptionRepository
abstract fun mobileConnectionsRepository(
impl: MobileConnectionsRepositoryImpl
): MobileConnectionsRepository
@Binds
abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository

View File

@@ -0,0 +1,185 @@
/*
* Copyright (C) 2022 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
import android.telephony.CellSignalStrength
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.SubscriptionInfo
import android.telephony.TelephonyCallback
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
import android.telephony.TelephonyManager
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import java.lang.IllegalStateException
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/**
* Every mobile line of service can be identified via a [SubscriptionInfo] object. We set up a
* repository for each individual, tracked subscription via [MobileConnectionsRepository], and this
* repository is responsible for setting up a [TelephonyManager] object tied to its subscriptionId
*
* There should only ever be one [MobileConnectionRepository] per subscription, since
* [TelephonyManager] limits the number of callbacks that can be registered per process.
*
* This repository should have all of the relevant information for a single line of service, which
* eventually becomes a single icon in the status bar.
*/
interface MobileConnectionRepository {
/**
* A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
* listener + model.
*/
val subscriptionModelFlow: Flow<MobileSubscriptionModel>
}
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
class MobileConnectionRepositoryImpl(
private val subId: Int,
telephonyManager: TelephonyManager,
bgDispatcher: CoroutineDispatcher,
logger: ConnectivityPipelineLogger,
scope: CoroutineScope,
) : MobileConnectionRepository {
init {
if (telephonyManager.subscriptionId != subId) {
throw IllegalStateException(
"TelephonyManager should be created with subId($subId). " +
"Found ${telephonyManager.subscriptionId} instead."
)
}
}
override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
var state = MobileSubscriptionModel()
conflatedCallbackFlow {
// TODO (b/240569788): log all of these into the connectivity logger
val callback =
object :
TelephonyCallback(),
TelephonyCallback.ServiceStateListener,
TelephonyCallback.SignalStrengthsListener,
TelephonyCallback.DataConnectionStateListener,
TelephonyCallback.DataActivityListener,
TelephonyCallback.CarrierNetworkListener,
TelephonyCallback.DisplayInfoListener {
override fun onServiceStateChanged(serviceState: ServiceState) {
state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
trySend(state)
}
override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
val cdmaLevel =
signalStrength
.getCellSignalStrengths(CellSignalStrengthCdma::class.java)
.let { strengths ->
if (!strengths.isEmpty()) {
strengths[0].level
} else {
CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
}
}
val primaryLevel = signalStrength.level
state =
state.copy(
cdmaLevel = cdmaLevel,
primaryLevel = primaryLevel,
isGsm = signalStrength.isGsm,
)
trySend(state)
}
override fun onDataConnectionStateChanged(
dataState: Int,
networkType: Int
) {
state = state.copy(dataConnectionState = dataState)
trySend(state)
}
override fun onDataActivity(direction: Int) {
state = state.copy(dataActivityDirection = direction)
trySend(state)
}
override fun onCarrierNetworkChange(active: Boolean) {
state = state.copy(carrierNetworkChangeActive = active)
trySend(state)
}
override fun onDisplayInfoChanged(
telephonyDisplayInfo: TelephonyDisplayInfo
) {
val networkType =
if (
telephonyDisplayInfo.overrideNetworkType ==
OVERRIDE_NETWORK_TYPE_NONE
) {
DefaultNetworkType(telephonyDisplayInfo.networkType)
} else {
OverrideNetworkType(telephonyDisplayInfo.overrideNetworkType)
}
state = state.copy(resolvedNetworkType = networkType)
trySend(state)
}
}
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
}
.onEach { logger.logOutputChange("mobileSubscriptionModel", it.toString()) }
.stateIn(scope, SharingStarted.WhileSubscribed(), state)
}
class Factory
@Inject
constructor(
private val telephonyManager: TelephonyManager,
private val logger: ConnectivityPipelineLogger,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
) {
fun build(subId: Int): MobileConnectionRepository {
return MobileConnectionRepositoryImpl(
subId,
telephonyManager.createForSubscriptionId(subId),
bgDispatcher,
logger,
scope,
)
}
}
}

View File

@@ -19,22 +19,10 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.content.Context
import android.content.IntentFilter
import android.telephony.CarrierConfigManager
import android.telephony.CellSignalStrength
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyCallback.CarrierNetworkListener
import android.telephony.TelephonyCallback.DataActivityListener
import android.telephony.TelephonyCallback.DataConnectionStateListener
import android.telephony.TelephonyCallback.DisplayInfoListener
import android.telephony.TelephonyCallback.ServiceStateListener
import android.telephony.TelephonyCallback.SignalStrengthsListener
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
import android.telephony.TelephonyManager
import androidx.annotation.VisibleForTesting
import com.android.settingslib.mobile.MobileMappings
@@ -44,10 +32,6 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -68,7 +52,7 @@ import kotlinx.coroutines.withContext
* Repo for monitoring the complete active subscription info list, to be consumed and filtered based
* on various policy
*/
interface MobileSubscriptionRepository {
interface MobileConnectionsRepository {
/** Observable list of current mobile subscriptions */
val subscriptionsFlow: Flow<List<SubscriptionInfo>>
@@ -78,14 +62,14 @@ interface MobileSubscriptionRepository {
/** Observable for [MobileMappings.Config] tracking the defaults */
val defaultDataSubRatConfig: StateFlow<Config>
/** Get or create an observable for the given subscription ID */
fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel>
/** Get or create a repository for the line of service for the given subscription ID */
fun getRepoForSubId(subId: Int): MobileConnectionRepository
}
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class MobileSubscriptionRepositoryImpl
class MobileConnectionsRepositoryImpl
@Inject
constructor(
private val subscriptionManager: SubscriptionManager,
@@ -93,11 +77,11 @@ constructor(
private val logger: ConnectivityPipelineLogger,
broadcastDispatcher: BroadcastDispatcher,
private val context: Context,
private val mobileMappings: MobileMappingsProxy,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
) : MobileSubscriptionRepository {
private val subIdFlowCache: MutableMap<Int, StateFlow<MobileSubscriptionModel>> = mutableMapOf()
private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory
) : MobileConnectionsRepository {
private val subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf()
/**
* State flow that emits the set of mobile data subscriptions, each represented by its own
@@ -121,6 +105,7 @@ constructor(
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
}
.mapLatest { fetchSubscriptionsList() }
.onEach { infos -> dropUnusedReposFromCache(infos) }
.stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
/** StateFlow that keeps track of the current active mobile data subscription */
@@ -172,103 +157,43 @@ constructor(
initialValue = Config.readConfig(context)
)
/**
* Each mobile subscription needs its own flow, which comes from registering listeners on the
* system. Use this method to create those flows and cache them for reuse
*/
override fun getFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> {
return subIdFlowCache[subId]
?: createFlowForSubId(subId).also { subIdFlowCache[subId] = it }
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
if (!isValidSubId(subId)) {
throw IllegalArgumentException(
"subscriptionId $subId is not in the list of valid subscriptions"
)
}
return subIdRepositoryCache[subId]
?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
}
@VisibleForTesting fun getSubIdFlowCache() = subIdFlowCache
private fun createFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> = run {
var state = MobileSubscriptionModel()
conflatedCallbackFlow {
val phony = telephonyManager.createForSubscriptionId(subId)
// TODO (b/240569788): log all of these into the connectivity logger
val callback =
object :
TelephonyCallback(),
ServiceStateListener,
SignalStrengthsListener,
DataConnectionStateListener,
DataActivityListener,
CarrierNetworkListener,
DisplayInfoListener {
override fun onServiceStateChanged(serviceState: ServiceState) {
state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
trySend(state)
}
override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
val cdmaLevel =
signalStrength
.getCellSignalStrengths(CellSignalStrengthCdma::class.java)
.let { strengths ->
if (!strengths.isEmpty()) {
strengths[0].level
} else {
CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
}
}
val primaryLevel = signalStrength.level
state =
state.copy(
cdmaLevel = cdmaLevel,
primaryLevel = primaryLevel,
isGsm = signalStrength.isGsm,
)
trySend(state)
}
override fun onDataConnectionStateChanged(
dataState: Int,
networkType: Int
) {
state = state.copy(dataConnectionState = dataState)
trySend(state)
}
override fun onDataActivity(direction: Int) {
state = state.copy(dataActivityDirection = direction)
trySend(state)
}
override fun onCarrierNetworkChange(active: Boolean) {
state = state.copy(carrierNetworkChangeActive = active)
trySend(state)
}
override fun onDisplayInfoChanged(
telephonyDisplayInfo: TelephonyDisplayInfo
) {
val networkType =
if (
telephonyDisplayInfo.overrideNetworkType ==
OVERRIDE_NETWORK_TYPE_NONE
) {
DefaultNetworkType(telephonyDisplayInfo.networkType)
} else {
OverrideNetworkType(telephonyDisplayInfo.overrideNetworkType)
}
state = state.copy(resolvedNetworkType = networkType)
trySend(state)
}
}
phony.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
awaitClose {
phony.unregisterTelephonyCallback(callback)
// Release the cached flow
subIdFlowCache.remove(subId)
}
private fun isValidSubId(subId: Int): Boolean {
subscriptionsFlow.value.forEach {
if (it.subscriptionId == subId) {
return true
}
.onEach { logger.logOutputChange("mobileSubscriptionModel", it.toString()) }
.stateIn(scope, SharingStarted.WhileSubscribed(), state)
}
return false
}
@VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
return mobileConnectionRepositoryFactory.build(subId)
}
private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
// Remove any connection repository from the cache that isn't in the new set of IDs. They
// will get garbage collected once their subscribers go away
val currentValidSubscriptionIds = newInfos.map { it.subscriptionId }
subIdRepositoryCache.keys.forEach {
if (!currentValidSubscriptionIds.contains(it)) {
subIdRepositoryCache.remove(it)
}
}
}
private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =

View File

@@ -19,8 +19,8 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CarrierConfigManager
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
import kotlinx.coroutines.flow.Flow
@@ -50,8 +50,10 @@ class MobileIconInteractorImpl(
defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>,
defaultMobileIconGroup: Flow<MobileIconGroup>,
mobileMappingsProxy: MobileMappingsProxy,
mobileStatusInfo: Flow<MobileSubscriptionModel>,
connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor {
private val mobileStatusInfo = connectionRepository.subscriptionModelFlow
/** Observable for the current RAT indicator icon ([MobileIconGroup]) */
override val networkTypeIconGroup: Flow<MobileIconGroup> =
combine(

View File

@@ -23,8 +23,7 @@ import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
@@ -59,7 +58,7 @@ interface MobileIconsInteractor {
class MobileIconsInteractorImpl
@Inject
constructor(
private val mobileSubscriptionRepo: MobileSubscriptionRepository,
private val mobileSubscriptionRepo: MobileConnectionsRepository,
private val carrierConfigTracker: CarrierConfigTracker,
private val mobileMappingsProxy: MobileMappingsProxy,
userSetupRepo: UserSetupRepository,
@@ -138,12 +137,6 @@ constructor(
defaultMobileIconMapping,
defaultMobileIconGroup,
mobileMappingsProxy,
mobileSubscriptionFlowForSubId(subId),
mobileSubscriptionRepo.getRepoForSubId(subId),
)
/**
* Create a new flow for a given subscription ID, which usually maps 1:1 with mobile connections
*/
private fun mobileSubscriptionFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> =
mobileSubscriptionRepo.getFlowForSubId(subId)
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2022 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
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileConnectionRepository : MobileConnectionRepository {
private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel())
override val subscriptionModelFlow: Flow<MobileSubscriptionModel> = _subscriptionsModelFlow
fun setMobileSubscriptionModel(model: MobileSubscriptionModel) {
_subscriptionsModelFlow.value = model
}
}

View File

@@ -19,11 +19,10 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileSubscriptionRepository : MobileSubscriptionRepository {
class FakeMobileConnectionsRepository : MobileConnectionsRepository {
private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
@@ -34,10 +33,9 @@ class FakeMobileSubscriptionRepository : MobileSubscriptionRepository {
private val _defaultDataSubRatConfig = MutableStateFlow(Config())
override val defaultDataSubRatConfig = _defaultDataSubRatConfig
private val subIdFlows = mutableMapOf<Int, MutableStateFlow<MobileSubscriptionModel>>()
override fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> {
return subIdFlows[subId]
?: MutableStateFlow(MobileSubscriptionModel()).also { subIdFlows[subId] = it }
private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it }
}
fun setSubscriptions(subs: List<SubscriptionInfo>) {
@@ -52,8 +50,7 @@ class FakeMobileSubscriptionRepository : MobileSubscriptionRepository {
_activeMobileDataSubscriptionId.value = subId
}
fun setMobileSubscriptionModel(model: MobileSubscriptionModel, subId: Int) {
val subscription = subIdFlows[subId] ?: throw Exception("no flow exists for this subId yet")
subscription.value = model
fun setMobileConnectionRepositoryForId(subId: Int, repo: MobileConnectionRepository) {
subIdRepos[subId] = repo
}
}

View File

@@ -22,13 +22,7 @@ import android.telephony.SignalStrength
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyCallback.CarrierNetworkListener
import android.telephony.TelephonyCallback.DataActivityListener
import android.telephony.TelephonyCallback.DataConnectionStateListener
import android.telephony.TelephonyCallback.DisplayInfoListener
import android.telephony.TelephonyCallback.ServiceStateListener
import android.telephony.TelephonyCallback.SignalStrengthsListener
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
import android.telephony.TelephonyManager
@@ -36,70 +30,52 @@ 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.broadcast.BroadcastDispatcher
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class MobileSubscriptionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileSubscriptionRepositoryImpl
class MobileConnectionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileConnectionRepositoryImpl
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
private val scope = CoroutineScope(IMMEDIATE)
private val mobileMappings = FakeMobileMappingsProxy()
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(
broadcastDispatcher.broadcastFlow(
any(),
nullable(),
ArgumentMatchers.anyInt(),
nullable(),
)
)
.thenReturn(flowOf(Unit))
whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
underTest =
MobileSubscriptionRepositoryImpl(
subscriptionManager,
MobileConnectionRepositoryImpl(
SUB_1_ID,
telephonyManager,
logger,
broadcastDispatcher,
context,
mobileMappings,
IMMEDIATE,
logger,
scope,
)
}
@@ -109,79 +85,11 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
scope.cancel()
}
@Test
fun testSubscriptions_initiallyEmpty() =
runBlocking(IMMEDIATE) {
assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
}
@Test
fun testSubscriptions_listUpdates() =
runBlocking(IMMEDIATE) {
var latest: List<SubscriptionInfo>? = null
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2))
getSubscriptionCallback().onSubscriptionsChanged()
assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
job.cancel()
}
@Test
fun testSubscriptions_removingSub_updatesList() =
runBlocking(IMMEDIATE) {
var latest: List<SubscriptionInfo>? = null
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
// WHEN 2 networks show up
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2))
getSubscriptionCallback().onSubscriptionsChanged()
// WHEN one network is removed
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_2))
getSubscriptionCallback().onSubscriptionsChanged()
// THEN the subscriptions list represents the newest change
assertThat(latest).isEqualTo(listOf(SUB_2))
job.cancel()
}
@Test
fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
runBlocking(IMMEDIATE) {
assertThat(underTest.activeMobileDataSubscriptionId.value)
.isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
}
@Test
fun testActiveDataSubscriptionId_updates() =
runBlocking(IMMEDIATE) {
var active: Int? = null
val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
getActiveDataSubscriptionCallback().onActiveDataSubscriptionIdChanged(SUB_2_ID)
assertThat(active).isEqualTo(SUB_2_ID)
job.cancel()
}
@Test
fun testFlowForSubId_default() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
var latest: MobileSubscriptionModel? = null
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
assertThat(latest).isEqualTo(MobileSubscriptionModel())
@@ -191,10 +99,8 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_emergencyOnly() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
var latest: MobileSubscriptionModel? = null
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
val serviceState = ServiceState()
serviceState.isEmergencyOnly = true
@@ -209,10 +115,8 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_emergencyOnly_toggles() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
var latest: MobileSubscriptionModel? = null
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<ServiceStateListener>()
val serviceState = ServiceState()
@@ -229,13 +133,11 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_signalStrengths_levelsUpdate() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
var latest: MobileSubscriptionModel? = null
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<SignalStrengthsListener>()
val strength = signalStrength(1, 2, true)
val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
callback.onSignalStrengthsChanged(strength)
assertThat(latest?.isGsm).isEqualTo(true)
@@ -248,12 +150,11 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_dataConnectionState() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
var latest: MobileSubscriptionModel? = null
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<DataConnectionStateListener>()
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(100, 200 /* unused */)
assertThat(latest?.dataConnectionState).isEqualTo(100)
@@ -264,12 +165,10 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_dataActivity() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
var latest: MobileSubscriptionModel? = null
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<DataActivityListener>()
val callback = getTelephonyCallbackForType<TelephonyCallback.DataActivityListener>()
callback.onDataActivity(3)
assertThat(latest?.dataActivityDirection).isEqualTo(3)
@@ -280,12 +179,10 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_carrierNetworkChange() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
var latest: MobileSubscriptionModel? = null
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<CarrierNetworkListener>()
val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>()
callback.onCarrierNetworkChange(true)
assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
@@ -294,12 +191,10 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
}
@Test
fun testFlowForSubId_defaultNetworkType() =
fun subscriptionFlow_networkType_default() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
var latest: MobileSubscriptionModel? = null
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
val type = NETWORK_TYPE_UNKNOWN
val expected = DefaultNetworkType(type)
@@ -310,14 +205,12 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
}
@Test
fun testFlowForSubId_networkTypeUpdates_default() =
fun subscriptionFlow_networkType_updatesUsingDefault() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
var latest: MobileSubscriptionModel? = null
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<DisplayInfoListener>()
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = NETWORK_TYPE_LTE
val expected = DefaultNetworkType(type)
val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
@@ -329,14 +222,12 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
}
@Test
fun testFlowForSubId_networkTypeUpdates_override() =
fun subscriptionFlow_networkType_updatesUsingOverride() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
var latest: MobileSubscriptionModel? = null
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<DisplayInfoListener>()
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = OVERRIDE_NETWORK_TYPE_LTE_CA
val expected = OverrideNetworkType(type)
val ti =
@@ -350,49 +241,9 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
job.cancel()
}
@Test
fun testFlowForSubId_isCached() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
val state1 = underTest.getFlowForSubId(SUB_1_ID)
val state2 = underTest.getFlowForSubId(SUB_1_ID)
assertThat(state1).isEqualTo(state2)
}
@Test
fun testFlowForSubId_isRemovedAfterFinish() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
var latest: MobileSubscriptionModel? = null
// Start collecting on some flow
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
// There should be once cached flow now
assertThat(underTest.getSubIdFlowCache().size).isEqualTo(1)
// When the job is canceled, the cache should be cleared
job.cancel()
assertThat(underTest.getSubIdFlowCache().size).isEqualTo(0)
}
private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
verify(subscriptionManager)
.addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
return callbackCaptor.value!!
}
private fun getActiveDataSubscriptionCallback(): ActiveDataSubscriptionIdListener =
getTelephonyCallbackForType()
private fun getTelephonyCallbacks(): List<TelephonyCallback> {
val callbackCaptor = argumentCaptor<TelephonyCallback>()
verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
return callbackCaptor.allValues
}
@@ -420,9 +271,5 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
private const val SUB_1_ID = 1
private val SUB_1 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
private const val SUB_2_ID = 2
private val SUB_2 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
}
}

View File

@@ -0,0 +1,246 @@
/*
* Copyright (C) 2022 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
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class MobileConnectionsRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileConnectionsRepositoryImpl
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
private val scope = CoroutineScope(IMMEDIATE)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(
broadcastDispatcher.broadcastFlow(
any(),
nullable(),
ArgumentMatchers.anyInt(),
nullable(),
)
)
.thenReturn(flowOf(Unit))
underTest =
MobileConnectionsRepositoryImpl(
subscriptionManager,
telephonyManager,
logger,
broadcastDispatcher,
context,
IMMEDIATE,
scope,
mock(),
)
}
@After
fun tearDown() {
scope.cancel()
}
@Test
fun testSubscriptions_initiallyEmpty() =
runBlocking(IMMEDIATE) {
assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
}
@Test
fun testSubscriptions_listUpdates() =
runBlocking(IMMEDIATE) {
var latest: List<SubscriptionInfo>? = null
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2))
getSubscriptionCallback().onSubscriptionsChanged()
assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
job.cancel()
}
@Test
fun testSubscriptions_removingSub_updatesList() =
runBlocking(IMMEDIATE) {
var latest: List<SubscriptionInfo>? = null
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
// WHEN 2 networks show up
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2))
getSubscriptionCallback().onSubscriptionsChanged()
// WHEN one network is removed
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_2))
getSubscriptionCallback().onSubscriptionsChanged()
// THEN the subscriptions list represents the newest change
assertThat(latest).isEqualTo(listOf(SUB_2))
job.cancel()
}
@Test
fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
runBlocking(IMMEDIATE) {
assertThat(underTest.activeMobileDataSubscriptionId.value)
.isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
}
@Test
fun testActiveDataSubscriptionId_updates() =
runBlocking(IMMEDIATE) {
var active: Int? = null
val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
.onActiveDataSubscriptionIdChanged(SUB_2_ID)
assertThat(active).isEqualTo(SUB_2_ID)
job.cancel()
}
@Test
fun testConnectionRepository_validSubId_isCached() =
runBlocking(IMMEDIATE) {
val job = underTest.subscriptionsFlow.launchIn(this)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1))
getSubscriptionCallback().onSubscriptionsChanged()
val repo1 = underTest.getRepoForSubId(SUB_1_ID)
val repo2 = underTest.getRepoForSubId(SUB_1_ID)
assertThat(repo1).isSameInstanceAs(repo2)
job.cancel()
}
@Test
fun testConnectionCache_clearsInvalidSubscriptions() =
runBlocking(IMMEDIATE) {
val job = underTest.subscriptionsFlow.launchIn(this)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2))
getSubscriptionCallback().onSubscriptionsChanged()
// Get repos to trigger caching
val repo1 = underTest.getRepoForSubId(SUB_1_ID)
val repo2 = underTest.getRepoForSubId(SUB_2_ID)
assertThat(underTest.getSubIdRepoCache())
.containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
// SUB_2 disappears
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1))
getSubscriptionCallback().onSubscriptionsChanged()
assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
job.cancel()
}
@Test
fun testConnectionRepository_invalidSubId_throws() =
runBlocking(IMMEDIATE) {
val job = underTest.subscriptionsFlow.launchIn(this)
assertThrows(IllegalArgumentException::class.java) {
underTest.getRepoForSubId(SUB_1_ID)
}
job.cancel()
}
private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
verify(subscriptionManager)
.addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
return callbackCaptor.value!!
}
private fun getTelephonyCallbacks(): List<TelephonyCallback> {
val callbackCaptor = argumentCaptor<TelephonyCallback>()
verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
return callbackCaptor.allValues
}
private inline fun <reified T> getTelephonyCallbackForType(): T {
val cbs = getTelephonyCallbacks().filterIsInstance<T>()
assertThat(cbs.size).isEqualTo(1)
return cbs[0]
}
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
private const val SUB_1_ID = 1
private val SUB_1 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
private const val SUB_2_ID = 2
private val SUB_2 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
}
}

View File

@@ -26,7 +26,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FIVE_G_OVERRIDE
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G
@@ -45,10 +45,9 @@ import org.junit.Test
@SmallTest
class MobileIconInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconInteractor
private val mobileSubscriptionRepository = FakeMobileSubscriptionRepository()
private val mobileMappingsProxy = FakeMobileMappingsProxy()
private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
private val sub1Flow = mobileSubscriptionRepository.getFlowForSubId(SUB_1_ID)
private val connectionRepository = FakeMobileConnectionRepository()
@Before
fun setUp() {
@@ -57,16 +56,15 @@ class MobileIconInteractorTest : SysuiTestCase() {
mobileIconsInteractor.defaultMobileIconMapping,
mobileIconsInteractor.defaultMobileIconGroup,
mobileMappingsProxy,
sub1Flow,
connectionRepository,
)
}
@Test
fun gsm_level_default_unknown() =
runBlocking(IMMEDIATE) {
mobileSubscriptionRepository.setMobileSubscriptionModel(
connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(isGsm = true),
SUB_1_ID
)
var latest: Int? = null
@@ -80,13 +78,12 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun gsm_usesGsmLevel() =
runBlocking(IMMEDIATE) {
mobileSubscriptionRepository.setMobileSubscriptionModel(
connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(
isGsm = true,
primaryLevel = GSM_LEVEL,
cdmaLevel = CDMA_LEVEL
),
SUB_1_ID
)
var latest: Int? = null
@@ -100,9 +97,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun cdma_level_default_unknown() =
runBlocking(IMMEDIATE) {
mobileSubscriptionRepository.setMobileSubscriptionModel(
connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(isGsm = false),
SUB_1_ID
)
var latest: Int? = null
@@ -115,13 +111,12 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun cdma_usesCdmaLevel() =
runBlocking(IMMEDIATE) {
mobileSubscriptionRepository.setMobileSubscriptionModel(
connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(
isGsm = false,
primaryLevel = GSM_LEVEL,
cdmaLevel = CDMA_LEVEL
),
SUB_1_ID
)
var latest: Int? = null
@@ -135,9 +130,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun iconGroup_three_g() =
runBlocking(IMMEDIATE) {
mobileSubscriptionRepository.setMobileSubscriptionModel(
connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)),
SUB_1_ID
)
var latest: MobileIconGroup? = null
@@ -151,19 +145,17 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun iconGroup_updates_on_change() =
runBlocking(IMMEDIATE) {
mobileSubscriptionRepository.setMobileSubscriptionModel(
connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)),
SUB_1_ID
)
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
mobileSubscriptionRepository.setMobileSubscriptionModel(
connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(
resolvedNetworkType = DefaultNetworkType(FOUR_G),
),
SUB_1_ID
)
yield()
@@ -175,9 +167,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun iconGroup_5g_override_type() =
runBlocking(IMMEDIATE) {
mobileSubscriptionRepository.setMobileSubscriptionModel(
connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(resolvedNetworkType = OverrideNetworkType(FIVE_G_OVERRIDE)),
SUB_1_ID
)
var latest: MobileIconGroup? = null
@@ -191,11 +182,10 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun iconGroup_default_if_no_lookup() =
runBlocking(IMMEDIATE) {
mobileSubscriptionRepository.setMobileSubscriptionModel(
connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(
resolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN),
),
SUB_1_ID
)
var latest: MobileIconGroup? = null
@@ -215,9 +205,5 @@ class MobileIconInteractorTest : SysuiTestCase() {
private const val SUB_1_ID = 1
private val SUB_1 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
private const val SUB_2_ID = 2
private val SUB_2 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
}
}

View File

@@ -19,7 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.SubscriptionInfo
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
@@ -41,7 +41,7 @@ import org.mockito.MockitoAnnotations
class MobileIconsInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsInteractor
private val userSetupRepository = FakeUserSetupRepository()
private val subscriptionsRepository = FakeMobileSubscriptionRepository()
private val subscriptionsRepository = FakeMobileConnectionsRepository()
private val mobileMappingsProxy = FakeMobileMappingsProxy()
private val scope = CoroutineScope(IMMEDIATE)