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:
committed by
Android (Google) Code Review
commit
f9d5d4c9d7
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"})"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -60,7 +60,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
|
||||
mobileIconsInteractor.activeDataConnectionHasDataEnabled,
|
||||
mobileIconsInteractor.alwaysShowDataRatIcon,
|
||||
mobileIconsInteractor.alwaysUseCdmaLevel,
|
||||
mobileIconsInteractor.defaultMobileNetworkConnectivity,
|
||||
mobileIconsInteractor.mobileIsDefault,
|
||||
mobileIconsInteractor.defaultMobileIconMapping,
|
||||
mobileIconsInteractor.defaultMobileIconGroup,
|
||||
mobileIconsInteractor.defaultDataSubId,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user