Merge changes I9414c0b2,I649b9198 into tm-qpr-dev am: aa972051b0

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20855888

Change-Id: I481e4d25d72536f7829b9d3da9bd9f7e6cfb1196
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Caitlin Shkuratov
2023-01-12 17:14:41 +00:00
committed by Automerger Merge Worker
26 changed files with 2008 additions and 165 deletions

View File

@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model
import android.telephony.Annotation.NetworkType
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
/**
@@ -38,4 +40,12 @@ sealed interface ResolvedNetworkType {
data class OverrideNetworkType(
override val lookupKey: String,
) : ResolvedNetworkType
/** Represents the carrier merged network. See [CarrierMergedConnectionRepository]. */
object CarrierMergedNetworkType : ResolvedNetworkType {
// Effectively unused since [iconGroupOverride] is used instead.
override val lookupKey: String = "cwf"
val iconGroupOverride: SignalIcon.MobileIconGroup = TelephonyIcons.CARRIER_MERGED_WIFI
}
}

View File

@@ -22,7 +22,6 @@ import android.telephony.TelephonyManager
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
/**
@@ -50,7 +49,7 @@ interface MobileConnectionRepository {
* A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
* listener + model.
*/
val connectionInfo: Flow<MobileConnectionModel>
val connectionInfo: StateFlow<MobileConnectionModel>
/** The total number of levels. Used with [SignalDrawable]. */
val numberOfLevels: StateFlow<Int>

View File

@@ -39,7 +39,11 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConn
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.CarrierMergedConnectionRepository.Companion.createCarrierMergedConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.MOBILE_CONNECTION_BUFFER_SIZE
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,15 +64,19 @@ import kotlinx.coroutines.launch
class DemoMobileConnectionsRepository
@Inject
constructor(
private val dataSource: DemoModeMobileConnectionDataSource,
private val mobileDataSource: DemoModeMobileConnectionDataSource,
private val wifiDataSource: DemoModeWifiDataSource,
@Application private val scope: CoroutineScope,
context: Context,
private val logFactory: TableLogBufferFactory,
) : MobileConnectionsRepository {
private var demoCommandJob: Job? = null
private var mobileDemoCommandJob: Job? = null
private var wifiDemoCommandJob: Job? = null
private var connectionRepoCache = mutableMapOf<Int, DemoMobileConnectionRepository>()
private var carrierMergedSubId: Int? = null
private var connectionRepoCache = mutableMapOf<Int, CacheContainer>()
private val subscriptionInfoCache = mutableMapOf<Int, SubscriptionModel>()
val demoModeFinishedEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
@@ -144,52 +152,83 @@ constructor(
override val defaultMobileNetworkConnectivity = MutableStateFlow(MobileConnectivityModel())
override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
return connectionRepoCache[subId]
?: createDemoMobileConnectionRepo(subId).also { connectionRepoCache[subId] = it }
val current = connectionRepoCache[subId]?.repo
if (current != null) {
return current
}
val new = createDemoMobileConnectionRepo(subId)
connectionRepoCache[subId] = new
return new.repo
}
private fun createDemoMobileConnectionRepo(subId: Int): DemoMobileConnectionRepository {
val tableLogBuffer = logFactory.getOrCreate("DemoMobileConnectionLog [$subId]", 100)
private fun createDemoMobileConnectionRepo(subId: Int): CacheContainer {
val tableLogBuffer =
logFactory.getOrCreate(
"DemoMobileConnectionLog [$subId]",
MOBILE_CONNECTION_BUFFER_SIZE,
)
return DemoMobileConnectionRepository(
subId,
tableLogBuffer,
)
val repo =
DemoMobileConnectionRepository(
subId,
tableLogBuffer,
)
return CacheContainer(repo, lastMobileState = null)
}
override val globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
fun startProcessingCommands() {
demoCommandJob =
mobileDemoCommandJob =
scope.launch {
dataSource.mobileEvents.filterNotNull().collect { event -> processEvent(event) }
mobileDataSource.mobileEvents.filterNotNull().collect { event ->
processMobileEvent(event)
}
}
wifiDemoCommandJob =
scope.launch {
wifiDataSource.wifiEvents.filterNotNull().collect { event ->
processWifiEvent(event)
}
}
}
fun stopProcessingCommands() {
demoCommandJob?.cancel()
mobileDemoCommandJob?.cancel()
wifiDemoCommandJob?.cancel()
_subscriptions.value = listOf()
connectionRepoCache.clear()
subscriptionInfoCache.clear()
}
private fun processEvent(event: FakeNetworkEventModel) {
private fun processMobileEvent(event: FakeNetworkEventModel) {
when (event) {
is Mobile -> {
processEnabledMobileState(event)
}
is MobileDisabled -> {
processDisabledMobileState(event)
maybeRemoveSubscription(event.subId)
}
}
}
private fun processWifiEvent(event: FakeWifiEventModel) {
when (event) {
is FakeWifiEventModel.WifiDisabled -> disableCarrierMerged()
is FakeWifiEventModel.Wifi -> disableCarrierMerged()
is FakeWifiEventModel.CarrierMerged -> processCarrierMergedWifiState(event)
}
}
private fun processEnabledMobileState(state: Mobile) {
// get or create the connection repo, and set its values
val subId = state.subId ?: DEFAULT_SUB_ID
maybeCreateSubscription(subId)
val connection = getRepoForSubId(subId)
connectionRepoCache[subId]?.lastMobileState = state
// This is always true here, because we split out disabled states at the data-source level
connection.dataEnabled.value = true
connection.networkName.value = NetworkNameModel.Derived(state.name)
@@ -198,14 +237,36 @@ constructor(
connection.connectionInfo.value = state.toMobileConnectionModel()
}
private fun processDisabledMobileState(state: MobileDisabled) {
private fun processCarrierMergedWifiState(event: FakeWifiEventModel.CarrierMerged) {
// The new carrier merged connection is for a different sub ID, so disable carrier merged
// for the current (now old) sub
if (carrierMergedSubId != event.subscriptionId) {
disableCarrierMerged()
}
// get or create the connection repo, and set its values
val subId = event.subscriptionId
maybeCreateSubscription(subId)
carrierMergedSubId = subId
val connection = getRepoForSubId(subId)
// This is always true here, because we split out disabled states at the data-source level
connection.dataEnabled.value = true
connection.networkName.value = NetworkNameModel.Derived(CARRIER_MERGED_NAME)
connection.numberOfLevels.value = event.numberOfLevels
connection.cdmaRoaming.value = false
connection.connectionInfo.value = event.toMobileConnectionModel()
Log.e("CCS", "output connection info = ${connection.connectionInfo.value}")
}
private fun maybeRemoveSubscription(subId: Int?) {
if (_subscriptions.value.isEmpty()) {
// Nothing to do here
return
}
val subId =
state.subId
val finalSubId =
subId
?: run {
// For sake of usability, we can allow for no subId arg if there is only one
// subscription
@@ -223,7 +284,21 @@ constructor(
_subscriptions.value[0].subscriptionId
}
removeSubscription(subId)
removeSubscription(finalSubId)
}
private fun disableCarrierMerged() {
val currentCarrierMergedSubId = carrierMergedSubId ?: return
// If this sub ID was previously not carrier merged, we should reset it to its previous
// connection.
val lastMobileState = connectionRepoCache[carrierMergedSubId]?.lastMobileState
if (lastMobileState != null) {
processEnabledMobileState(lastMobileState)
} else {
// Otherwise, just remove the subscription entirely
removeSubscription(currentCarrierMergedSubId)
}
}
private fun removeSubscription(subId: Int) {
@@ -251,6 +326,10 @@ constructor(
)
}
private fun FakeWifiEventModel.CarrierMerged.toMobileConnectionModel(): MobileConnectionModel {
return createCarrierMergedConnectionModel(this.level)
}
private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType {
val key = mobileMappingsReverseLookup.value[this] ?: "dis"
return DefaultNetworkType(key)
@@ -260,9 +339,17 @@ constructor(
private const val TAG = "DemoMobileConnectionsRepo"
private const val DEFAULT_SUB_ID = 1
private const val CARRIER_MERGED_NAME = "Carrier Merged Network"
}
}
class CacheContainer(
var repo: DemoMobileConnectionRepository,
/** The last received [Mobile] event. Used when switching from carrier merged back to mobile. */
var lastMobileState: Mobile?,
)
class DemoMobileConnectionRepository(
override val subId: Int,
override val tableLogBuffer: TableLogBuffer,

View File

@@ -0,0 +1,181 @@
/*
* 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.prod
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
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.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/**
* A repository implementation for a carrier merged (aka VCN) network. A carrier merged network is
* delivered to SysUI as a wifi network (see [WifiNetworkModel.CarrierMerged], but is visually
* displayed as a mobile network triangle.
*
* See [android.net.wifi.WifiInfo.isCarrierMerged] for more information.
*
* See [MobileConnectionRepositoryImpl] for a repository implementation of a typical mobile
* connection.
*/
class CarrierMergedConnectionRepository(
override val subId: Int,
override val tableLogBuffer: TableLogBuffer,
defaultNetworkName: NetworkNameModel,
@Application private val scope: CoroutineScope,
val wifiRepository: WifiRepository,
) : MobileConnectionRepository {
/**
* Outputs the carrier merged network to use, or null if we don't have a valid carrier merged
* network.
*/
private val network: Flow<WifiNetworkModel.CarrierMerged?> =
combine(
wifiRepository.isWifiEnabled,
wifiRepository.isWifiDefault,
wifiRepository.wifiNetwork,
) { isEnabled, isDefault, network ->
when {
!isEnabled -> null
!isDefault -> null
network !is WifiNetworkModel.CarrierMerged -> null
network.subscriptionId != subId -> {
Log.w(
TAG,
"Connection repo subId=$subId " +
"does not equal wifi repo subId=${network.subscriptionId}; " +
"not showing carrier merged"
)
null
}
else -> network
}
}
override val connectionInfo: StateFlow<MobileConnectionModel> =
network
.map { it.toMobileConnectionModel() }
.stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectionModel())
// TODO(b/238425913): Add logging to this class.
// TODO(b/238425913): Make sure SignalStrength.getEmptyState is used when appropriate.
// Carrier merged is never roaming.
override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
// TODO(b/238425913): Fetch the carrier merged network name.
override val networkName: StateFlow<NetworkNameModel> =
flowOf(defaultNetworkName)
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
override val numberOfLevels: StateFlow<Int> =
wifiRepository.wifiNetwork
.map {
if (it is WifiNetworkModel.CarrierMerged) {
it.numberOfLevels
} else {
DEFAULT_NUM_LEVELS
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS)
override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
private fun WifiNetworkModel.CarrierMerged?.toMobileConnectionModel(): MobileConnectionModel {
if (this == null) {
return MobileConnectionModel()
}
return createCarrierMergedConnectionModel(level)
}
companion object {
/**
* Creates an instance of [MobileConnectionModel] that represents a carrier merged network
* with the given [level].
*/
fun createCarrierMergedConnectionModel(level: Int): MobileConnectionModel {
return MobileConnectionModel(
primaryLevel = level,
cdmaLevel = level,
// A [WifiNetworkModel.CarrierMerged] instance is always connected.
// (A [WifiNetworkModel.Inactive] represents a disconnected network.)
dataConnectionState = DataConnectionState.Connected,
// TODO(b/238425913): This should come from [WifiRepository.wifiActivity].
dataActivityDirection =
DataActivityModel(
hasActivityIn = false,
hasActivityOut = false,
),
resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType,
// Carrier merged is never roaming
isRoaming = false,
// TODO(b/238425913): Verify that these fields never change for carrier merged.
isEmergencyOnly = false,
operatorAlphaShort = null,
isInService = true,
isGsm = false,
carrierNetworkChangeActive = false,
)
}
}
@SysUISingleton
class Factory
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val wifiRepository: WifiRepository,
) {
fun build(
subId: Int,
mobileLogger: TableLogBuffer,
defaultNetworkName: NetworkNameModel,
): MobileConnectionRepository {
return CarrierMergedConnectionRepository(
subId,
mobileLogger,
defaultNetworkName,
scope,
wifiRepository,
)
}
}
}
private const val TAG = "CarrierMergedConnectionRepository"

View File

@@ -0,0 +1,179 @@
/*
* 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.prod
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
/**
* A repository that fully implements a mobile connection.
*
* This connection could either be a typical mobile connection (see [MobileConnectionRepositoryImpl]
* or a carrier merged connection (see [CarrierMergedConnectionRepository]). This repository
* switches between the two types of connections based on whether the connection is currently
* carrier merged (see [setIsCarrierMerged]).
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
class FullMobileConnectionRepository(
override val subId: Int,
startingIsCarrierMerged: Boolean,
override val tableLogBuffer: TableLogBuffer,
private val defaultNetworkName: NetworkNameModel,
private val networkNameSeparator: String,
private val globalMobileDataSettingChangedEvent: Flow<Unit>,
@Application scope: CoroutineScope,
private val mobileRepoFactory: MobileConnectionRepositoryImpl.Factory,
private val carrierMergedRepoFactory: CarrierMergedConnectionRepository.Factory,
) : MobileConnectionRepository {
/**
* Sets whether this connection is a typical mobile connection or a carrier merged connection.
*/
fun setIsCarrierMerged(isCarrierMerged: Boolean) {
_isCarrierMerged.value = isCarrierMerged
}
/**
* Returns true if this repo is currently for a carrier merged connection and false otherwise.
*/
@VisibleForTesting fun getIsCarrierMerged() = _isCarrierMerged.value
private val _isCarrierMerged = MutableStateFlow(startingIsCarrierMerged)
private val isCarrierMerged: StateFlow<Boolean> =
_isCarrierMerged
.logDiffsForTable(
tableLogBuffer,
columnPrefix = "",
columnName = "isCarrierMerged",
initialValue = startingIsCarrierMerged,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), startingIsCarrierMerged)
private val mobileRepo: MobileConnectionRepository by lazy {
mobileRepoFactory.build(
subId,
tableLogBuffer,
defaultNetworkName,
networkNameSeparator,
globalMobileDataSettingChangedEvent,
)
}
private val carrierMergedRepo: MobileConnectionRepository by lazy {
carrierMergedRepoFactory.build(subId, tableLogBuffer, defaultNetworkName)
}
@VisibleForTesting
internal val activeRepo: StateFlow<MobileConnectionRepository> = run {
val initial =
if (startingIsCarrierMerged) {
carrierMergedRepo
} else {
mobileRepo
}
this.isCarrierMerged
.mapLatest { isCarrierMerged ->
if (isCarrierMerged) {
carrierMergedRepo
} else {
mobileRepo
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
}
override val cdmaRoaming =
activeRepo
.flatMapLatest { it.cdmaRoaming }
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaRoaming.value)
override val connectionInfo =
activeRepo
.flatMapLatest { it.connectionInfo }
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.connectionInfo.value)
override val dataEnabled =
activeRepo
.flatMapLatest { it.dataEnabled }
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.dataEnabled.value)
override val numberOfLevels =
activeRepo
.flatMapLatest { it.numberOfLevels }
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.numberOfLevels.value)
override val networkName =
activeRepo
.flatMapLatest { it.networkName }
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value)
class Factory
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val logFactory: TableLogBufferFactory,
private val mobileRepoFactory: MobileConnectionRepositoryImpl.Factory,
private val carrierMergedRepoFactory: CarrierMergedConnectionRepository.Factory,
) {
fun build(
subId: Int,
startingIsCarrierMerged: Boolean,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
globalMobileDataSettingChangedEvent: Flow<Unit>,
): FullMobileConnectionRepository {
val mobileLogger =
logFactory.getOrCreate(tableBufferLogName(subId), MOBILE_CONNECTION_BUFFER_SIZE)
return FullMobileConnectionRepository(
subId,
startingIsCarrierMerged,
mobileLogger,
defaultNetworkName,
networkNameSeparator,
globalMobileDataSettingChangedEvent,
scope,
mobileRepoFactory,
carrierMergedRepoFactory,
)
}
companion object {
/** The buffer size to use for logging. */
const val MOBILE_CONNECTION_BUFFER_SIZE = 100
/** Returns a log buffer name for a mobile connection with the given [subId]. */
fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]"
}
}
}

View File

@@ -38,7 +38,6 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -70,6 +69,10 @@ import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/**
* A repository implementation for a typical mobile connection (as opposed to a carrier merged
* connection -- see [CarrierMergedConnectionRepository]).
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
class MobileConnectionRepositoryImpl(
@@ -298,18 +301,16 @@ class MobileConnectionRepositoryImpl(
private val logger: ConnectivityPipelineLogger,
private val globalSettings: GlobalSettings,
private val mobileMappingsProxy: MobileMappingsProxy,
private val logFactory: TableLogBufferFactory,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
) {
fun build(
subId: Int,
mobileLogger: TableLogBuffer,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
globalMobileDataSettingChangedEvent: Flow<Unit>,
): MobileConnectionRepository {
val mobileLogger = logFactory.getOrCreate(tableBufferLogName(subId), 100)
return MobileConnectionRepositoryImpl(
context,
subId,
@@ -327,8 +328,4 @@ class MobileConnectionRepositoryImpl(
)
}
}
companion object {
fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]"
}
}

View File

@@ -46,11 +46,12 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -85,9 +86,14 @@ constructor(
private val context: Context,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory
// Some "wifi networks" should be rendered as a mobile connection, which is why the wifi
// repository is an input to the mobile repository.
// See [CarrierMergedConnectionRepository] for details.
wifiRepository: WifiRepository,
private val fullMobileRepoFactory: FullMobileConnectionRepository.Factory,
) : MobileConnectionsRepository {
private var subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf()
private var subIdRepositoryCache: MutableMap<Int, FullMobileConnectionRepository> =
mutableMapOf()
private val defaultNetworkName =
NetworkNameModel.Default(
@@ -97,30 +103,43 @@ constructor(
private val networkNameSeparator: String =
context.getString(R.string.status_bar_network_name_separator)
private val carrierMergedSubId: StateFlow<Int?> =
wifiRepository.wifiNetwork
.mapLatest {
if (it is WifiNetworkModel.CarrierMerged) {
it.subscriptionId
} else {
null
}
}
.distinctUntilChanged()
.stateIn(scope, started = SharingStarted.WhileSubscribed(), null)
private val mobileSubscriptionsChangeEvent: Flow<Unit> = conflatedCallbackFlow {
val callback =
object : SubscriptionManager.OnSubscriptionsChangedListener() {
override fun onSubscriptionsChanged() {
trySend(Unit)
}
}
subscriptionManager.addOnSubscriptionsChangedListener(
bgDispatcher.asExecutor(),
callback,
)
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
}
/**
* State flow that emits the set of mobile data subscriptions, each represented by its own
* [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
* info object, but for now we keep track of the infos themselves.
* [SubscriptionModel].
*/
override val subscriptions: StateFlow<List<SubscriptionModel>> =
conflatedCallbackFlow {
val callback =
object : SubscriptionManager.OnSubscriptionsChangedListener() {
override fun onSubscriptionsChanged() {
trySend(Unit)
}
}
subscriptionManager.addOnSubscriptionsChangedListener(
bgDispatcher.asExecutor(),
callback,
)
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
}
merge(mobileSubscriptionsChangeEvent, carrierMergedSubId)
.mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } }
.logInputChange(logger, "onSubscriptionsChanged")
.onEach { infos -> dropUnusedReposFromCache(infos) }
.onEach { infos -> updateRepos(infos) }
.stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
/** StateFlow that keeps track of the current active mobile data subscription */
@@ -173,7 +192,7 @@ constructor(
.distinctUntilChanged()
.logInputChange(logger, "defaultMobileIconGroup")
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository {
if (!isValidSubId(subId)) {
throw IllegalArgumentException(
"subscriptionId $subId is not in the list of valid subscriptions"
@@ -251,15 +270,27 @@ constructor(
@VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
return mobileConnectionRepositoryFactory.build(
private fun createRepositoryForSubId(subId: Int): FullMobileConnectionRepository {
return fullMobileRepoFactory.build(
subId,
isCarrierMerged(subId),
defaultNetworkName,
networkNameSeparator,
globalMobileDataSettingChangedEvent,
)
}
private fun updateRepos(newInfos: List<SubscriptionModel>) {
dropUnusedReposFromCache(newInfos)
subIdRepositoryCache.forEach { (subId, repo) ->
repo.setIsCarrierMerged(isCarrierMerged(subId))
}
}
private fun isCarrierMerged(subId: Int): Boolean {
return subId == carrierMergedSubId.value
}
private fun dropUnusedReposFromCache(newInfos: List<SubscriptionModel>) {
// 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

View File

@@ -22,8 +22,8 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
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.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -138,7 +138,11 @@ class MobileIconInteractorImpl(
defaultMobileIconMapping,
defaultMobileIconGroup,
) { info, mapping, defaultGroup ->
mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup
when (info.resolvedNetworkType) {
is ResolvedNetworkType.CarrierMergedNetworkType ->
info.resolvedNetworkType.iconGroupOverride
else -> mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)

View File

@@ -16,13 +16,18 @@
package com.android.systemui.statusbar.pipeline.wifi.data.model
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.annotation.VisibleForTesting
import com.android.systemui.log.table.TableRowLogger
import com.android.systemui.log.table.Diffable
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
/** Provides information about the current wifi network. */
sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
// TODO(b/238425913): Have a better, more unified strategy for diff-logging instead of
// copy-pasting the column names for each sub-object.
/**
* A model representing that we couldn't fetch any wifi information.
*
@@ -41,8 +46,43 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
override fun logFull(row: TableRowLogger) {
row.logChange(COL_NETWORK_TYPE, TYPE_UNAVAILABLE)
row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT)
row.logChange(COL_SUB_ID, SUB_ID_DEFAULT)
row.logChange(COL_VALIDATED, false)
row.logChange(COL_LEVEL, LEVEL_DEFAULT)
row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
row.logChange(COL_SSID, null)
row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
row.logChange(COL_ONLINE_SIGN_UP, false)
row.logChange(COL_PASSPOINT_NAME, null)
}
}
/**
* A model representing that the wifi information we received was invalid in some way.
*/
data class Invalid(
/** A description of why the wifi information was invalid. */
val invalidReason: String,
) : WifiNetworkModel() {
override fun toString() = "WifiNetwork.Invalid[$invalidReason]"
override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
if (prevVal !is Invalid) {
logFull(row)
return
}
if (invalidReason != prevVal.invalidReason) {
row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE $invalidReason")
}
}
override fun logFull(row: TableRowLogger) {
row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE $invalidReason")
row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT)
row.logChange(COL_SUB_ID, SUB_ID_DEFAULT)
row.logChange(COL_VALIDATED, false)
row.logChange(COL_LEVEL, LEVEL_DEFAULT)
row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
row.logChange(COL_SSID, null)
row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
row.logChange(COL_ONLINE_SIGN_UP, false)
@@ -59,18 +99,21 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
return
}
if (prevVal is CarrierMerged) {
// The only difference between CarrierMerged and Inactive is the type
row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE)
return
}
// When changing from Active to Inactive, we need to log diffs to all the fields.
logFullNonActiveNetwork(TYPE_INACTIVE, row)
// When changing to Inactive, we need to log diffs to all the fields.
logFull(row)
}
override fun logFull(row: TableRowLogger) {
logFullNonActiveNetwork(TYPE_INACTIVE, row)
row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE)
row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT)
row.logChange(COL_SUB_ID, SUB_ID_DEFAULT)
row.logChange(COL_VALIDATED, false)
row.logChange(COL_LEVEL, LEVEL_DEFAULT)
row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
row.logChange(COL_SSID, null)
row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
row.logChange(COL_ONLINE_SIGN_UP, false)
row.logChange(COL_PASSPOINT_NAME, null)
}
}
@@ -80,22 +123,75 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
*
* See [android.net.wifi.WifiInfo.isCarrierMerged] for more information.
*/
object CarrierMerged : WifiNetworkModel() {
override fun toString() = "WifiNetwork.CarrierMerged"
data class CarrierMerged(
/**
* The [android.net.Network.netId] we received from
* [android.net.ConnectivityManager.NetworkCallback] in association with this wifi network.
*
* Importantly, **not** [android.net.wifi.WifiInfo.getNetworkId].
*/
val networkId: Int,
/**
* The subscription ID that this connection represents.
*
* Comes from [android.net.wifi.WifiInfo.getSubscriptionId].
*
* Per that method, this value must not be [INVALID_SUBSCRIPTION_ID] (if it was invalid,
* then this is *not* a carrier merged network).
*/
val subscriptionId: Int,
/**
* The signal level, guaranteed to be 0 <= level <= numberOfLevels.
*/
val level: Int,
/**
* The maximum possible level.
*/
val numberOfLevels: Int = DEFAULT_NUM_LEVELS,
) : WifiNetworkModel() {
init {
require(level in MIN_VALID_LEVEL..numberOfLevels) {
"0 <= wifi level <= $numberOfLevels required; level was $level"
}
require(subscriptionId != INVALID_SUBSCRIPTION_ID) {
"subscription ID cannot be invalid"
}
}
override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
if (prevVal is CarrierMerged) {
if (prevVal !is CarrierMerged) {
logFull(row)
return
}
if (prevVal is Inactive) {
// The only difference between CarrierMerged and Inactive is the type.
row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)
return
if (prevVal.networkId != networkId) {
row.logChange(COL_NETWORK_ID, networkId)
}
if (prevVal.subscriptionId != subscriptionId) {
row.logChange(COL_SUB_ID, subscriptionId)
}
if (prevVal.level != level) {
row.logChange(COL_LEVEL, level)
}
if (prevVal.numberOfLevels != numberOfLevels) {
row.logChange(COL_NUM_LEVELS, numberOfLevels)
}
}
// When changing from Active to CarrierMerged, we need to log diffs to all the fields.
logFullNonActiveNetwork(TYPE_CARRIER_MERGED, row)
override fun logFull(row: TableRowLogger) {
row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)
row.logChange(COL_NETWORK_ID, networkId)
row.logChange(COL_SUB_ID, subscriptionId)
row.logChange(COL_VALIDATED, true)
row.logChange(COL_LEVEL, level)
row.logChange(COL_NUM_LEVELS, numberOfLevels)
row.logChange(COL_SSID, null)
row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
row.logChange(COL_ONLINE_SIGN_UP, false)
row.logChange(COL_PASSPOINT_NAME, null)
}
}
@@ -137,38 +233,50 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
if (prevVal !is Active) {
row.logChange(COL_NETWORK_TYPE, TYPE_ACTIVE)
logFull(row)
return
}
if (prevVal !is Active || prevVal.networkId != networkId) {
if (prevVal.networkId != networkId) {
row.logChange(COL_NETWORK_ID, networkId)
}
if (prevVal !is Active || prevVal.isValidated != isValidated) {
if (prevVal.isValidated != isValidated) {
row.logChange(COL_VALIDATED, isValidated)
}
if (prevVal !is Active || prevVal.level != level) {
if (prevVal.level != level) {
row.logChange(COL_LEVEL, level)
}
if (prevVal !is Active || prevVal.ssid != ssid) {
if (prevVal.ssid != ssid) {
row.logChange(COL_SSID, ssid)
}
// TODO(b/238425913): The passpoint-related values are frequently never used, so it
// would be great to not log them when they're not used.
if (prevVal !is Active || prevVal.isPasspointAccessPoint != isPasspointAccessPoint) {
if (prevVal.isPasspointAccessPoint != isPasspointAccessPoint) {
row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint)
}
if (prevVal !is Active ||
prevVal.isOnlineSignUpForPasspointAccessPoint !=
if (prevVal.isOnlineSignUpForPasspointAccessPoint !=
isOnlineSignUpForPasspointAccessPoint) {
row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint)
}
if (prevVal !is Active ||
prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) {
if (prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) {
row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName)
}
}
override fun logFull(row: TableRowLogger) {
row.logChange(COL_NETWORK_TYPE, TYPE_ACTIVE)
row.logChange(COL_NETWORK_ID, networkId)
row.logChange(COL_SUB_ID, null)
row.logChange(COL_VALIDATED, isValidated)
row.logChange(COL_LEVEL, level)
row.logChange(COL_NUM_LEVELS, null)
row.logChange(COL_SSID, ssid)
row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint)
row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint)
row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName)
}
override fun toString(): String {
// Only include the passpoint-related values in the string if we have them. (Most
// networks won't have them so they'll be mostly clutter.)
@@ -188,22 +296,14 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
}
companion object {
@VisibleForTesting
internal const val MIN_VALID_LEVEL = 0
@VisibleForTesting
internal const val MAX_VALID_LEVEL = 4
}
}
internal fun logFullNonActiveNetwork(type: String, row: TableRowLogger) {
row.logChange(COL_NETWORK_TYPE, type)
row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT)
row.logChange(COL_VALIDATED, false)
row.logChange(COL_LEVEL, LEVEL_DEFAULT)
row.logChange(COL_SSID, null)
row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
row.logChange(COL_ONLINE_SIGN_UP, false)
row.logChange(COL_PASSPOINT_NAME, null)
companion object {
@VisibleForTesting
internal const val MIN_VALID_LEVEL = 0
}
}
@@ -214,12 +314,16 @@ const val TYPE_ACTIVE = "Active"
const val COL_NETWORK_TYPE = "type"
const val COL_NETWORK_ID = "networkId"
const val COL_SUB_ID = "subscriptionId"
const val COL_VALIDATED = "isValidated"
const val COL_LEVEL = "level"
const val COL_NUM_LEVELS = "maxLevel"
const val COL_SSID = "ssid"
const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint"
const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint"
const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName"
val LEVEL_DEFAULT: String? = null
val NUM_LEVELS_DEFAULT: String? = null
val NETWORK_ID_DEFAULT: String? = null
val SUB_ID_DEFAULT: String? = null

View File

@@ -22,6 +22,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -43,10 +44,10 @@ constructor(
private fun Bundle.toWifiEvent(): FakeWifiEventModel? {
val wifi = getString("wifi") ?: return null
return if (wifi == "show") {
activeWifiEvent()
} else {
FakeWifiEventModel.WifiDisabled
return when (wifi) {
"show" -> activeWifiEvent()
"carriermerged" -> carrierMergedWifiEvent()
else -> FakeWifiEventModel.WifiDisabled
}
}
@@ -64,6 +65,14 @@ constructor(
)
}
private fun Bundle.carrierMergedWifiEvent(): FakeWifiEventModel.CarrierMerged {
val subId = getString("slot")?.toInt() ?: DEFAULT_CARRIER_MERGED_SUB_ID
val level = getString("level")?.toInt() ?: 0
val numberOfLevels = getString("numlevels")?.toInt() ?: DEFAULT_NUM_LEVELS
return FakeWifiEventModel.CarrierMerged(subId, level, numberOfLevels)
}
private fun String.toActivity(): Int =
when (this) {
"inout" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT
@@ -71,4 +80,8 @@ constructor(
"out" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT
else -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE
}
companion object {
const val DEFAULT_CARRIER_MERGED_SUB_ID = 10
}
}

View File

@@ -66,6 +66,7 @@ constructor(
private fun processEvent(event: FakeWifiEventModel) =
when (event) {
is FakeWifiEventModel.Wifi -> processEnabledWifiState(event)
is FakeWifiEventModel.CarrierMerged -> processCarrierMergedWifiState(event)
is FakeWifiEventModel.WifiDisabled -> processDisabledWifiState()
}
@@ -85,6 +86,14 @@ constructor(
_wifiNetwork.value = event.toWifiNetworkModel()
}
private fun processCarrierMergedWifiState(event: FakeWifiEventModel.CarrierMerged) {
_isWifiEnabled.value = true
_isWifiDefault.value = true
// TODO(b/238425913): Support activity in demo mode.
_wifiActivity.value = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
_wifiNetwork.value = event.toCarrierMergedModel()
}
private fun FakeWifiEventModel.Wifi.toWifiNetworkModel(): WifiNetworkModel =
WifiNetworkModel.Active(
networkId = DEMO_NET_ID,
@@ -99,6 +108,14 @@ constructor(
passpointProviderFriendlyName = null,
)
private fun FakeWifiEventModel.CarrierMerged.toCarrierMergedModel(): WifiNetworkModel =
WifiNetworkModel.CarrierMerged(
networkId = DEMO_NET_ID,
subscriptionId = subscriptionId,
level = level,
numberOfLevels = numberOfLevels,
)
companion object {
private const val DEMO_NET_ID = 1234
}

View File

@@ -29,5 +29,11 @@ sealed interface FakeWifiEventModel {
val validated: Boolean?,
) : FakeWifiEventModel
data class CarrierMerged(
val subscriptionId: Int,
val level: Int,
val numberOfLevels: Int,
) : FakeWifiEventModel
object WifiDisabled : FakeWifiEventModel
}

View File

@@ -29,6 +29,7 @@ import android.net.NetworkRequest
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import com.android.settingslib.Utils
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -269,7 +270,19 @@ constructor(
wifiManager: WifiManager,
): WifiNetworkModel {
return if (wifiInfo.isCarrierMerged) {
WifiNetworkModel.CarrierMerged
if (wifiInfo.subscriptionId == INVALID_SUBSCRIPTION_ID) {
WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
} else {
WifiNetworkModel.CarrierMerged(
networkId = network.getNetId(),
subscriptionId = wifiInfo.subscriptionId,
level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
// The WiFi signal level returned by WifiManager#calculateSignalLevel start
// from 0, so WifiManager#getMaxSignalLevel + 1 represents the total level
// buckets count.
numberOfLevels = wifiManager.maxSignalLevel + 1,
)
}
} else {
WifiNetworkModel.Active(
network.getNetId(),
@@ -302,6 +315,9 @@ constructor(
.build()
private const val WIFI_NETWORK_CALLBACK_NAME = "wifiNetworkModel"
private const val CARRIER_MERGED_INVALID_SUB_ID_REASON =
"Wifi network was carrier merged but had invalid sub ID"
}
@SysUISingleton

View File

@@ -66,6 +66,7 @@ class WifiInteractorImpl @Inject constructor(
override val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
when (info) {
is WifiNetworkModel.Unavailable -> null
is WifiNetworkModel.Invalid -> null
is WifiNetworkModel.Inactive -> null
is WifiNetworkModel.CarrierMerged -> null
is WifiNetworkModel.Active -> when {

View File

@@ -83,6 +83,7 @@ constructor(
private fun WifiNetworkModel.icon(): WifiIcon {
return when (this) {
is WifiNetworkModel.Unavailable -> WifiIcon.Hidden
is WifiNetworkModel.Invalid -> WifiIcon.Hidden
is WifiNetworkModel.CarrierMerged -> WifiIcon.Hidden
is WifiNetworkModel.Inactive -> WifiIcon.Visible(
res = WIFI_NO_NETWORK,

View File

@@ -34,6 +34,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.valid
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.mock
@@ -71,8 +73,10 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
private lateinit var underTest: MobileRepositorySwitcher
private lateinit var realRepo: MobileConnectionsRepositoryImpl
private lateinit var demoRepo: DemoMobileConnectionsRepository
private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
private lateinit var mobileDataSource: DemoModeMobileConnectionDataSource
private lateinit var wifiDataSource: DemoModeWifiDataSource
private lateinit var logFactory: TableLogBufferFactory
private lateinit var wifiRepository: FakeWifiRepository
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var subscriptionManager: SubscriptionManager
@@ -96,10 +100,15 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
// Never start in demo mode
whenever(demoModeController.isInDemoMode).thenReturn(false)
mockDataSource =
mobileDataSource =
mock<DemoModeMobileConnectionDataSource>().also {
whenever(it.mobileEvents).thenReturn(fakeNetworkEventsFlow)
}
wifiDataSource =
mock<DemoModeWifiDataSource>().also {
whenever(it.wifiEvents).thenReturn(MutableStateFlow(null))
}
wifiRepository = FakeWifiRepository()
realRepo =
MobileConnectionsRepositoryImpl(
@@ -113,12 +122,14 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
context,
IMMEDIATE,
scope,
wifiRepository,
mock(),
)
demoRepo =
DemoMobileConnectionsRepository(
dataSource = mockDataSource,
mobileDataSource = mobileDataSource,
wifiDataSource = wifiDataSource,
scope = scope,
context = context,
logFactory = logFactory,

View File

@@ -29,6 +29,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectio
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
@@ -63,10 +65,12 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
private val testScope = TestScope(testDispatcher)
private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
private val fakeWifiEventFlow = MutableStateFlow<FakeWifiEventModel?>(null)
private lateinit var connectionsRepo: DemoMobileConnectionsRepository
private lateinit var underTest: DemoMobileConnectionRepository
private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
private lateinit var mockWifiDataSource: DemoModeWifiDataSource
@Before
fun setUp() {
@@ -75,10 +79,15 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
mock<DemoModeMobileConnectionDataSource>().also {
whenever(it.mobileEvents).thenReturn(fakeNetworkEventFlow)
}
mockWifiDataSource =
mock<DemoModeWifiDataSource>().also {
whenever(it.wifiEvents).thenReturn(fakeWifiEventFlow)
}
connectionsRepo =
DemoMobileConnectionsRepository(
dataSource = mockDataSource,
mobileDataSource = mockDataSource,
wifiDataSource = mockWifiDataSource,
scope = testScope.backgroundScope,
context = context,
logFactory = logFactory,

View File

@@ -32,6 +32,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionMod
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
@@ -57,21 +59,28 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
private val testScope = TestScope(testDispatcher)
private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
private val fakeWifiEventFlow = MutableStateFlow<FakeWifiEventModel?>(null)
private lateinit var underTest: DemoMobileConnectionsRepository
private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
private lateinit var mobileDataSource: DemoModeMobileConnectionDataSource
private lateinit var wifiDataSource: DemoModeWifiDataSource
@Before
fun setUp() {
// The data source only provides one API, so we can mock it with a flow here for convenience
mockDataSource =
mobileDataSource =
mock<DemoModeMobileConnectionDataSource>().also {
whenever(it.mobileEvents).thenReturn(fakeNetworkEventFlow)
}
wifiDataSource =
mock<DemoModeWifiDataSource>().also {
whenever(it.wifiEvents).thenReturn(fakeWifiEventFlow)
}
underTest =
DemoMobileConnectionsRepository(
dataSource = mockDataSource,
mobileDataSource = mobileDataSource,
wifiDataSource = wifiDataSource,
scope = testScope.backgroundScope,
context = context,
logFactory = logFactory,
@@ -96,6 +105,22 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel()
}
@Test
fun `wifi carrier merged event - create new subscription`() =
testScope.runTest {
var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
assertThat(latest).isEmpty()
fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5)
assertThat(latest).hasSize(1)
assertThat(latest!![0].subscriptionId).isEqualTo(5)
job.cancel()
}
@Test
fun `network event - reuses subscription when same Id`() =
testScope.runTest {
@@ -118,6 +143,28 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel()
}
@Test
fun `wifi carrier merged event - reuses subscription when same Id`() =
testScope.runTest {
var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
assertThat(latest).isEmpty()
fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5, level = 1)
assertThat(latest).hasSize(1)
assertThat(latest!![0].subscriptionId).isEqualTo(5)
// Second network event comes in with the same subId, does not create a new subscription
fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5, level = 2)
assertThat(latest).hasSize(1)
assertThat(latest!![0].subscriptionId).isEqualTo(5)
job.cancel()
}
@Test
fun `multiple subscriptions`() =
testScope.runTest {
@@ -132,6 +179,35 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel()
}
@Test
fun `mobile subscription and carrier merged subscription`() =
testScope.runTest {
var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
fakeNetworkEventFlow.value = validMobileEvent(subId = 1)
fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5)
assertThat(latest).hasSize(2)
job.cancel()
}
@Test
fun `multiple mobile subscriptions and carrier merged subscription`() =
testScope.runTest {
var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
fakeNetworkEventFlow.value = validMobileEvent(subId = 1)
fakeNetworkEventFlow.value = validMobileEvent(subId = 2)
fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 3)
assertThat(latest).hasSize(3)
job.cancel()
}
@Test
fun `mobile disabled event - disables connection - subId specified - single conn`() =
testScope.runTest {
@@ -194,6 +270,112 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel()
}
@Test
fun `wifi network updates to disabled - carrier merged connection removed`() =
testScope.runTest {
var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 1)
assertThat(latest).hasSize(1)
fakeWifiEventFlow.value = FakeWifiEventModel.WifiDisabled
assertThat(latest).isEmpty()
job.cancel()
}
@Test
fun `wifi network updates to active - carrier merged connection removed`() =
testScope.runTest {
var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 1)
assertThat(latest).hasSize(1)
fakeWifiEventFlow.value =
FakeWifiEventModel.Wifi(
level = 1,
activity = 0,
ssid = null,
validated = true,
)
assertThat(latest).isEmpty()
job.cancel()
}
@Test
fun `mobile sub updates to carrier merged - only one connection`() =
testScope.runTest {
var latestSubsList: List<SubscriptionModel>? = null
var connections: List<DemoMobileConnectionRepository>? = null
val job =
underTest.subscriptions
.onEach { latestSubsList = it }
.onEach { infos ->
connections =
infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) }
}
.launchIn(this)
fakeNetworkEventFlow.value = validMobileEvent(subId = 3, level = 2)
assertThat(latestSubsList).hasSize(1)
val carrierMergedEvent = validCarrierMergedEvent(subId = 3, level = 1)
fakeWifiEventFlow.value = carrierMergedEvent
assertThat(latestSubsList).hasSize(1)
val connection = connections!!.find { it.subId == 3 }!!
assertCarrierMergedConnection(connection, carrierMergedEvent)
job.cancel()
}
@Test
fun `mobile sub updates to carrier merged then back - has old mobile data`() =
testScope.runTest {
var latestSubsList: List<SubscriptionModel>? = null
var connections: List<DemoMobileConnectionRepository>? = null
val job =
underTest.subscriptions
.onEach { latestSubsList = it }
.onEach { infos ->
connections =
infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) }
}
.launchIn(this)
val mobileEvent = validMobileEvent(subId = 3, level = 2)
fakeNetworkEventFlow.value = mobileEvent
assertThat(latestSubsList).hasSize(1)
val carrierMergedEvent = validCarrierMergedEvent(subId = 3, level = 1)
fakeWifiEventFlow.value = carrierMergedEvent
assertThat(latestSubsList).hasSize(1)
var connection = connections!!.find { it.subId == 3 }!!
assertCarrierMergedConnection(connection, carrierMergedEvent)
// WHEN the carrier merged is removed
fakeWifiEventFlow.value =
FakeWifiEventModel.Wifi(
level = 4,
activity = 0,
ssid = null,
validated = true,
)
// THEN the subId=3 connection goes back to the mobile information
connection = connections!!.find { it.subId == 3 }!!
assertConnection(connection, mobileEvent)
job.cancel()
}
/** Regression test for b/261706421 */
@Test
fun `multiple connections - remove all - does not throw`() =
@@ -289,6 +471,51 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel()
}
@Test
fun `demo connection - two connections - update carrier merged - no affect on first`() =
testScope.runTest {
var currentEvent1 = validMobileEvent(subId = 1)
var connection1: DemoMobileConnectionRepository? = null
var currentEvent2 = validCarrierMergedEvent(subId = 2)
var connection2: DemoMobileConnectionRepository? = null
var connections: List<DemoMobileConnectionRepository>? = null
val job =
underTest.subscriptions
.onEach { infos ->
connections =
infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) }
}
.launchIn(this)
fakeNetworkEventFlow.value = currentEvent1
fakeWifiEventFlow.value = currentEvent2
assertThat(connections).hasSize(2)
connections!!.forEach {
when (it.subId) {
1 -> connection1 = it
2 -> connection2 = it
else -> Assert.fail("Unexpected subscription")
}
}
assertConnection(connection1!!, currentEvent1)
assertCarrierMergedConnection(connection2!!, currentEvent2)
// WHEN the event changes for connection 2, it updates, and connection 1 stays the same
currentEvent2 = validCarrierMergedEvent(subId = 2, level = 4)
fakeWifiEventFlow.value = currentEvent2
assertConnection(connection1!!, currentEvent1)
assertCarrierMergedConnection(connection2!!, currentEvent2)
// and vice versa
currentEvent1 = validMobileEvent(subId = 1, inflateStrength = true)
fakeNetworkEventFlow.value = currentEvent1
assertConnection(connection1!!, currentEvent1)
assertCarrierMergedConnection(connection2!!, currentEvent2)
job.cancel()
}
private fun assertConnection(
conn: DemoMobileConnectionRepository,
model: FakeNetworkEventModel
@@ -315,6 +542,21 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
else -> {}
}
}
private fun assertCarrierMergedConnection(
conn: DemoMobileConnectionRepository,
model: FakeWifiEventModel.CarrierMerged,
) {
val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
assertThat(conn.subId).isEqualTo(model.subscriptionId)
assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
assertThat(connectionInfo.carrierNetworkChangeActive).isEqualTo(false)
assertThat(connectionInfo.isRoaming).isEqualTo(false)
assertThat(connectionInfo.isEmergencyOnly).isFalse()
assertThat(connectionInfo.isGsm).isFalse()
assertThat(connectionInfo.dataConnectionState).isEqualTo(DataConnectionState.Connected)
}
}
/** Convenience to create a valid fake network event with minimal params */
@@ -339,3 +581,14 @@ fun validMobileEvent(
roaming = roaming,
name = "demo name",
)
fun validCarrierMergedEvent(
subId: Int = 1,
level: Int = 1,
numberOfLevels: Int = 4,
): FakeWifiEventModel.CarrierMerged =
FakeWifiEventModel.CarrierMerged(
subscriptionId = subId,
level = level,
numberOfLevels = numberOfLevels,
)

View File

@@ -0,0 +1,251 @@
/*
* 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.prod
import android.testing.AndroidTestingRunner
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.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
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.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
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.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: CarrierMergedConnectionRepository
private lateinit var wifiRepository: FakeWifiRepository
@Mock private lateinit var logger: TableLogBuffer
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
wifiRepository = FakeWifiRepository()
underTest =
CarrierMergedConnectionRepository(
SUB_ID,
logger,
NetworkNameModel.Default("name"),
testScope.backgroundScope,
wifiRepository,
)
}
@Test
fun connectionInfo_inactiveWifi_isDefault() =
testScope.runTest {
var latest: MobileConnectionModel? = null
val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
assertThat(latest).isEqualTo(MobileConnectionModel())
job.cancel()
}
@Test
fun connectionInfo_activeWifi_isDefault() =
testScope.runTest {
var latest: MobileConnectionModel? = null
val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = NET_ID, level = 1))
assertThat(latest).isEqualTo(MobileConnectionModel())
job.cancel()
}
@Test
fun connectionInfo_carrierMergedWifi_isValidAndFieldsComeFromWifiNetwork() =
testScope.runTest {
var latest: MobileConnectionModel? = null
val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
wifiRepository.setIsWifiEnabled(true)
wifiRepository.setIsWifiDefault(true)
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
networkId = NET_ID,
subscriptionId = SUB_ID,
level = 3,
)
)
val expected =
MobileConnectionModel(
primaryLevel = 3,
cdmaLevel = 3,
dataConnectionState = DataConnectionState.Connected,
dataActivityDirection =
DataActivityModel(
hasActivityIn = false,
hasActivityOut = false,
),
resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType,
isRoaming = false,
isEmergencyOnly = false,
operatorAlphaShort = null,
isInService = true,
isGsm = false,
carrierNetworkChangeActive = false,
)
assertThat(latest).isEqualTo(expected)
job.cancel()
}
@Test
fun connectionInfo_carrierMergedWifi_wrongSubId_isDefault() =
testScope.runTest {
var latest: MobileConnectionModel? = null
val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
networkId = NET_ID,
subscriptionId = SUB_ID + 10,
level = 3,
)
)
assertThat(latest).isEqualTo(MobileConnectionModel())
assertThat(latest!!.primaryLevel).isNotEqualTo(3)
assertThat(latest!!.resolvedNetworkType)
.isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
job.cancel()
}
// This scenario likely isn't possible, but write a test for it anyway
@Test
fun connectionInfo_carrierMergedButNotEnabled_isDefault() =
testScope.runTest {
var latest: MobileConnectionModel? = null
val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
networkId = NET_ID,
subscriptionId = SUB_ID,
level = 3,
)
)
wifiRepository.setIsWifiEnabled(false)
assertThat(latest).isEqualTo(MobileConnectionModel())
job.cancel()
}
// This scenario likely isn't possible, but write a test for it anyway
@Test
fun connectionInfo_carrierMergedButWifiNotDefault_isDefault() =
testScope.runTest {
var latest: MobileConnectionModel? = null
val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
networkId = NET_ID,
subscriptionId = SUB_ID,
level = 3,
)
)
wifiRepository.setIsWifiDefault(false)
assertThat(latest).isEqualTo(MobileConnectionModel())
job.cancel()
}
@Test
fun numberOfLevels_comesFromCarrierMerged() =
testScope.runTest {
var latest: Int? = null
val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
networkId = NET_ID,
subscriptionId = SUB_ID,
level = 1,
numberOfLevels = 6,
)
)
assertThat(latest).isEqualTo(6)
job.cancel()
}
@Test
fun dataEnabled_matchesWifiEnabled() =
testScope.runTest {
var latest: Boolean? = null
val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
wifiRepository.setIsWifiEnabled(true)
assertThat(latest).isTrue()
wifiRepository.setIsWifiEnabled(false)
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun cdmaRoaming_alwaysFalse() =
testScope.runTest {
var latest: Boolean? = null
val job = underTest.cdmaRoaming.onEach { latest = it }.launchIn(this)
assertThat(latest).isFalse()
job.cancel()
}
private companion object {
const val SUB_ID = 123
const val NET_ID = 456
}
}

View File

@@ -0,0 +1,389 @@
/*
* 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 androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
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.Mockito.never
import org.mockito.Mockito.verify
/**
* This repo acts as a dispatcher to either the `typical` or `carrier merged` versions of the
* repository interface it's switching on. These tests just need to verify that the entire interface
* properly switches over when the value of `isCarrierMerged` changes.
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class FullMobileConnectionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: FullMobileConnectionRepository
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
private val mobileMappings = FakeMobileMappingsProxy()
private val tableLogBuffer = mock<TableLogBuffer>()
private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>()
private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>()
private lateinit var connectionsRepo: FakeMobileConnectionsRepository
private val globalMobileDataSettingChangedEvent: Flow<Unit>
get() = connectionsRepo.globalMobileDataSettingChangedEvent
private lateinit var mobileRepo: FakeMobileConnectionRepository
private lateinit var carrierMergedRepo: FakeMobileConnectionRepository
@Before
fun setUp() {
connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogBuffer)
mobileRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer)
carrierMergedRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer)
whenever(
mobileFactory.build(
eq(SUB_ID),
any(),
eq(DEFAULT_NAME),
eq(SEP),
eq(globalMobileDataSettingChangedEvent),
)
)
.thenReturn(mobileRepo)
whenever(carrierMergedFactory.build(eq(SUB_ID), any(), eq(DEFAULT_NAME)))
.thenReturn(carrierMergedRepo)
}
@Test
fun startingIsCarrierMerged_usesCarrierMergedInitially() =
testScope.runTest {
val carrierMergedConnectionInfo =
MobileConnectionModel(
operatorAlphaShort = "Carrier Merged Operator",
)
carrierMergedRepo.setConnectionInfo(carrierMergedConnectionInfo)
initializeRepo(startingIsCarrierMerged = true)
assertThat(underTest.activeRepo.value).isEqualTo(carrierMergedRepo)
assertThat(underTest.connectionInfo.value).isEqualTo(carrierMergedConnectionInfo)
verify(mobileFactory, never())
.build(
SUB_ID,
tableLogBuffer,
DEFAULT_NAME,
SEP,
globalMobileDataSettingChangedEvent
)
}
@Test
fun startingNotCarrierMerged_usesTypicalInitially() =
testScope.runTest {
val mobileConnectionInfo =
MobileConnectionModel(
operatorAlphaShort = "Typical Operator",
)
mobileRepo.setConnectionInfo(mobileConnectionInfo)
initializeRepo(startingIsCarrierMerged = false)
assertThat(underTest.activeRepo.value).isEqualTo(mobileRepo)
assertThat(underTest.connectionInfo.value).isEqualTo(mobileConnectionInfo)
verify(carrierMergedFactory, never()).build(SUB_ID, tableLogBuffer, DEFAULT_NAME)
}
@Test
fun activeRepo_matchesIsCarrierMerged() =
testScope.runTest {
initializeRepo(startingIsCarrierMerged = false)
var latest: MobileConnectionRepository? = null
val job = underTest.activeRepo.onEach { latest = it }.launchIn(this)
underTest.setIsCarrierMerged(true)
assertThat(latest).isEqualTo(carrierMergedRepo)
underTest.setIsCarrierMerged(false)
assertThat(latest).isEqualTo(mobileRepo)
underTest.setIsCarrierMerged(true)
assertThat(latest).isEqualTo(carrierMergedRepo)
job.cancel()
}
@Test
fun connectionInfo_getsUpdatesFromRepo_carrierMerged() =
testScope.runTest {
initializeRepo(startingIsCarrierMerged = false)
var latest: MobileConnectionModel? = null
val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
underTest.setIsCarrierMerged(true)
val info1 =
MobileConnectionModel(
operatorAlphaShort = "Carrier Merged Operator",
primaryLevel = 1,
)
carrierMergedRepo.setConnectionInfo(info1)
assertThat(latest).isEqualTo(info1)
val info2 =
MobileConnectionModel(
operatorAlphaShort = "Carrier Merged Operator #2",
primaryLevel = 2,
)
carrierMergedRepo.setConnectionInfo(info2)
assertThat(latest).isEqualTo(info2)
val info3 =
MobileConnectionModel(
operatorAlphaShort = "Carrier Merged Operator #3",
primaryLevel = 3,
)
carrierMergedRepo.setConnectionInfo(info3)
assertThat(latest).isEqualTo(info3)
job.cancel()
}
@Test
fun connectionInfo_getsUpdatesFromRepo_mobile() =
testScope.runTest {
initializeRepo(startingIsCarrierMerged = false)
var latest: MobileConnectionModel? = null
val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
underTest.setIsCarrierMerged(false)
val info1 =
MobileConnectionModel(
operatorAlphaShort = "Typical Merged Operator",
primaryLevel = 1,
)
mobileRepo.setConnectionInfo(info1)
assertThat(latest).isEqualTo(info1)
val info2 =
MobileConnectionModel(
operatorAlphaShort = "Typical Merged Operator #2",
primaryLevel = 2,
)
mobileRepo.setConnectionInfo(info2)
assertThat(latest).isEqualTo(info2)
val info3 =
MobileConnectionModel(
operatorAlphaShort = "Typical Merged Operator #3",
primaryLevel = 3,
)
mobileRepo.setConnectionInfo(info3)
assertThat(latest).isEqualTo(info3)
job.cancel()
}
@Test
fun connectionInfo_updatesWhenCarrierMergedUpdates() =
testScope.runTest {
initializeRepo(startingIsCarrierMerged = false)
var latest: MobileConnectionModel? = null
val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val carrierMergedInfo =
MobileConnectionModel(
operatorAlphaShort = "Carrier Merged Operator",
primaryLevel = 4,
)
carrierMergedRepo.setConnectionInfo(carrierMergedInfo)
val mobileInfo =
MobileConnectionModel(
operatorAlphaShort = "Typical Operator",
primaryLevel = 2,
)
mobileRepo.setConnectionInfo(mobileInfo)
// Start with the mobile info
assertThat(latest).isEqualTo(mobileInfo)
// WHEN isCarrierMerged is set to true
underTest.setIsCarrierMerged(true)
// THEN the carrier merged info is used
assertThat(latest).isEqualTo(carrierMergedInfo)
val newCarrierMergedInfo =
MobileConnectionModel(
operatorAlphaShort = "New CM Operator",
primaryLevel = 0,
)
carrierMergedRepo.setConnectionInfo(newCarrierMergedInfo)
assertThat(latest).isEqualTo(newCarrierMergedInfo)
// WHEN isCarrierMerged is set to false
underTest.setIsCarrierMerged(false)
// THEN the typical info is used
assertThat(latest).isEqualTo(mobileInfo)
val newMobileInfo =
MobileConnectionModel(
operatorAlphaShort = "New Mobile Operator",
primaryLevel = 3,
)
mobileRepo.setConnectionInfo(newMobileInfo)
assertThat(latest).isEqualTo(newMobileInfo)
job.cancel()
}
@Test
fun `factory - reuses log buffers for same connection`() =
testScope.runTest {
val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock())
val factory =
FullMobileConnectionRepository.Factory(
scope = testScope.backgroundScope,
realLoggerFactory,
mobileFactory,
carrierMergedFactory,
)
// Create two connections for the same subId. Similar to if the connection appeared
// and disappeared from the connectionFactory's perspective
val connection1 =
factory.build(
SUB_ID,
startingIsCarrierMerged = false,
DEFAULT_NAME,
SEP,
globalMobileDataSettingChangedEvent,
)
val connection1Repeat =
factory.build(
SUB_ID,
startingIsCarrierMerged = false,
DEFAULT_NAME,
SEP,
globalMobileDataSettingChangedEvent,
)
assertThat(connection1.tableLogBuffer)
.isSameInstanceAs(connection1Repeat.tableLogBuffer)
}
@Test
fun `factory - reuses log buffers for same sub ID even if carrier merged`() =
testScope.runTest {
val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock())
val factory =
FullMobileConnectionRepository.Factory(
scope = testScope.backgroundScope,
realLoggerFactory,
mobileFactory,
carrierMergedFactory,
)
val connection1 =
factory.build(
SUB_ID,
startingIsCarrierMerged = false,
DEFAULT_NAME,
SEP,
globalMobileDataSettingChangedEvent,
)
// WHEN a connection with the same sub ID but carrierMerged = true is created
val connection1Repeat =
factory.build(
SUB_ID,
startingIsCarrierMerged = true,
DEFAULT_NAME,
SEP,
globalMobileDataSettingChangedEvent,
)
// THEN the same table is re-used
assertThat(connection1.tableLogBuffer)
.isSameInstanceAs(connection1Repeat.tableLogBuffer)
}
// TODO(b/238425913): Verify that the logging switches correctly (once the carrier merged repo
// implements logging).
private fun initializeRepo(startingIsCarrierMerged: Boolean) {
underTest =
FullMobileConnectionRepository(
SUB_ID,
startingIsCarrierMerged,
tableLogBuffer,
DEFAULT_NAME,
SEP,
globalMobileDataSettingChangedEvent,
testScope.backgroundScope,
mobileFactory,
carrierMergedFactory,
)
}
private companion object {
const val SUB_ID = 42
private val DEFAULT_NAME = NetworkNameModel.Default("default name")
private const val SEP = "-"
}
}

View File

@@ -37,17 +37,18 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -74,6 +75,9 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileConnectionsRepositoryImpl
private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
private lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory
private lateinit var wifiRepository: FakeWifiRepository
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@@ -100,6 +104,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
mock<TableLogBuffer>()
}
wifiRepository = FakeWifiRepository()
connectionFactory =
MobileConnectionRepositoryImpl.Factory(
fakeBroadcastDispatcher,
@@ -110,7 +116,18 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
logger = logger,
mobileMappingsProxy = mobileMappings,
scope = scope,
)
carrierMergedFactory =
CarrierMergedConnectionRepository.Factory(
scope,
wifiRepository,
)
fullConnectionFactory =
FullMobileConnectionRepository.Factory(
scope = scope,
logFactory = logBufferFactory,
mobileRepoFactory = connectionFactory,
carrierMergedRepoFactory = carrierMergedFactory,
)
underTest =
@@ -125,7 +142,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
context,
IMMEDIATE,
scope,
connectionFactory,
wifiRepository,
fullConnectionFactory,
)
}
@@ -179,6 +197,40 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel()
}
@Test
fun testSubscriptions_carrierMergedOnly_listHasCarrierMerged() =
runBlocking(IMMEDIATE) {
var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
assertThat(latest).isEqualTo(listOf(MODEL_CM))
job.cancel()
}
@Test
fun testSubscriptions_carrierMergedAndOther_listHasBothWithCarrierMergedLast() =
runBlocking(IMMEDIATE) {
var latest: List<SubscriptionModel>? = null
val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2, MODEL_CM))
job.cancel()
}
@Test
fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
runBlocking(IMMEDIATE) {
@@ -218,6 +270,96 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel()
}
@Test
fun testConnectionRepository_carrierMergedSubId_isCached() =
runBlocking(IMMEDIATE) {
val job = underTest.subscriptions.launchIn(this)
wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
val repo1 = underTest.getRepoForSubId(SUB_CM_ID)
val repo2 = underTest.getRepoForSubId(SUB_CM_ID)
assertThat(repo1).isSameInstanceAs(repo2)
job.cancel()
}
@Test
fun testConnectionRepository_carrierMergedAndMobileSubs_usesCorrectRepos() =
runBlocking(IMMEDIATE) {
val job = underTest.subscriptions.launchIn(this)
wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
val mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
job.cancel()
}
@Test
fun testSubscriptions_subNoLongerCarrierMerged_repoUpdates() =
runBlocking(IMMEDIATE) {
val job = underTest.subscriptions.launchIn(this)
wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
var mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
// WHEN the wifi network updates to be not carrier merged
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 4, level = 1))
// THEN the repos update
val noLongerCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
assertThat(noLongerCarrierMergedRepo.getIsCarrierMerged()).isFalse()
assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
job.cancel()
}
@Test
fun testSubscriptions_subBecomesCarrierMerged_repoUpdates() =
runBlocking(IMMEDIATE) {
val job = underTest.subscriptions.launchIn(this)
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
val notYetCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
var mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
assertThat(notYetCarrierMergedRepo.getIsCarrierMerged()).isFalse()
assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
// WHEN the wifi network updates to be carrier merged
wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
// THEN the repos update
val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
job.cancel()
}
@Test
fun testConnectionCache_clearsInvalidSubscriptions() =
runBlocking(IMMEDIATE) {
@@ -244,6 +386,34 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel()
}
@Test
fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() =
runBlocking(IMMEDIATE) {
val job = underTest.subscriptions.launchIn(this)
wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
// Get repos to trigger caching
val repo1 = underTest.getRepoForSubId(SUB_1_ID)
val repo2 = underTest.getRepoForSubId(SUB_2_ID)
val repoCarrierMerged = underTest.getRepoForSubId(SUB_CM_ID)
assertThat(underTest.getSubIdRepoCache())
.containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2, SUB_CM_ID, repoCarrierMerged)
// SUB_2 and SUB_CM disappear
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1))
getSubscriptionCallback().onSubscriptionsChanged()
assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
job.cancel()
}
/** Regression test for b/261706421 */
@Test
fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() =
@@ -295,59 +465,19 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
underTest.getRepoForSubId(SUB_1_ID)
verify(logBufferFactory)
.getOrCreate(
eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_1_ID)),
eq(tableBufferLogName(SUB_1_ID)),
anyInt(),
)
underTest.getRepoForSubId(SUB_2_ID)
verify(logBufferFactory)
.getOrCreate(
eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_2_ID)),
eq(tableBufferLogName(SUB_2_ID)),
anyInt(),
)
job.cancel()
}
@Test
fun `connection repository factory - reuses log buffers for same connection`() =
runBlocking(IMMEDIATE) {
val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock())
connectionFactory =
MobileConnectionRepositoryImpl.Factory(
fakeBroadcastDispatcher,
context = context,
telephonyManager = telephonyManager,
bgDispatcher = IMMEDIATE,
globalSettings = globalSettings,
logger = logger,
mobileMappingsProxy = mobileMappings,
scope = scope,
logFactory = realLoggerFactory,
)
// Create two connections for the same subId. Similar to if the connection appeared
// and disappeared from the connectionFactory's perspective
val connection1 =
connectionFactory.build(
1,
NetworkNameModel.Default("default_name"),
"-",
underTest.globalMobileDataSettingChangedEvent,
)
val connection1_repeat =
connectionFactory.build(
1,
NetworkNameModel.Default("default_name"),
"-",
underTest.globalMobileDataSettingChangedEvent,
)
assertThat(connection1.tableLogBuffer)
.isSameInstanceAs(connection1_repeat.tableLogBuffer)
}
@Test
fun mobileConnectivity_default() {
assertThat(underTest.defaultMobileNetworkConnectivity.value)
@@ -461,7 +591,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
context,
IMMEDIATE,
scope,
connectionFactory,
wifiRepository,
fullConnectionFactory,
)
var latest: MobileMappings.Config? = null
@@ -571,5 +702,16 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
private const val NET_ID = 123
private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
private const val SUB_CM_ID = 5
private val SUB_CM =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_CM_ID) }
private val MODEL_CM = SubscriptionModel(subscriptionId = SUB_CM_ID)
private val WIFI_NETWORK_CM =
WifiNetworkModel.CarrierMerged(
networkId = 3,
subscriptionId = SUB_CM_ID,
level = 1,
)
}
}

View File

@@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
@@ -270,6 +271,23 @@ class MobileIconInteractorTest : SysuiTestCase() {
job.cancel()
}
@Test
fun iconGroup_carrierMerged_usesOverride() =
runBlocking(IMMEDIATE) {
connectionRepository.setConnectionInfo(
MobileConnectionModel(
resolvedNetworkType = CarrierMergedNetworkType,
),
)
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
assertThat(latest).isEqualTo(CarrierMergedNetworkType.iconGroupOverride)
job.cancel()
}
@Test
fun alwaysShowDataRatIcon_matchesParent() =
runBlocking(IMMEDIATE) {

View File

@@ -16,11 +16,12 @@
package com.android.systemui.statusbar.pipeline.wifi.data.model
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableRowLogger
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MIN_VALID_LEVEL
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Companion.MIN_VALID_LEVEL
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -44,8 +45,52 @@ class WifiNetworkModelTest : SysuiTestCase() {
WifiNetworkModel.Active(NETWORK_ID, level = MAX_VALID_LEVEL + 1)
}
@Test(expected = IllegalArgumentException::class)
fun carrierMerged_invalidSubId_exceptionThrown() {
WifiNetworkModel.CarrierMerged(NETWORK_ID, INVALID_SUBSCRIPTION_ID, 1)
}
// Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken
@Test
fun logDiffs_carrierMergedToInactive_resetsAllFields() {
val logger = TestLogger()
val prevVal =
WifiNetworkModel.CarrierMerged(
networkId = 5,
subscriptionId = 3,
level = 1,
)
WifiNetworkModel.Inactive.logDiffs(prevVal, logger)
assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_INACTIVE))
assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString()))
assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
}
@Test
fun logDiffs_inactiveToCarrierMerged_logsAllFields() {
val logger = TestLogger()
val carrierMerged =
WifiNetworkModel.CarrierMerged(
networkId = 6,
subscriptionId = 3,
level = 2,
)
carrierMerged.logDiffs(prevVal = WifiNetworkModel.Inactive, logger)
assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED))
assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "6"))
assertThat(logger.changes).contains(Pair(COL_SUB_ID, "3"))
assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
assertThat(logger.changes).contains(Pair(COL_LEVEL, "2"))
assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
}
@Test
fun logDiffs_inactiveToActive_logsAllActiveFields() {
val logger = TestLogger()
@@ -95,8 +140,14 @@ class WifiNetworkModelTest : SysuiTestCase() {
level = 3,
ssid = "Test SSID"
)
val prevVal =
WifiNetworkModel.CarrierMerged(
networkId = 5,
subscriptionId = 3,
level = 1,
)
activeNetwork.logDiffs(prevVal = WifiNetworkModel.CarrierMerged, logger)
activeNetwork.logDiffs(prevVal, logger)
assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE))
assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5"))
@@ -105,7 +156,7 @@ class WifiNetworkModelTest : SysuiTestCase() {
assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
}
@Test
fun logDiffs_activeToCarrierMerged_resetsAllActiveFields() {
fun logDiffs_activeToCarrierMerged_logsAllFields() {
val logger = TestLogger()
val activeNetwork =
WifiNetworkModel.Active(
@@ -114,13 +165,20 @@ class WifiNetworkModelTest : SysuiTestCase() {
level = 3,
ssid = "Test SSID"
)
val carrierMerged =
WifiNetworkModel.CarrierMerged(
networkId = 6,
subscriptionId = 3,
level = 2,
)
WifiNetworkModel.CarrierMerged.logDiffs(prevVal = activeNetwork, logger)
carrierMerged.logDiffs(prevVal = activeNetwork, logger)
assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED))
assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString()))
assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "6"))
assertThat(logger.changes).contains(Pair(COL_SUB_ID, "3"))
assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
assertThat(logger.changes).contains(Pair(COL_LEVEL, "2"))
assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
}

View File

@@ -26,6 +26,7 @@ import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -340,7 +341,6 @@ class WifiRepositoryImplTest : SysuiTestCase() {
.launchIn(this)
val wifiInfo = mock<WifiInfo>().apply {
whenever(this.ssid).thenReturn(SSID)
whenever(this.isPrimary).thenReturn(true)
whenever(this.isCarrierMerged).thenReturn(true)
}
@@ -352,6 +352,67 @@ class WifiRepositoryImplTest : SysuiTestCase() {
job.cancel()
}
@Test
fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
runBlocking(IMMEDIATE) {
var latest: WifiNetworkModel? = null
val job = underTest
.wifiNetwork
.onEach { latest = it }
.launchIn(this)
val wifiInfo = mock<WifiInfo>().apply {
whenever(this.isPrimary).thenReturn(true)
whenever(this.isCarrierMerged).thenReturn(true)
whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
}
getNetworkCallback().onCapabilitiesChanged(
NETWORK,
createWifiNetworkCapabilities(wifiInfo),
)
assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java)
job.cancel()
}
@Test
fun wifiNetwork_isCarrierMerged_getsCorrectValues() =
runBlocking(IMMEDIATE) {
var latest: WifiNetworkModel? = null
val job = underTest
.wifiNetwork
.onEach { latest = it }
.launchIn(this)
val rssi = -57
val wifiInfo = mock<WifiInfo>().apply {
whenever(this.isPrimary).thenReturn(true)
whenever(this.isCarrierMerged).thenReturn(true)
whenever(this.rssi).thenReturn(rssi)
whenever(this.subscriptionId).thenReturn(567)
}
whenever(wifiManager.calculateSignalLevel(rssi)).thenReturn(2)
whenever(wifiManager.maxSignalLevel).thenReturn(5)
getNetworkCallback().onCapabilitiesChanged(
NETWORK,
createWifiNetworkCapabilities(wifiInfo),
)
assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
val latestCarrierMerged = latest as WifiNetworkModel.CarrierMerged
assertThat(latestCarrierMerged.networkId).isEqualTo(NETWORK_ID)
assertThat(latestCarrierMerged.subscriptionId).isEqualTo(567)
assertThat(latestCarrierMerged.level).isEqualTo(2)
// numberOfLevels = maxSignalLevel + 1
assertThat(latestCarrierMerged.numberOfLevels).isEqualTo(6)
job.cancel()
}
@Test
fun wifiNetwork_notValidated_networkNotValidated() = runBlocking(IMMEDIATE) {
var latest: WifiNetworkModel? = null

View File

@@ -84,7 +84,9 @@ class WifiInteractorImplTest : SysuiTestCase() {
@Test
fun ssid_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(networkId = 1, subscriptionId = 2, level = 1)
)
var latest: String? = "default"
val job = underTest

View File

@@ -206,7 +206,8 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
// Enabled = false => no networks shown
TestCase(
enabled = false,
network = WifiNetworkModel.CarrierMerged,
network =
WifiNetworkModel.CarrierMerged(NETWORK_ID, subscriptionId = 1, level = 1),
expected = null,
),
TestCase(
@@ -228,7 +229,8 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
// forceHidden = true => no networks shown
TestCase(
forceHidden = true,
network = WifiNetworkModel.CarrierMerged,
network =
WifiNetworkModel.CarrierMerged(NETWORK_ID, subscriptionId = 1, level = 1),
expected = null,
),
TestCase(
@@ -369,7 +371,8 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
// network = CarrierMerged => not shown
TestCase(
network = WifiNetworkModel.CarrierMerged,
network =
WifiNetworkModel.CarrierMerged(NETWORK_ID, subscriptionId = 1, level = 1),
expected = null,
),