diff options
| author | 2023-03-20 17:49:05 +0000 | |
|---|---|---|
| committer | 2023-03-24 19:11:24 +0000 | |
| commit | 0202e4a3373d81f5742d34cd925749800194421b (patch) | |
| tree | b6e184804ba6ff2f7275607fe2eb686dbdefe0f9 | |
| parent | ae05dfe4d4b6a3d8ae0f80e452a11f8203d28716 (diff) | |
[SB Refactor] Define a `defaultConnections` flow in the shared repo.
This CL:
(1) Updates the wifi and mobile repos to use this shared flow instead
of defining their own default network callbacks.
(2) Updates `MobileConnectionsRepoImpl.defaultMobileNetworkConnectivity`
to have `isDefault = true` when mobile **or** carrier merged is the
default connection. This is needed to fix b/272586234: since carrier
merged is displayed as a mobile network, but we only display mobile
network information if mobile is default, we need `isDefault` to be true
even in the carrier merged case.
Bug: 272586234
Test: all tests in statusbar.pipeline
Test: manual: verify wifi icon still works and updates with normal wifi
Test: unfortunately, it's prohibitively difficult to set up a valid
carrier merged connection to test this, so I was not able to manually
test carrier merged. I wrote extensive unit tests instead
Change-Id: I7922b47547c3a4d186e78b76f7fae9cf3460d4d6
14 files changed, 895 insertions, 232 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt index 73bf188857c9..68cbbceb056d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt @@ -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, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index a6fa0c55888d..8c93bf7c2198 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -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 @@ -50,6 +44,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameMode 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 @@ -79,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, @@ -260,50 +255,11 @@ constructor( subIdRepositoryCache[subId] ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it } - @SuppressLint("MissingPermission") - private val defaultMobileNetworkConnectivity: StateFlow<DefaultConnectionModel> = - conflatedCallbackFlow { - val callback = - object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) { - override fun onLost(network: Network) { - logger.logOnLost(network, isDefaultNetworkCallback = true) - trySend( - DefaultConnectionModel( - mobileIsDefault = false, - defaultConnectionIsValidated = false, - ) - ) - } - - override fun onCapabilitiesChanged( - network: Network, - caps: NetworkCapabilities - ) { - logger.logOnCapabilitiesChanged( - network, - caps, - isDefaultNetworkCallback = true, - ) - trySend( - DefaultConnectionModel( - mobileIsDefault = caps.hasTransport(TRANSPORT_CELLULAR), - defaultConnectionIsValidated = - caps.hasCapability(NET_CAPABILITY_VALIDATED), - ) - ) - } - } - - connectivityManager.registerDefaultNetworkCallback(callback) - - awaitClose { connectivityManager.unregisterNetworkCallback(callback) } - } - .distinctUntilChanged() - .stateIn(scope, SharingStarted.WhileSubscribed(), DefaultConnectionModel()) - override val mobileIsDefault: StateFlow<Boolean> = - defaultMobileNetworkConnectivity - .map { it.mobileIsDefault } + 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, @@ -314,8 +270,8 @@ constructor( .stateIn(scope, SharingStarted.WhileSubscribed(), false) override val defaultConnectionIsValidated: StateFlow<Boolean> = - defaultMobileNetworkConnectivity - .map { it.defaultConnectionIsValidated } + connectivityRepository.defaultConnections + .map { it.isValidated } .distinctUntilChanged() .logDiffsForTable( tableLogger, @@ -407,11 +363,6 @@ constructor( groupUuid = groupUuid, ) - private data class DefaultConnectionModel( - val mobileIsDefault: Boolean = false, - val defaultConnectionIsValidated: Boolean = false, - ) - companion object { private const val LOGGING_PREFIX = "Repo" } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt index 95548b84f769..82492babba46 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt @@ -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" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt new file mode 100644 index 000000000000..2a02687f0761 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt @@ -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"})" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt index 5d9ba018822d..6479f3d9f8a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt @@ -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 + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt index b5e7b7a13505..f80aa688268f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt @@ -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, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt deleted file mode 100644 index 7c9351c8495d..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt +++ /dev/null @@ -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() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt index 07c8cee9a3d4..0b2028532307 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt @@ -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, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 742abc273c8d..d65277f37ec4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -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 @@ -43,6 +47,9 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierCon 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 @@ -80,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 @@ -120,6 +128,17 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } } + connectivityRepository = + ConnectivityRepositoryImpl( + connectivityManager, + ConnectivitySlots(context), + context, + mock(), + mock(), + scope, + mock(), + ) + wifiRepository = FakeWifiRepository() carrierConfigRepository = @@ -158,7 +177,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { underTest = MobileConnectionsRepositoryImpl( - connectivityManager, + connectivityRepository, subscriptionManager, telephonyManager, logger, @@ -707,6 +726,128 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { 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 mobileIsDefault_carrierMergedViaMobile_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(carrierMergedInfo) + } + + var latest: Boolean? = null + val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + + assertThat(latest).isTrue() + + job.cancel() + } + + /** Regression test for b/272586234. */ + @Test + fun mobileIsDefault_carrierMergedViaWifiWithVcnTransport_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(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_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() @@ -759,7 +900,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { // the resources and then re-create the repo. underTest = MobileConnectionsRepositoryImpl( - connectivityManager, + connectivityRepository, subscriptionManager, telephonyManager, logger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt new file mode 100644 index 000000000000..03cd94fa9d70 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt @@ -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") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt index 46b751b60e15..87d4f5c618a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt @@ -16,17 +16,27 @@ 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.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn @@ -38,7 +48,7 @@ import kotlinx.coroutines.yield 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 @@ -48,6 +58,7 @@ 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 @@ -220,9 +231,327 @@ 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, @@ -234,7 +563,7 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { 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!! } @@ -245,9 +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" + + const val NETWORK_ID = 45 + val NETWORK = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt index bd70034b13de..9e825b704851 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt @@ -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 } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt index 25678b0530f6..70d2d2b68460 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt @@ -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, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt index c7b31bcf9a0c..f69e9a39909b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt @@ -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, |