Merge changes I7922b475,Id73df747,Ie8d492c5,If608a313 into udc-dev

* changes:
  [SB Refactor] Define a `defaultConnections` flow in the shared repo.
  [SB Refactor] Migrate `ConnectivityRepositoryImplTest` to TestScope.
  [SB Refactor] Split out mobile's default information into two flows.
  [SB Refactor] Keep connection failed as false when forcing validation.
This commit is contained in:
Caitlin Shkuratov
2023-03-27 14:02:03 +00:00
committed by Android (Google) Code Review
28 changed files with 1153 additions and 657 deletions

View File

@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.pipeline.mobile.data
import android.net.Network
import android.net.NetworkCapabilities
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.TelephonyDisplayInfo
@@ -27,7 +25,6 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.pipeline.dagger.MobileInputLog
import com.android.systemui.statusbar.pipeline.shared.LoggerHelper
import javax.inject.Inject
/** Logs for inputs into the mobile pipeline. */
@@ -37,24 +34,6 @@ class MobileInputLogger
constructor(
@MobileInputLog private val buffer: LogBuffer,
) {
fun logOnCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities,
isDefaultNetworkCallback: Boolean,
) {
LoggerHelper.logOnCapabilitiesChanged(
buffer,
TAG,
network,
networkCapabilities,
isDefaultNetworkCallback,
)
}
fun logOnLost(network: Network, isDefaultNetworkCallback: Boolean) {
LoggerHelper.logOnLost(buffer, TAG, network, isDefaultNetworkCallback)
}
fun logOnServiceStateChanged(serviceState: ServiceState, subId: Int) {
buffer.log(
TAG,

View File

@@ -1,49 +0,0 @@
/*
* 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.model
import android.net.NetworkCapabilities
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger
/** Provides information about a mobile network connection */
data class MobileConnectivityModel(
/** Whether mobile is the connected transport see [NetworkCapabilities.TRANSPORT_CELLULAR] */
val isConnected: Boolean = false,
/** Whether the mobile transport is validated [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */
val isValidated: Boolean = false,
) : Diffable<MobileConnectivityModel> {
// TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes?
override fun logDiffs(prevVal: MobileConnectivityModel, row: TableRowLogger) {
if (prevVal.isConnected != isConnected) {
row.logChange(COL_IS_CONNECTED, isConnected)
}
if (prevVal.isValidated != isValidated) {
row.logChange(COL_IS_VALIDATED, isValidated)
}
}
override fun logFull(row: TableRowLogger) {
row.logChange(COL_IS_CONNECTED, isConnected)
row.logChange(COL_IS_VALIDATED, isValidated)
}
companion object {
private const val COL_IS_CONNECTED = "isConnected"
private const val COL_IS_VALIDATED = "isValidated"
}
}

View File

@@ -21,7 +21,6 @@ import android.telephony.SubscriptionManager
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@@ -52,8 +51,17 @@ interface MobileConnectionsRepository {
/** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
val defaultDataSubId: StateFlow<Int>
/** The current connectivity status for the default mobile network connection */
val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel>
/**
* True if the default network connection is a mobile-like connection and false otherwise.
*
* This is typically shown by having [android.net.NetworkCapabilities.TRANSPORT_CELLULAR], but
* there are edge cases (like carrier merged wifi) that could also result in the default
* connection being mobile-like.
*/
val mobileIsDefault: StateFlow<Boolean>
/** True if the default network connection is validated and false otherwise. */
val defaultConnectionIsValidated: StateFlow<Boolean>
/** Get or create a repository for the line of service for the given subscription ID */
fun getRepoForSubId(subId: Int): MobileConnectionRepository

View File

@@ -24,7 +24,6 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
@@ -155,13 +154,18 @@ constructor(
.flatMapLatest { it.defaultDataSubId }
.stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.defaultDataSubId.value)
override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
override val mobileIsDefault: StateFlow<Boolean> =
activeRepo
.flatMapLatest { it.defaultMobileNetworkConnectivity }
.flatMapLatest { it.mobileIsDefault }
.stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.mobileIsDefault.value)
override val defaultConnectionIsValidated: StateFlow<Boolean> =
activeRepo
.flatMapLatest { it.defaultConnectionIsValidated }
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
realRepository.defaultMobileNetworkConnectivity.value
realRepository.defaultConnectionIsValidated.value
)
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {

View File

@@ -24,7 +24,6 @@ import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.qualifiers.Application
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.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -158,8 +157,10 @@ constructor(
override val defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
// TODO(b/261029387): not yet supported
override val defaultMobileNetworkConnectivity =
MutableStateFlow(MobileConnectivityModel(isConnected = true, isValidated = true))
override val mobileIsDefault: StateFlow<Boolean> = MutableStateFlow(true)
// TODO(b/261029387): not yet supported
override val defaultConnectionIsValidated: StateFlow<Boolean> = MutableStateFlow(true)
override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
val current = connectionRepoCache[subId]?.repo

View File

@@ -19,12 +19,6 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.annotation.SuppressLint
import android.content.Context
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
@@ -46,11 +40,11 @@ import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
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.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.util.kotlin.pairwise
@@ -80,7 +74,7 @@ import kotlinx.coroutines.withContext
class MobileConnectionsRepositoryImpl
@Inject
constructor(
private val connectivityManager: ConnectivityManager,
connectivityRepository: ConnectivityRepository,
private val subscriptionManager: SubscriptionManager,
private val telephonyManager: TelephonyManager,
private val logger: MobileInputLogger,
@@ -261,47 +255,31 @@ constructor(
subIdRepositoryCache[subId]
?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
@SuppressLint("MissingPermission")
override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
conflatedCallbackFlow {
val callback =
object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
override fun onLost(network: Network) {
logger.logOnLost(network, isDefaultNetworkCallback = true)
// Send a disconnected model when lost. Maybe should create a sealed
// type or null here?
trySend(MobileConnectivityModel())
}
override fun onCapabilitiesChanged(
network: Network,
caps: NetworkCapabilities
) {
logger.logOnCapabilitiesChanged(
network,
caps,
isDefaultNetworkCallback = true,
)
trySend(
MobileConnectivityModel(
isConnected = caps.hasTransport(TRANSPORT_CELLULAR),
isValidated = caps.hasCapability(NET_CAPABILITY_VALIDATED),
)
)
}
}
connectivityManager.registerDefaultNetworkCallback(callback)
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
override val mobileIsDefault: StateFlow<Boolean> =
connectivityRepository.defaultConnections
// Because carrier merged networks are displayed as mobile networks, they're
// part of the `isDefault` calculation. See b/272586234.
.map { it.mobile.isDefault || it.carrierMerged.isDefault }
.distinctUntilChanged()
.logDiffsForTable(
tableLogger,
columnPrefix = "$LOGGING_PREFIX.defaultConnection",
initialValue = MobileConnectivityModel(),
columnPrefix = "",
columnName = "mobileIsDefault",
initialValue = false,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val defaultConnectionIsValidated: StateFlow<Boolean> =
connectivityRepository.defaultConnections
.map { it.isValidated }
.distinctUntilChanged()
.logDiffsForTable(
tableLogger,
columnPrefix = "",
columnName = "defaultConnectionIsValidated",
initialValue = false,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
/**
* Flow that tracks the active mobile data subscriptions. Emits `true` whenever the active data

View File

@@ -22,7 +22,6 @@ import com.android.settingslib.mobile.TelephonyIcons.NOT_DEFAULT_DATA
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.MobileConnectivityModel
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
@@ -46,17 +45,8 @@ interface MobileIconInteractor {
/** The current mobile data activity */
val activity: Flow<DataActivityModel>
/**
* This bit is meant to be `true` if and only if the default network capabilities (see
* [android.net.ConnectivityManager.registerDefaultNetworkCallback]) result in a network that
* has the [android.net.NetworkCapabilities.TRANSPORT_CELLULAR] represented.
*
* Note that this differs from [isDataConnected], which is tracked by telephony and has to do
* with the state of using this mobile connection for data as opposed to just voice. It is
* possible for a mobile subscription to be connected but not be in a connected data state, and
* thus we wouldn't want to show the network type icon.
*/
val isConnected: Flow<Boolean>
/** See [MobileConnectionsRepository.mobileIsDefault]. */
val mobileIsDefault: Flow<Boolean>
/**
* True when telephony tells us that the data state is CONNECTED. See
@@ -126,7 +116,7 @@ class MobileIconInteractorImpl(
defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
override val alwaysShowDataRatIcon: StateFlow<Boolean>,
override val alwaysUseCdmaLevel: StateFlow<Boolean>,
defaultMobileConnectivity: StateFlow<MobileConnectivityModel>,
override val mobileIsDefault: StateFlow<Boolean>,
defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
defaultMobileIconGroup: StateFlow<MobileIconGroup>,
defaultDataSubId: StateFlow<Int>,
@@ -138,8 +128,6 @@ class MobileIconInteractorImpl(
override val activity = connectionRepository.dataActivityDirection
override val isConnected: Flow<Boolean> = defaultMobileConnectivity.mapLatest { it.isConnected }
override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
private val isDefault =

View File

@@ -25,7 +25,6 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
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
@@ -61,8 +60,12 @@ import kotlinx.coroutines.flow.transformLatest
* icon
*/
interface MobileIconsInteractor {
/** See [MobileConnectionsRepository.mobileIsDefault]. */
val mobileIsDefault: StateFlow<Boolean>
/** List of subscriptions, potentially filtered for CBRS */
val filteredSubscriptions: Flow<List<SubscriptionModel>>
/** True if the active mobile data subscription has data enabled */
val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
@@ -75,20 +78,15 @@ interface MobileIconsInteractor {
/** Tracks the subscriptionId set as the default for data connections */
val defaultDataSubId: StateFlow<Int>
/**
* The connectivity of the default mobile network. Note that this can differ from what is
* reported from [MobileConnectionsRepository] in some cases. E.g., when the active subscription
* changes but the groupUuid remains the same, we keep the old validation information for 2
* seconds to avoid icon flickering.
*/
val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel>
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
/** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
val defaultMobileIconGroup: StateFlow<MobileIconGroup>
/** True only if the default network is mobile, and validation also failed */
val isDefaultConnectionFailed: StateFlow<Boolean>
/** True once the user has been set up */
val isUserSetup: StateFlow<Boolean>
@@ -115,6 +113,9 @@ constructor(
userSetupRepo: UserSetupRepository,
@Application private val scope: CoroutineScope,
) : MobileIconsInteractor {
override val mobileIsDefault = mobileConnectionsRepo.mobileIsDefault
override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
mobileConnectionsRepo.activeMobileDataRepository
.flatMapLatest { it?.dataEnabled ?: flowOf(false) }
@@ -197,7 +198,7 @@ constructor(
*/
private val forcingCellularValidation =
mobileConnectionsRepo.activeSubChangedInGroupEvent
.filter { mobileConnectionsRepo.defaultMobileNetworkConnectivity.value.isValidated }
.filter { mobileConnectionsRepo.defaultConnectionIsValidated.value }
.transformLatest {
emit(true)
delay(2000)
@@ -211,32 +212,6 @@ constructor(
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
combine(
mobileConnectionsRepo.defaultMobileNetworkConnectivity,
forcingCellularValidation,
) { networkConnectivity, forceValidation ->
return@combine if (forceValidation) {
MobileConnectivityModel(
isValidated = true,
isConnected = networkConnectivity.isConnected
)
} else {
networkConnectivity
}
}
.distinctUntilChanged()
.logDiffsForTable(
tableLogger,
columnPrefix = "$LOGGING_PREFIX.defaultConnection",
initialValue = mobileConnectionsRepo.defaultMobileNetworkConnectivity.value,
)
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
mobileConnectionsRepo.defaultMobileNetworkConnectivity.value
)
/**
* Mapping from network type to [MobileIconGroup] using the config generated for the default
* subscription Id. This mapping is the same for every subscription.
@@ -271,12 +246,15 @@ constructor(
* other transport type is active, because then we expect there not to be validation.
*/
override val isDefaultConnectionFailed: StateFlow<Boolean> =
mobileConnectionsRepo.defaultMobileNetworkConnectivity
.mapLatest { connectivityModel ->
if (!connectivityModel.isConnected) {
false
} else {
!connectivityModel.isValidated
combine(
mobileConnectionsRepo.mobileIsDefault,
mobileConnectionsRepo.defaultConnectionIsValidated,
forcingCellularValidation,
) { mobileIsDefault, defaultConnectionIsValidated, forcingCellularValidation ->
when {
!mobileIsDefault -> false
forcingCellularValidation -> false
else -> !defaultConnectionIsValidated
}
}
.logDiffsForTable(
@@ -301,7 +279,7 @@ constructor(
activeDataConnectionHasDataEnabled,
alwaysShowDataRatIcon,
alwaysUseCdmaLevel,
defaultMobileNetworkConnectivity,
mobileIsDefault,
defaultMobileIconMapping,
defaultMobileIconGroup,
defaultDataSubId,

View File

@@ -148,9 +148,9 @@ constructor(
iconInteractor.isDataEnabled,
iconInteractor.isDefaultConnectionFailed,
iconInteractor.alwaysShowDataRatIcon,
iconInteractor.isConnected,
) { dataConnected, dataEnabled, failedConnection, alwaysShow, connected ->
alwaysShow || (dataConnected && dataEnabled && !failedConnection && connected)
iconInteractor.mobileIsDefault,
) { dataConnected, dataEnabled, failedConnection, alwaysShow, mobileIsDefault ->
alwaysShow || (dataConnected && dataEnabled && !failedConnection && mobileIsDefault)
}
.distinctUntilChanged()
.logDiffsForTable(

View File

@@ -16,10 +16,13 @@
package com.android.systemui.statusbar.pipeline.shared
import android.net.Network
import android.net.NetworkCapabilities
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.pipeline.dagger.SharedConnectivityInputLog
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
import javax.inject.Inject
/** Logs for connectivity-related inputs that are shared across wifi, mobile, etc. */
@@ -32,6 +35,32 @@ constructor(
fun logTuningChanged(tuningList: String?) {
buffer.log(TAG, LogLevel.DEBUG, { str1 = tuningList }, { "onTuningChanged: $str1" })
}
fun logOnDefaultCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities,
) {
LoggerHelper.logOnCapabilitiesChanged(
buffer,
TAG,
network,
networkCapabilities,
isDefaultNetworkCallback = true,
)
}
fun logOnDefaultLost(network: Network) {
LoggerHelper.logOnLost(buffer, TAG, network, isDefaultNetworkCallback = true)
}
fun logDefaultConnectionsChanged(model: DefaultConnectionModel) {
buffer.log(
TAG,
LogLevel.DEBUG,
model::messageInitializer,
model::messagePrinter,
)
}
}
private const val TAG = "ConnectivityInputLogger"

View File

@@ -0,0 +1,87 @@
/*
* 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.shared.data.model
import android.net.NetworkCapabilities
import com.android.systemui.plugins.log.LogMessage
/**
* A model for all of the current default connections(s).
*
* Uses different classes for each connection type to ensure type safety when setting the values.
*
* Important: We generally expect there to be only *one* default network at a time (with the
* exception of carrier merged). Specifically, we don't expect to ever have both wifi *and* cellular
* as default at the same time. However, the framework network callbacks don't provide any
* guarantees about why types of network could be default at the same time, so we don't enforce any
* guarantees on this class.
*/
data class DefaultConnectionModel(
/** Wifi's status as default or not. */
val wifi: Wifi = Wifi(isDefault = false),
/** Mobile's status as default or not. */
val mobile: Mobile = Mobile(isDefault = false),
/**
* True if the current default network represents a carrier merged network, and false otherwise.
* See [android.net.wifi.WifiInfo.isCarrierMerged] for more information.
*
* Important: A carrier merged network can come in as either a
* [NetworkCapabilities.TRANSPORT_CELLULAR] *or* as a [NetworkCapabilities.TRANSPORT_WIFI]. This
* means that when carrier merged is in effect, either:
* - [wifi] *and* [carrierMerged] will be marked as default; or
* - [mobile] *and* [carrierMerged] will be marked as default
*
* Specifically, [carrierMerged] will never be the *only* default connection.
*/
val carrierMerged: CarrierMerged = CarrierMerged(isDefault = false),
/** Ethernet's status as default or not. */
val ethernet: Ethernet = Ethernet(isDefault = false),
/** True if the default connection is currently validated and false otherwise. */
val isValidated: Boolean = false,
) {
data class Wifi(val isDefault: Boolean)
data class Mobile(val isDefault: Boolean)
data class CarrierMerged(val isDefault: Boolean)
data class Ethernet(val isDefault: Boolean)
/**
* Used in conjunction with [ConnectivityInputLogger] to log this class without calling
* [toString] on it.
*
* Be sure to change [messagePrinter] whenever this method is changed.
*/
fun messageInitializer(message: LogMessage) {
message.bool1 = wifi.isDefault
message.bool2 = mobile.isDefault
message.bool3 = carrierMerged.isDefault
message.bool4 = ethernet.isDefault
message.int1 = if (isValidated) 1 else 0
}
fun messagePrinter(message: LogMessage): String {
return "DefaultConnectionModel(" +
"wifi.isDefault=${message.bool1}, " +
"mobile.isDefault=${message.bool2}, " +
"carrierMerged.isDefault=${message.bool3}, " +
"ethernet.isDefault=${message.bool4}, " +
"isValidated=${if (message.int1 == 1) "true" else "false"})"
}
}

View File

@@ -16,7 +16,17 @@
package com.android.systemui.statusbar.pipeline.shared.data.repository
import android.annotation.SuppressLint
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo
import androidx.annotation.ArrayRes
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
@@ -29,6 +39,11 @@ import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.pipeline.shared.ConnectivityInputLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.CarrierMerged
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Ethernet
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Mobile
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Wifi
import com.android.systemui.tuner.TunerService
import java.io.PrintWriter
import javax.inject.Inject
@@ -37,6 +52,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/**
@@ -46,6 +63,9 @@ import kotlinx.coroutines.flow.stateIn
interface ConnectivityRepository {
/** Observable for the current set of connectivity icons that should be force-hidden. */
val forceHiddenSlots: StateFlow<Set<ConnectivitySlot>>
/** Observable for which connection(s) are currently default. */
val defaultConnections: StateFlow<DefaultConnectionModel>
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -53,6 +73,7 @@ interface ConnectivityRepository {
class ConnectivityRepositoryImpl
@Inject
constructor(
connectivityManager: ConnectivityManager,
private val connectivitySlots: ConnectivitySlots,
context: Context,
dumpManager: DumpManager,
@@ -61,7 +82,7 @@ constructor(
tunerService: TunerService,
) : ConnectivityRepository, Dumpable {
init {
dumpManager.registerDumpable("ConnectivityRepository", this)
dumpManager.registerNormalDumpable("ConnectivityRepository", this)
}
// The default set of hidden icons to use if we don't get any from [TunerService].
@@ -97,6 +118,67 @@ constructor(
initialValue = defaultHiddenIcons
)
@SuppressLint("MissingPermission")
override val defaultConnections: StateFlow<DefaultConnectionModel> =
conflatedCallbackFlow {
val callback =
object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
override fun onLost(network: Network) {
logger.logOnDefaultLost(network)
// The system no longer has a default network, so everything is
// non-default.
trySend(
DefaultConnectionModel(
Wifi(isDefault = false),
Mobile(isDefault = false),
CarrierMerged(isDefault = false),
Ethernet(isDefault = false),
isValidated = false,
)
)
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities,
) {
logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities)
val isWifiDefault =
networkCapabilities.hasTransport(TRANSPORT_WIFI) ||
networkCapabilities.getMainOrUnderlyingWifiInfo() != null
val isMobileDefault =
networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
val isCarrierMergedDefault =
networkCapabilities
.getMainOrUnderlyingWifiInfo()
?.isCarrierMerged == true
val isEthernetDefault =
networkCapabilities.hasTransport(TRANSPORT_ETHERNET)
val isValidated =
networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)
trySend(
DefaultConnectionModel(
Wifi(isWifiDefault),
Mobile(isMobileDefault),
CarrierMerged(isCarrierMergedDefault),
Ethernet(isEthernetDefault),
isValidated,
)
)
}
}
connectivityManager.registerDefaultNetworkCallback(callback)
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
.distinctUntilChanged()
.onEach { logger.logDefaultConnectionsChanged(it) }
.stateIn(scope, SharingStarted.Eagerly, DefaultConnectionModel())
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.apply { println("defaultHiddenIcons=$defaultHiddenIcons") }
}
@@ -116,5 +198,35 @@ constructor(
.mapNotNull { connectivitySlots.getSlotFromName(it) }
.toSet()
}
/**
* Returns a [WifiInfo] object from the capabilities if it has one, or null if there is no
* underlying wifi network.
*
* This will return a valid [WifiInfo] object if wifi is the main transport **or** wifi is
* an underlying transport. This is important for carrier merged networks, where the main
* transport info is *not* wifi, but the underlying transport info *is* wifi. We want to
* always use [WifiInfo] if it's available, so we need to check the underlying transport
* info.
*/
fun NetworkCapabilities.getMainOrUnderlyingWifiInfo(): WifiInfo? {
// Wifi info can either come from a WIFI Transport, or from a CELLULAR transport for
// virtual networks like VCN.
val canHaveWifiInfo =
this.hasTransport(TRANSPORT_CELLULAR) || this.hasTransport(TRANSPORT_WIFI)
if (!canHaveWifiInfo) {
return null
}
return when (val currentTransportInfo = transportInfo) {
// This VcnTransportInfo logic is copied from
// [com.android.settingslib.Utils.tryGetWifiInfoForVcn]. It's copied instead of
// re-used because it makes the logic here clearer, and because the method will be
// removed once this pipeline is fully launched.
is VcnTransportInfo -> currentTransportInfo.wifiInfo
is WifiInfo -> currentTransportInfo
else -> null
}
}
}
}

View File

@@ -30,7 +30,6 @@ 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
import com.android.systemui.dagger.SysUISingleton
@@ -41,6 +40,8 @@ import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
@@ -55,6 +56,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
@@ -70,6 +72,7 @@ class WifiRepositoryImpl
constructor(
broadcastDispatcher: BroadcastDispatcher,
connectivityManager: ConnectivityManager,
connectivityRepository: ConnectivityRepository,
logger: WifiInputLogger,
@WifiTableLog wifiTableLogBuffer: TableLogBuffer,
@Main mainExecutor: Executor,
@@ -105,39 +108,9 @@ constructor(
)
override val isWifiDefault: StateFlow<Boolean> =
conflatedCallbackFlow {
// Note: This callback doesn't do any logging because we already log every network
// change in the [wifiNetwork] callback.
val callback =
object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
logger.logOnCapabilitiesChanged(
network,
networkCapabilities,
isDefaultNetworkCallback = true,
)
// This method will always be called immediately after the network
// becomes the default, in addition to any time the capabilities change
// while the network is the default.
// If this network is a wifi network, then wifi is the default network.
trySend(isWifiNetwork(networkCapabilities))
}
override fun onLost(network: Network) {
logger.logOnLost(network, isDefaultNetworkCallback = true)
// The system no longer has a default network, so wifi is definitely not
// default.
trySend(false)
}
}
connectivityManager.registerDefaultNetworkCallback(callback)
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
connectivityRepository.defaultConnections
// TODO(b/274493701): Should wifi be considered default if it's carrier merged?
.map { it.wifi.isDefault || it.carrierMerged.isDefault }
.distinctUntilChanged()
.logDiffsForTable(
wifiTableLogBuffer,
@@ -165,7 +138,7 @@ constructor(
wifiNetworkChangeEvents.tryEmit(Unit)
val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
val wifiInfo = networkCapabilities.getMainOrUnderlyingWifiInfo()
if (wifiInfo?.isPrimary == true) {
val wifiNetworkModel =
createWifiNetworkModel(
@@ -248,34 +221,6 @@ constructor(
// NetworkCallback inside [wifiNetwork] for our wifi network information.
val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
private fun networkCapabilitiesToWifiInfo(
networkCapabilities: NetworkCapabilities
): WifiInfo? {
return when {
networkCapabilities.hasTransport(TRANSPORT_CELLULAR) ->
// Sometimes, cellular networks can act as wifi networks (known as VCN --
// virtual carrier network). So, see if this cellular network has wifi info.
Utils.tryGetWifiInfoForVcn(networkCapabilities)
networkCapabilities.hasTransport(TRANSPORT_WIFI) ->
if (networkCapabilities.transportInfo is WifiInfo) {
networkCapabilities.transportInfo as WifiInfo
} else {
null
}
else -> null
}
}
/** True if these capabilities represent a wifi network. */
private fun isWifiNetwork(networkCapabilities: NetworkCapabilities): Boolean {
return when {
networkCapabilities.hasTransport(TRANSPORT_WIFI) -> true
networkCapabilities.hasTransport(TRANSPORT_CELLULAR) ->
Utils.tryGetWifiInfoForVcn(networkCapabilities) != null
else -> false
}
}
private fun createWifiNetworkModel(
wifiInfo: WifiInfo,
network: Network,
@@ -337,6 +282,7 @@ constructor(
constructor(
private val broadcastDispatcher: BroadcastDispatcher,
private val connectivityManager: ConnectivityManager,
private val connectivityRepository: ConnectivityRepository,
private val logger: WifiInputLogger,
@WifiTableLog private val wifiTableLogBuffer: TableLogBuffer,
@Main private val mainExecutor: Executor,
@@ -346,6 +292,7 @@ constructor(
return WifiRepositoryImpl(
broadcastDispatcher,
connectivityManager,
connectivityRepository,
logger,
wifiTableLogBuffer,
mainExecutor,

View File

@@ -1,81 +0,0 @@
/*
* 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
import android.net.Network
import android.net.NetworkCapabilities
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.plugins.log.LogcatEchoTracker
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
import org.junit.Test
import org.mockito.Mockito
import org.mockito.Mockito.mock
@SmallTest
class MobileInputLoggerTest : SysuiTestCase() {
private val buffer =
LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java)).create("buffer", 10)
private val logger = MobileInputLogger(buffer)
@Test
fun testLogNetworkCapsChange_bufferHasInfo() {
logger.logOnCapabilitiesChanged(NET_1, NET_1_CAPS, isDefaultNetworkCallback = true)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
val actualString = stringWriter.toString()
val expectedNetId = NET_1_ID.toString()
val expectedCaps = NET_1_CAPS.toString()
assertThat(actualString).contains("onDefaultCapabilitiesChanged")
assertThat(actualString).contains(expectedNetId)
assertThat(actualString).contains(expectedCaps)
}
@Test
fun testLogOnLost_bufferHasNetIdOfLostNetwork() {
logger.logOnLost(NET_1, isDefaultNetworkCallback = false)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
val actualString = stringWriter.toString()
val expectedNetId = NET_1_ID.toString()
assertThat(actualString).contains("onLost")
assertThat(actualString).contains(expectedNetId)
}
companion object {
private const val NET_1_ID = 100
private val NET_1 =
com.android.systemui.util.mockito.mock<Network>().also {
Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
}
private val NET_1_CAPS =
NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
.build()
}
}

View File

@@ -23,7 +23,6 @@ import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -66,8 +65,9 @@ class FakeMobileConnectionsRepository(
private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
override val defaultDataSubId = _defaultDataSubId
private val _mobileConnectivity = MutableStateFlow(MobileConnectivityModel())
override val defaultMobileNetworkConnectivity = _mobileConnectivity
override val mobileIsDefault = MutableStateFlow(false)
override val defaultConnectionIsValidated = MutableStateFlow(false)
private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
@@ -88,14 +88,6 @@ class FakeMobileConnectionsRepository(
_subscriptions.value = subs
}
fun setDefaultDataSubId(id: Int) {
_defaultDataSubId.value = id
}
fun setMobileConnectivity(model: MobileConnectivityModel) {
_mobileConnectivity.value = model
}
fun setActiveMobileDataSubscriptionId(subId: Int) {
// Simulate the filtering that the repo does
if (subId == INVALID_SUBSCRIPTION_ID) {

View File

@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.net.ConnectivityManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
@@ -35,6 +34,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.validMobileEvent
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.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
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
@@ -77,8 +78,8 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
private lateinit var wifiDataSource: DemoModeWifiDataSource
private lateinit var logFactory: TableLogBufferFactory
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var connectivityRepository: ConnectivityRepository
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: MobileInputLogger
@@ -110,9 +111,11 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
}
wifiRepository = FakeWifiRepository()
connectivityRepository = FakeConnectivityRepository()
realRepo =
MobileConnectionsRepositoryImpl(
connectivityManager,
connectivityRepository,
subscriptionManager,
telephonyManager,
logger,

View File

@@ -91,11 +91,17 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
}
@Test
fun `connectivity - defaults to connected and validated`() =
fun isDefault_defaultsToTrue() =
testScope.runTest {
val connectivity = underTest.defaultMobileNetworkConnectivity.value
assertThat(connectivity.isConnected).isTrue()
assertThat(connectivity.isValidated).isTrue()
val isDefault = underTest.mobileIsDefault.value
assertThat(isDefault).isTrue()
}
@Test
fun validated_defaultsToTrue() =
testScope.runTest {
val isValidated = underTest.defaultConnectionIsValidated.value
assertThat(isValidated).isTrue()
}
@Test

View File

@@ -22,6 +22,10 @@ import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo
import android.os.ParcelUuid
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
@@ -38,12 +42,14 @@ 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.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
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.data.model.ConnectivitySlots
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.util.mockito.any
@@ -81,6 +87,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
private lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory
private lateinit var connectivityRepository: ConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var carrierConfigRepository: CarrierConfigRepository
@Mock private lateinit var connectivityManager: ConnectivityManager
@@ -121,6 +128,17 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
}
connectivityRepository =
ConnectivityRepositoryImpl(
connectivityManager,
ConnectivitySlots(context),
context,
mock(),
mock(),
scope,
mock(),
)
wifiRepository = FakeWifiRepository()
carrierConfigRepository =
@@ -159,7 +177,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
underTest =
MobileConnectionsRepositoryImpl(
connectivityManager,
connectivityRepository,
subscriptionManager,
telephonyManager,
logger,
@@ -668,75 +686,205 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
@Test
fun mobileConnectivity_default() {
assertThat(underTest.defaultMobileNetworkConnectivity.value)
.isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
fun mobileIsDefault_startsAsFalse() {
assertThat(underTest.mobileIsDefault.value).isFalse()
}
@Test
fun mobileConnectivity_isConnected_isValidated() =
fun mobileIsDefault_capsHaveCellular_isDefault() =
runBlocking(IMMEDIATE) {
val caps = createCapabilities(connected = true, validated = true)
val caps =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
}
var latest: MobileConnectivityModel? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
var latest: Boolean? = null
val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
assertThat(latest)
.isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = true))
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun mobileConnectivity_isConnected_isNotValidated() =
fun mobileIsDefault_capsDoNotHaveCellular_isNotDefault() =
runBlocking(IMMEDIATE) {
val caps = createCapabilities(connected = true, validated = false)
val caps =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
}
var latest: MobileConnectivityModel? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
var latest: Boolean? = null
val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
assertThat(latest)
.isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = false))
assertThat(latest).isFalse()
job.cancel()
}
/** Regression test for b/272586234. */
@Test
fun mobileIsDefault_carrierMergedViaWifi_isDefault() =
runBlocking(IMMEDIATE) {
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
val caps =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
whenever(it.transportInfo).thenReturn(carrierMergedInfo)
}
var latest: Boolean? = null
val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun mobileConnectivity_isNotConnected_isNotValidated() =
fun mobileIsDefault_carrierMergedViaMobile_isDefault() =
runBlocking(IMMEDIATE) {
val caps = createCapabilities(connected = false, validated = false)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
val caps =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
whenever(it.transportInfo).thenReturn(carrierMergedInfo)
}
var latest: MobileConnectivityModel? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
var latest: Boolean? = null
val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
assertThat(latest)
.isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
assertThat(latest).isTrue()
job.cancel()
}
/** In practice, I don't think this state can ever happen (!connected, validated) */
/** Regression test for b/272586234. */
@Test
fun mobileConnectivity_isNotConnected_isValidated() =
fun mobileIsDefault_carrierMergedViaWifiWithVcnTransport_isDefault() =
runBlocking(IMMEDIATE) {
val caps = createCapabilities(connected = false, validated = true)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
val caps =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
}
var latest: MobileConnectivityModel? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
var latest: Boolean? = null
val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
assertThat(latest).isEqualTo(MobileConnectivityModel(false, true))
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun mobileIsDefault_carrierMergedViaMobileWithVcnTransport_isDefault() =
runBlocking(IMMEDIATE) {
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
val caps =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
}
var latest: Boolean? = null
val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun mobileIsDefault_wifiDefault_mobileNotDefault() =
runBlocking(IMMEDIATE) {
val caps =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
}
var latest: Boolean? = null
val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun mobileIsDefault_ethernetDefault_mobileNotDefault() =
runBlocking(IMMEDIATE) {
val caps =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true)
}
var latest: Boolean? = null
val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun defaultConnectionIsValidated_startsAsFalse() {
assertThat(underTest.defaultConnectionIsValidated.value).isFalse()
}
@Test
fun defaultConnectionIsValidated_capsHaveValidated_isValidated() =
runBlocking(IMMEDIATE) {
val caps =
mock<NetworkCapabilities>().also {
whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true)
}
var latest: Boolean? = null
val job = underTest.defaultConnectionIsValidated.onEach { latest = it }.launchIn(this)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun defaultConnectionIsValidated_capsHaveNotValidated_isNotValidated() =
runBlocking(IMMEDIATE) {
val caps =
mock<NetworkCapabilities>().also {
whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(false)
}
var latest: Boolean? = null
val job = underTest.defaultConnectionIsValidated.onEach { latest = it }.launchIn(this)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
assertThat(latest).isFalse()
job.cancel()
}
@@ -752,7 +900,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
// the resources and then re-create the repo.
underTest =
MobileConnectionsRepositoryImpl(
connectivityManager,
connectivityRepository,
subscriptionManager,
telephonyManager,
logger,
@@ -860,12 +1008,6 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel()
}
private fun createCapabilities(connected: Boolean, validated: Boolean): NetworkCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(connected)
whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(validated)
}
private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())

View File

@@ -40,7 +40,7 @@ class FakeMobileIconInteractor(
)
)
override val isConnected = MutableStateFlow(true)
override val mobileIsDefault = MutableStateFlow(true)
private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.THREE_G)
override val networkTypeIconGroup = _iconGroup

View File

@@ -23,7 +23,6 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import kotlinx.coroutines.flow.MutableStateFlow
@@ -62,7 +61,7 @@ class FakeMobileIconsInteractor(
override val alwaysUseCdmaLevel = MutableStateFlow(false)
override val defaultDataSubId = MutableStateFlow(DEFAULT_DATA_SUB_ID)
override val defaultMobileNetworkConnectivity = MutableStateFlow(MobileConnectivityModel())
override val mobileIsDefault = MutableStateFlow(false)
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
override val defaultMobileIconMapping = _defaultMobileIconMapping

View File

@@ -60,7 +60,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
mobileIconsInteractor.activeDataConnectionHasDataEnabled,
mobileIconsInteractor.alwaysShowDataRatIcon,
mobileIconsInteractor.alwaysUseCdmaLevel,
mobileIconsInteractor.defaultMobileNetworkConnectivity,
mobileIconsInteractor.mobileIsDefault,
mobileIconsInteractor.defaultMobileIconMapping,
mobileIconsInteractor.defaultMobileIconGroup,
mobileIconsInteractor.defaultDataSubId,

View File

@@ -22,7 +22,6 @@ import androidx.test.filters.SmallTest
import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
@@ -307,11 +306,13 @@ class MobileIconsInteractorTest : SysuiTestCase() {
}
@Test
fun failedConnection_connected_validated_notFailed() =
fun failedConnection_default_validated_notFailed() =
testScope.runTest {
var latest: Boolean? = null
val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, true))
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
yield()
assertThat(latest).isFalse()
@@ -320,12 +321,13 @@ class MobileIconsInteractorTest : SysuiTestCase() {
}
@Test
fun failedConnection_notConnected_notValidated_notFailed() =
fun failedConnection_notDefault_notValidated_notFailed() =
testScope.runTest {
var latest: Boolean? = null
val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
connectionsRepository.setMobileConnectivity(MobileConnectivityModel(false, false))
connectionsRepository.mobileIsDefault.value = false
connectionsRepository.defaultConnectionIsValidated.value = false
yield()
assertThat(latest).isFalse()
@@ -334,12 +336,13 @@ class MobileIconsInteractorTest : SysuiTestCase() {
}
@Test
fun failedConnection_connected_notValidated_failed() =
fun failedConnection_default_notValidated_failed() =
testScope.runTest {
var latest: Boolean? = null
val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, false))
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = false
yield()
assertThat(latest).isTrue()
@@ -347,6 +350,46 @@ class MobileIconsInteractorTest : SysuiTestCase() {
job.cancel()
}
/** Regression test for b/275076959. */
@Test
fun failedConnection_dataSwitchInSameGroup_notFailed() =
testScope.runTest {
var latest: Boolean? = null
val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
// WHEN there's a data change in the same subscription group
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
connectionsRepository.defaultConnectionIsValidated.value = false
// THEN the default connection is *not* marked as failed because of forced validation
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun failedConnection_dataSwitchNotInSameGroup_isFailed() =
testScope.runTest {
var latestConnectionFailed: Boolean? = null
val job =
underTest.isDefaultConnectionFailed
.onEach { latestConnectionFailed = it }
.launchIn(this)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
// WHEN the connection is invalidated without a activeSubChangedInGroupEvent
connectionsRepository.defaultConnectionIsValidated.value = false
// THEN the connection is immediately marked as failed
assertThat(latestConnectionFailed).isTrue()
job.cancel()
}
@Test
fun alwaysShowDataRatIcon_configHasTrue() =
testScope.runTest {
@@ -412,137 +455,69 @@ class MobileIconsInteractorTest : SysuiTestCase() {
}
@Test
fun `default mobile connectivity - uses repo value`() =
fun mobileIsDefault_usesRepoValue() =
testScope.runTest {
var latest: MobileConnectivityModel? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
var latest: Boolean? = null
val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
var expected = MobileConnectivityModel(isConnected = true, isValidated = true)
connectionsRepository.setMobileConnectivity(expected)
assertThat(latest).isEqualTo(expected)
connectionsRepository.mobileIsDefault.value = true
assertThat(latest).isTrue()
expected = MobileConnectivityModel(isConnected = false, isValidated = true)
connectionsRepository.setMobileConnectivity(expected)
assertThat(latest).isEqualTo(expected)
connectionsRepository.mobileIsDefault.value = false
assertThat(latest).isFalse()
expected = MobileConnectivityModel(isConnected = true, isValidated = false)
connectionsRepository.setMobileConnectivity(expected)
assertThat(latest).isEqualTo(expected)
expected = MobileConnectivityModel(isConnected = false, isValidated = false)
connectionsRepository.setMobileConnectivity(expected)
assertThat(latest).isEqualTo(expected)
connectionsRepository.mobileIsDefault.value = true
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun `data switch - in same group - validated matches previous value`() =
testScope.runTest {
var latest: MobileConnectivityModel? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = true,
isValidated = true,
)
)
// Trigger a data change in the same subscription group
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = true,
)
)
job.cancel()
}
// The data switch tests are mostly testing the [forcingCellularValidation] flow, but that flow
// is private and can only be tested by looking at [isDefaultConnectionFailed].
@Test
fun `data switch - in same group - validated matches previous value - expires after 2s`() =
testScope.runTest {
var latest: MobileConnectivityModel? = null
var latestConnectionFailed: Boolean? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
underTest.isDefaultConnectionFailed
.onEach { latestConnectionFailed = it }
.launchIn(this)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = true,
isValidated = true,
)
)
// Trigger a data change in the same subscription group
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
// Trigger a data change in the same subscription group that's not yet validated
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
// After 1s, the force validation bit is still present
connectionsRepository.defaultConnectionIsValidated.value = false
// After 1s, the force validation bit is still present, so the connection is not marked
// as failed
advanceTimeBy(1000)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = true,
)
)
assertThat(latestConnectionFailed).isFalse()
// After 2s, the force validation expires
// After 2s, the force validation expires so the connection updates to failed
advanceTimeBy(1001)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
assertThat(latestConnectionFailed).isTrue()
job.cancel()
}
@Test
fun `data switch - in same group - not validated - uses new value immediately`() =
fun `data switch - in same group - not validated - immediately marked as failed`() =
testScope.runTest {
var latest: MobileConnectivityModel? = null
var latestConnectionFailed: Boolean? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
underTest.isDefaultConnectionFailed
.onEach { latestConnectionFailed = it }
.launchIn(this)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = false
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = true,
isValidated = false,
)
)
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
assertThat(latestConnectionFailed).isTrue()
job.cancel()
}
@@ -550,60 +525,34 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun `data switch - lose validation - then switch happens - clears forced bit`() =
testScope.runTest {
var latest: MobileConnectivityModel? = null
var latestConnectionFailed: Boolean? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
underTest.isDefaultConnectionFailed
.onEach { latestConnectionFailed = it }
.launchIn(this)
// GIVEN the network starts validated
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = true,
isValidated = true,
)
)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
// WHEN a data change happens in the same group
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
// WHEN the validation bit is lost
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
connectionsRepository.defaultConnectionIsValidated.value = false
// WHEN another data change happens in the same group
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
// THEN the forced validation bit is still removed after 2s
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = true,
)
)
// THEN the forced validation bit is still used...
assertThat(latestConnectionFailed).isFalse()
advanceTimeBy(1000)
assertThat(latestConnectionFailed).isFalse()
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = true,
)
)
// ... but expires after 2s
advanceTimeBy(1001)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
assertThat(latestConnectionFailed).isTrue()
job.cancel()
}
@@ -611,15 +560,13 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test
fun `data switch - while already forcing validation - resets clock`() =
testScope.runTest {
var latest: MobileConnectivityModel? = null
var latestConnectionFailed: Boolean? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = true,
isValidated = true,
)
)
underTest.isDefaultConnectionFailed
.onEach { latestConnectionFailed = it }
.launchIn(this)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
@@ -627,65 +574,17 @@ class MobileIconsInteractorTest : SysuiTestCase() {
// WHEN another change in same group event happens
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
connectionsRepository.defaultConnectionIsValidated.value = false
// THEN the forced validation remains for exactly 2 more seconds from now
// 1.500s from second event
advanceTimeBy(1500)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = true,
)
)
assertThat(latestConnectionFailed).isFalse()
// 2.001s from the second event
advanceTimeBy(501)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
job.cancel()
}
@Test
fun `data switch - not in same group - uses new values`() =
testScope.runTest {
var latest: MobileConnectivityModel? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = true,
isValidated = true,
)
)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
assertThat(latestConnectionFailed).isTrue()
job.cancel()
}

View File

@@ -400,10 +400,10 @@ class MobileIconViewModelTest : SysuiTestCase() {
}
@Test
fun `network type - alwaysShow - shown when not connected`() =
fun `network type - alwaysShow - shown when not default`() =
testScope.runTest {
interactor.setIconGroup(THREE_G)
interactor.isConnected.value = false
interactor.mobileIsDefault.value = false
interactor.alwaysShowDataRatIcon.value = true
var latest: Icon? = null
@@ -420,11 +420,11 @@ class MobileIconViewModelTest : SysuiTestCase() {
}
@Test
fun `network type - not shown when not connected`() =
fun `network type - not shown when not default`() =
testScope.runTest {
interactor.setIconGroup(THREE_G)
interactor.isDataConnected.value = true
interactor.isConnected.value = false
interactor.mobileIsDefault.value = false
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)

View File

@@ -0,0 +1,70 @@
/*
* 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.shared.data.model
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.log.LogMessageImpl
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@SmallTest
class DefaultConnectionModelTest : SysuiTestCase() {
@Test
fun messageInitializerAndPrinter_isValidatedFalse_hasCorrectInfo() {
val model =
DefaultConnectionModel(
DefaultConnectionModel.Wifi(isDefault = false),
DefaultConnectionModel.Mobile(isDefault = true),
DefaultConnectionModel.CarrierMerged(isDefault = true),
DefaultConnectionModel.Ethernet(isDefault = false),
isValidated = false,
)
val message = LogMessageImpl.create()
model.messageInitializer(message)
val messageString = model.messagePrinter(message)
assertThat(messageString).contains("wifi.isDefault=false")
assertThat(messageString).contains("mobile.isDefault=true")
assertThat(messageString).contains("carrierMerged.isDefault=true")
assertThat(messageString).contains("ethernet.isDefault=false")
assertThat(messageString).contains("isValidated=false")
}
@Test
fun messageInitializerAndPrinter_isValidatedTrue_hasCorrectInfo() {
val model =
DefaultConnectionModel(
DefaultConnectionModel.Wifi(isDefault = true),
DefaultConnectionModel.Mobile(isDefault = false),
DefaultConnectionModel.CarrierMerged(isDefault = false),
DefaultConnectionModel.Ethernet(isDefault = false),
isValidated = true,
)
val message = LogMessageImpl.create()
model.messageInitializer(message)
val messageString = model.messagePrinter(message)
assertThat(messageString).contains("wifi.isDefault=true")
assertThat(messageString).contains("mobile.isDefault=false")
assertThat(messageString).contains("carrierMerged.isDefault=false")
assertThat(messageString).contains("ethernet.isDefault=false")
assertThat(messageString).contains("isValidated=true")
}
}

View File

@@ -16,31 +16,39 @@
package com.android.systemui.statusbar.pipeline.shared.data.repository
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.pipeline.shared.ConnectivityInputLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.DEFAULT_HIDDEN_ICONS_RESOURCE
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.HIDDEN_ICONS_TUNABLE_KEY
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.yield
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -50,51 +58,30 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
private lateinit var underTest: ConnectivityRepositoryImpl
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var connectivitySlots: ConnectivitySlots
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var logger: ConnectivityInputLogger
private lateinit var scope: CoroutineScope
private lateinit var testScope: TestScope
@Mock private lateinit var tunerService: TunerService
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
scope = CoroutineScope(IMMEDIATE)
underTest =
ConnectivityRepositoryImpl(
connectivitySlots,
context,
dumpManager,
logger,
scope,
tunerService,
)
}
@After
fun tearDown() {
scope.cancel()
testScope = TestScope(UnconfinedTestDispatcher())
createAndSetRepo()
}
@Test
fun forceHiddenSlots_initiallyGetsDefault() =
runBlocking(IMMEDIATE) {
testScope.runTest {
setUpEthernetWifiMobileSlotNames()
context
.getOrCreateTestableResources()
.addOverride(DEFAULT_HIDDEN_ICONS_RESOURCE, arrayOf(SLOT_WIFI, SLOT_ETHERNET))
// Re-create our [ConnectivityRepositoryImpl], since it fetches
// config_statusBarIconsToExclude when it's first constructed
underTest =
ConnectivityRepositoryImpl(
connectivitySlots,
context,
dumpManager,
logger,
scope,
tunerService,
)
createAndSetRepo()
var latest: Set<ConnectivitySlot>? = null
val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
@@ -106,7 +93,7 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
@Test
fun forceHiddenSlots_slotNamesAdded_flowHasSlots() =
runBlocking(IMMEDIATE) {
testScope.runTest {
setUpEthernetWifiMobileSlotNames()
var latest: Set<ConnectivitySlot>? = null
@@ -121,7 +108,7 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
@Test
fun forceHiddenSlots_wrongKey_doesNotUpdate() =
runBlocking(IMMEDIATE) {
testScope.runTest {
setUpEthernetWifiMobileSlotNames()
var latest: Set<ConnectivitySlot>? = null
@@ -141,22 +128,14 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
@Test
fun forceHiddenSlots_slotNamesAddedThenNull_flowHasDefault() =
runBlocking(IMMEDIATE) {
testScope.runTest {
setUpEthernetWifiMobileSlotNames()
context
.getOrCreateTestableResources()
.addOverride(DEFAULT_HIDDEN_ICONS_RESOURCE, arrayOf(SLOT_WIFI, SLOT_ETHERNET))
// Re-create our [ConnectivityRepositoryImpl], since it fetches
// config_statusBarIconsToExclude when it's first constructed
underTest =
ConnectivityRepositoryImpl(
connectivitySlots,
context,
dumpManager,
logger,
scope,
tunerService,
)
createAndSetRepo()
var latest: Set<ConnectivitySlot>? = null
val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
@@ -177,7 +156,7 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
@Test
fun forceHiddenSlots_someInvalidSlotNames_flowHasValidSlotsOnly() =
runBlocking(IMMEDIATE) {
testScope.runTest {
var latest: Set<ConnectivitySlot>? = null
val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
@@ -193,7 +172,7 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
@Test
fun forceHiddenSlots_someEmptySlotNames_flowHasValidSlotsOnly() =
runBlocking(IMMEDIATE) {
testScope.runTest {
setUpEthernetWifiMobileSlotNames()
var latest: Set<ConnectivitySlot>? = null
@@ -210,7 +189,7 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
@Test
fun forceHiddenSlots_allInvalidOrEmptySlotNames_flowHasEmpty() =
runBlocking(IMMEDIATE) {
testScope.runTest {
var latest: Set<ConnectivitySlot>? = null
val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
@@ -231,7 +210,7 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
@Test
fun forceHiddenSlots_newSubscriberGetsCurrentValue() =
runBlocking(IMMEDIATE) {
testScope.runTest {
setUpEthernetWifiMobileSlotNames()
var latest1: Set<ConnectivitySlot>? = null
@@ -252,9 +231,339 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
job2.cancel()
}
@Test
fun defaultConnections_noTransports_nothingIsDefault() =
testScope.runTest {
var latest: DefaultConnectionModel? = null
val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.mobile.isDefault).isFalse()
assertThat(latest!!.wifi.isDefault).isFalse()
assertThat(latest!!.ethernet.isDefault).isFalse()
assertThat(latest!!.carrierMerged.isDefault).isFalse()
job.cancel()
}
@Test
fun defaultConnections_cellularTransport_mobileIsDefault() =
testScope.runTest {
var latest: DefaultConnectionModel? = null
val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.mobile.isDefault).isTrue()
assertThat(latest!!.wifi.isDefault).isFalse()
assertThat(latest!!.ethernet.isDefault).isFalse()
assertThat(latest!!.carrierMerged.isDefault).isFalse()
job.cancel()
}
@Test
fun defaultConnections_wifiTransport_wifiIsDefault() =
testScope.runTest {
var latest: DefaultConnectionModel? = null
val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.wifi.isDefault).isTrue()
assertThat(latest!!.ethernet.isDefault).isFalse()
assertThat(latest!!.carrierMerged.isDefault).isFalse()
assertThat(latest!!.mobile.isDefault).isFalse()
job.cancel()
}
@Test
fun defaultConnections_ethernetTransport_ethernetIsDefault() =
testScope.runTest {
var latest: DefaultConnectionModel? = null
val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true)
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.ethernet.isDefault).isTrue()
assertThat(latest!!.wifi.isDefault).isFalse()
assertThat(latest!!.carrierMerged.isDefault).isFalse()
assertThat(latest!!.mobile.isDefault).isFalse()
job.cancel()
}
@Test
fun defaultConnections_carrierMergedViaWifi_wifiAndCarrierMergedDefault() =
testScope.runTest {
var latest: DefaultConnectionModel? = null
val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
whenever(it.transportInfo).thenReturn(carrierMergedInfo)
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.wifi.isDefault).isTrue()
assertThat(latest!!.carrierMerged.isDefault).isTrue()
assertThat(latest!!.mobile.isDefault).isFalse()
job.cancel()
}
@Test
fun defaultConnections_carrierMergedViaMobile_mobileCarrierMergedWifiDefault() =
testScope.runTest {
var latest: DefaultConnectionModel? = null
val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
whenever(it.transportInfo).thenReturn(carrierMergedInfo)
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.mobile.isDefault).isTrue()
assertThat(latest!!.carrierMerged.isDefault).isTrue()
assertThat(latest!!.wifi.isDefault).isTrue()
job.cancel()
}
@Test
fun defaultConnections_carrierMergedViaWifiWithVcnTransport_wifiAndCarrierMergedDefault() =
testScope.runTest {
var latest: DefaultConnectionModel? = null
val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.wifi.isDefault).isTrue()
assertThat(latest!!.carrierMerged.isDefault).isTrue()
assertThat(latest!!.mobile.isDefault).isFalse()
job.cancel()
}
@Test
fun defaultConnections_carrierMergedViaMobileWithVcnTransport_mobileCarrierMergedWifiDefault() =
testScope.runTest {
var latest: DefaultConnectionModel? = null
val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.mobile.isDefault).isTrue()
assertThat(latest!!.carrierMerged.isDefault).isTrue()
assertThat(latest!!.wifi.isDefault).isTrue()
job.cancel()
}
@Test
fun defaultConnections_notCarrierMergedViaWifi_carrierMergedNotDefault() =
testScope.runTest {
var latest: DefaultConnectionModel? = null
val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(false) }
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
whenever(it.transportInfo).thenReturn(carrierMergedInfo)
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.carrierMerged.isDefault).isFalse()
job.cancel()
}
@Test
fun defaultConnections_notCarrierMergedViaMobile_carrierMergedNotDefault() =
testScope.runTest {
var latest: DefaultConnectionModel? = null
val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(false) }
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
whenever(it.transportInfo).thenReturn(carrierMergedInfo)
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.carrierMerged.isDefault).isFalse()
job.cancel()
}
@Test
fun defaultConnections_transportInfoNotWifi_wifiNotDefault() =
testScope.runTest {
var latest: DefaultConnectionModel? = null
val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.transportInfo).thenReturn(mock())
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.wifi.isDefault).isFalse()
job.cancel()
}
@Test
fun defaultConnections_multipleTransports_multipleDefault() =
testScope.runTest {
var latest: DefaultConnectionModel? = null
val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true)
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.mobile.isDefault).isTrue()
assertThat(latest!!.ethernet.isDefault).isTrue()
assertThat(latest!!.wifi.isDefault).isTrue()
job.cancel()
}
@Test
fun defaultConnections_hasValidated_isValidatedTrue() =
testScope.runTest {
var latest: DefaultConnectionModel? = null
val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED))
.thenReturn(true)
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.isValidated).isTrue()
job.cancel()
}
@Test
fun defaultConnections_noValidated_isValidatedFalse() =
testScope.runTest {
var latest: DefaultConnectionModel? = null
val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED))
.thenReturn(false)
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest!!.isValidated).isFalse()
job.cancel()
}
private fun createAndSetRepo() {
underTest =
ConnectivityRepositoryImpl(
connectivityManager,
connectivitySlots,
context,
dumpManager,
logger,
testScope.backgroundScope,
tunerService,
)
}
private fun getTunable(): TunerService.Tunable {
val callbackCaptor = argumentCaptor<TunerService.Tunable>()
Mockito.verify(tunerService).addTunable(callbackCaptor.capture(), any())
verify(tunerService).addTunable(callbackCaptor.capture(), any())
return callbackCaptor.value!!
}
@@ -265,10 +574,18 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
whenever(connectivitySlots.getSlotFromName(SLOT_MOBILE)).thenReturn(ConnectivitySlot.MOBILE)
}
companion object {
private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
return callbackCaptor.value!!
}
private companion object {
private const val SLOT_ETHERNET = "ethernet"
private const val SLOT_WIFI = "wifi"
private const val SLOT_MOBILE = "mobile"
private val IMMEDIATE = Dispatchers.Main.immediate
const val NETWORK_ID = 45
val NETWORK = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
}
}

View File

@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.shared.data.repository
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -26,6 +27,9 @@ class FakeConnectivityRepository : ConnectivityRepository {
MutableStateFlow(emptySet())
override val forceHiddenSlots: StateFlow<Set<ConnectivitySlot>> = _forceHiddenIcons
override val defaultConnections: StateFlow<DefaultConnectionModel> =
MutableStateFlow(DefaultConnectionModel())
fun setForceHiddenIcons(hiddenIcons: Set<ConnectivitySlot>) {
_forceHiddenIcons.value = hiddenIcons
}

View File

@@ -23,6 +23,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
@@ -78,6 +79,7 @@ class WifiRepositorySwitcherTest : SysuiTestCase() {
WifiRepositoryImpl(
fakeBroadcastDispatcher,
connectivityManager,
FakeConnectivityRepository(),
logger,
tableLogger,
mainExecutor,

View File

@@ -34,7 +34,10 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
@@ -78,6 +81,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var wifiManager: WifiManager
private lateinit var executor: Executor
private lateinit var scope: CoroutineScope
private lateinit var connectivityRepository: ConnectivityRepository
@Before
fun setUp() {
@@ -93,6 +97,18 @@ class WifiRepositoryImplTest : SysuiTestCase() {
.thenReturn(flowOf(Unit))
executor = FakeExecutor(FakeSystemClock())
scope = CoroutineScope(IMMEDIATE)
connectivityRepository =
ConnectivityRepositoryImpl(
connectivityManager,
ConnectivitySlots(context),
context,
mock(),
mock(),
scope,
mock(),
)
underTest = createRepo()
}
@@ -302,13 +318,77 @@ class WifiRepositoryImplTest : SysuiTestCase() {
}
@Test
fun isWifiDefault_cellularVcnNetwork_isTrue() =
fun isWifiDefault_carrierMergedViaCellular_isTrue() =
runBlocking(IMMEDIATE) {
val job = underTest.isWifiDefault.launchIn(this)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
val capabilities =
mock<NetworkCapabilities>().apply {
whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
whenever(this.transportInfo).thenReturn(carrierMergedInfo)
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(underTest.isWifiDefault.value).isTrue()
job.cancel()
}
@Test
fun isWifiDefault_carrierMergedViaCellular_withVcnTransport_isTrue() =
runBlocking(IMMEDIATE) {
val job = underTest.isWifiDefault.launchIn(this)
val capabilities =
mock<NetworkCapabilities>().apply {
whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(underTest.isWifiDefault.value).isTrue()
job.cancel()
}
@Test
fun isWifiDefault_carrierMergedViaWifi_isTrue() =
runBlocking(IMMEDIATE) {
val job = underTest.isWifiDefault.launchIn(this)
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
val capabilities =
mock<NetworkCapabilities>().apply {
whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
whenever(this.transportInfo).thenReturn(carrierMergedInfo)
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(underTest.isWifiDefault.value).isTrue()
job.cancel()
}
@Test
fun isWifiDefault_carrierMergedViaWifi_withVcnTransport_isTrue() =
runBlocking(IMMEDIATE) {
val job = underTest.isWifiDefault.launchIn(this)
val capabilities =
mock<NetworkCapabilities>().apply {
whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
}
@@ -931,6 +1011,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
return WifiRepositoryImpl(
broadcastDispatcher,
connectivityManager,
connectivityRepository,
logger,
tableLogger,
executor,