diff options
| author | 2022-08-12 18:58:37 +0000 | |
|---|---|---|
| committer | 2022-08-22 17:42:37 +0000 | |
| commit | db2c8323ef437b60baaa5cb5a0fc8e1f28583a45 (patch) | |
| tree | 111fbd3432ccff60eb59777ae1f2766299a378c8 | |
| parent | 829e25dfd3be305ccc87e98ecf3b264515575357 (diff) | |
[SB Refactor] Implement the wifi network flow using the
NetworkCallback.
This migrates the information from NetworkCapabilitiesRepo into
WifiRepositoryImpl. So, we can now delete NetworkCapabilitiesRepo and
CollectivityInfoCollector (which was just listening to NCR).
This implementation should approximately mimic
WifiStatusTracker.mNetworkCallback, but in a hopefully much more
straightfoward way!
Test: manual: compared logs from old system and new system and verified
the wifi updates are the same in both
Test: manual: verified new pipeline output logs
Test: WifiRepositoryImplTest
Bug: 238425913
Change-Id: I8bbe6cc21cd1c1ae2c1d364b94a2fb87d0a7821e
18 files changed, 591 insertions, 690 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index b9c4030d9d0e..a822e185479a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -600,6 +600,9 @@ public class Utils { * Returns the WifiInfo for the underlying WiFi network of the VCN network, returns null if the * input NetworkCapabilities is not for a VCN network with underlying WiFi network. * + * TODO(b/238425913): Move this method to be inside systemui not settingslib once we've migrated + * off of {@link WifiStatusTracker} and {@link NetworkControllerImpl}. + * * @param networkCapabilities NetworkCapabilities of the network. */ @Nullable diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt deleted file mode 100644 index 780a02da3410..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt +++ /dev/null @@ -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 - -import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo -import kotlinx.coroutines.flow.StateFlow - -/** - * Interface exposing a flow for raw connectivity information. Clients should collect on - * [rawConnectivityInfoFlow] to get updates on connectivity information. - * - * Note: [rawConnectivityInfoFlow] should be a *hot* flow, so that we only create one instance of it - * and all clients get references to the same flow. - * - * This will be used for the new status bar pipeline to compile information we need to display some - * of the icons in the RHS of the status bar. - */ -interface ConnectivityInfoCollector { - val rawConnectivityInfoFlow: StateFlow<RawConnectivityInfo> -} - -/** - * An object containing all of the raw connectivity information. - * - * Importantly, all the information in this object should not be processed at all (i.e., the data - * that we receive from callbacks should be piped straight into this object and not be filtered, - * manipulated, or processed in any way). Instead, any listeners on - * [ConnectivityInfoCollector.rawConnectivityInfoFlow] can do the processing. - * - * This allows us to keep all the processing in one place which is beneficial for logging and - * debugging purposes. - */ -data class RawConnectivityInfo( - val networkCapabilityInfo: Map<Int, NetworkCapabilityInfo> = emptyMap(), -) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt deleted file mode 100644 index 99798f9b38d3..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt +++ /dev/null @@ -1,46 +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 - -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilitiesRepo -import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn - -/** - * The real implementation of [ConnectivityInfoCollector] that will collect information from all the - * relevant connectivity callbacks and compile it into [rawConnectivityInfoFlow]. - */ -@SysUISingleton -class ConnectivityInfoCollectorImpl @Inject constructor( - networkCapabilitiesRepo: NetworkCapabilitiesRepo, - @Application scope: CoroutineScope, -) : ConnectivityInfoCollector { - override val rawConnectivityInfoFlow: StateFlow<RawConnectivityInfo> = - // TODO(b/238425913): Collect all the separate flows for individual raw information into - // this final flow. - networkCapabilitiesRepo.dataStream - .map { - RawConnectivityInfo(networkCapabilityInfo = it) - } - .stateIn(scope, started = Lazily, initialValue = RawConnectivityInfo()) -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt index 64c47f679142..0b018168fbd5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt @@ -20,31 +20,21 @@ import android.content.Context import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel import javax.inject.Inject import javax.inject.Provider import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** - * A processor that transforms raw connectivity information that we get from callbacks and turns it - * into a list of displayable connectivity information. + * A temporary object that collects on [WifiViewModel] flows for debugging purposes. * - * This will be used for the new status bar pipeline to calculate the list of icons that should be - * displayed in the RHS of the status bar. - * - * Anyone can listen to [processedInfoFlow] to get updates to the processed data. + * This will eventually get migrated to a view binder that will use the flow outputs to set state on + * views. For now, this just collects on flows so that the information gets logged. */ @SysUISingleton class ConnectivityInfoProcessor @Inject constructor( - connectivityInfoCollector: ConnectivityInfoCollector, context: Context, // TODO(b/238425913): Don't use the application scope; instead, use the status bar view's // scope so we only do work when there's UI that cares about it. @@ -52,21 +42,6 @@ class ConnectivityInfoProcessor @Inject constructor( private val statusBarPipelineFlags: StatusBarPipelineFlags, private val wifiViewModelProvider: Provider<WifiViewModel>, ) : CoreStartable(context) { - // Note: This flow will not start running until a client calls `collect` on it, which means that - // [connectivityInfoCollector]'s flow will also not start anything until that `collect` call - // happens. - // TODO(b/238425913): Delete this. - val processedInfoFlow: Flow<ProcessedConnectivityInfo> = - if (!statusBarPipelineFlags.isNewPipelineEnabled()) - emptyFlow() - else connectivityInfoCollector.rawConnectivityInfoFlow - .map { it.process() } - .stateIn( - scope, - started = Lazily, - initialValue = ProcessedConnectivityInfo() - ) - override fun start() { if (!statusBarPipelineFlags.isNewPipelineEnabled()) { return @@ -77,17 +52,4 @@ class ConnectivityInfoProcessor @Inject constructor( wifiViewModelProvider.get().isActivityInVisible.collect { } } } - - private fun RawConnectivityInfo.process(): ProcessedConnectivityInfo { - // TODO(b/238425913): Actually process the raw info into meaningful data. - return ProcessedConnectivityInfo(this.networkCapabilityInfo) - } } - -/** - * An object containing connectivity info that has been processed into data that can be directly - * used by the status bar (and potentially other SysUI areas) to display icons. - */ -data class ProcessedConnectivityInfo( - val networkCapabilityInfo: Map<Int, NetworkCapabilityInfo> = emptyMap(), -) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 7abe19e7bbe0..88eccb5ba47f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -17,8 +17,6 @@ package com.android.systemui.statusbar.pipeline.dagger import com.android.systemui.CoreStartable -import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollector -import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollectorImpl import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl @@ -36,10 +34,5 @@ abstract class StatusBarPipelineModule { abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable @Binds - abstract fun provideConnectivityInfoCollector( - impl: ConnectivityInfoCollectorImpl - ): ConnectivityInfoCollector - - @Binds abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt index a5fff5e65ebc..2a89309f7200 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt @@ -31,6 +31,9 @@ import kotlinx.coroutines.flow.onEach class ConnectivityPipelineLogger @Inject constructor( @StatusBarConnectivityLog private val buffer: LogBuffer, ) { + /** + * Logs a change in one of the **raw inputs** to the connectivity pipeline. + */ fun logInputChange(callbackName: String, changeInfo: String) { buffer.log( SB_LOGGING_TAG, @@ -45,6 +48,41 @@ class ConnectivityPipelineLogger @Inject constructor( ) } + /** + * Logs a **data transformation** that we performed within the connectivity pipeline. + */ + fun logTransformation(transformationName: String, oldValue: Any?, newValue: Any?) { + if (oldValue == newValue) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + str1 = transformationName + str2 = oldValue.toString() + }, + { + "Transform: $str1: $str2 (transformation didn't change it)" + } + ) + } else { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + str1 = transformationName + str2 = oldValue.toString() + str3 = newValue.toString() + }, + { + "Transform: $str1: $str2 -> $str3" + } + ) + } + } + + /** + * Logs a change in one of the **outputs** to the connectivity pipeline. + */ fun logOutputChange(outputParamName: String, changeInfo: String) { buffer.log( SB_LOGGING_TAG, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt deleted file mode 100644 index 1b7332265b7a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt +++ /dev/null @@ -1,29 +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.wifi.data.model - -/** Provides information about the current wifi state. */ -data class WifiModel( - /** See [android.net.wifi.WifiInfo.ssid]. */ - val ssid: String? = null, - /** See [android.net.wifi.WifiInfo.isPasspointAp]. */ - val isPasspointAccessPoint: Boolean = false, - /** See [android.net.wifi.WifiInfo.isOsuAp]. */ - val isOnlineSignUpForPasspointAccessPoint: Boolean = false, - /** See [android.net.wifi.WifiInfo.passpointProviderFriendlyName]. */ - val passpointProviderFriendlyName: String? = null, -) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt new file mode 100644 index 000000000000..5566fa6b5835 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt @@ -0,0 +1,46 @@ +/* + * 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.wifi.data.model + +/** Provides information about the current wifi network. */ +sealed class WifiNetworkModel { + /** A model representing that we have no active wifi network. */ + object Inactive : WifiNetworkModel() + + /** Provides information about an active wifi network. */ + class Active( + /** + * The [android.net.Network.netId] we received from + * [android.net.ConnectivityManager.NetworkCallback] in association with this wifi network. + * + * Importantly, **not** [android.net.wifi.WifiInfo.getNetworkId]. + */ + val networkId: Int, + + /** See [android.net.wifi.WifiInfo.ssid]. */ + val ssid: String? = null, + + /** See [android.net.wifi.WifiInfo.isPasspointAp]. */ + val isPasspointAccessPoint: Boolean = false, + + /** See [android.net.wifi.WifiInfo.isOsuAp]. */ + val isOnlineSignUpForPasspointAccessPoint: Boolean = false, + + /** See [android.net.wifi.WifiInfo.passpointProviderFriendlyName]. */ + val passpointProviderFriendlyName: String? = null, + ) : WifiNetworkModel() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt deleted file mode 100644 index 6c0a44524e3a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2021 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. - */ - -@file:OptIn(ExperimentalCoroutinesApi::class) - -package com.android.systemui.statusbar.pipeline.wifi.data.repository - -import android.annotation.SuppressLint -import android.net.ConnectivityManager -import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkRequest -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger -import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.stateIn - -/** - * Repository that contains all relevant [NetworkCapabilities] for the current networks. - * - * TODO(b/238425913): Figure out how to merge this with [WifiRepository]. - */ -@SysUISingleton -class NetworkCapabilitiesRepo @Inject constructor( - connectivityManager: ConnectivityManager, - @Application scope: CoroutineScope, - logger: ConnectivityPipelineLogger, -) { - @SuppressLint("MissingPermission") - val dataStream: StateFlow<Map<Int, NetworkCapabilityInfo>> = run { - var state = emptyMap<Int, NetworkCapabilityInfo>() - callbackFlow { - val networkRequest: NetworkRequest = - NetworkRequest.Builder() - .clearCapabilities() - .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .build() - val callback = - // TODO (b/240569788): log these using [LogBuffer] - object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) { - override fun onCapabilitiesChanged( - network: Network, - networkCapabilities: NetworkCapabilities - ) { - logger.logOnCapabilitiesChanged(network, networkCapabilities) - state = - state.toMutableMap().also { - it[network.getNetId()] = - NetworkCapabilityInfo(network, networkCapabilities) - } - trySend(state) - } - - override fun onLost(network: Network) { - logger.logOnLost(network) - state = state.toMutableMap().also { it.remove(network.getNetId()) } - trySend(state) - } - } - connectivityManager.registerNetworkCallback(networkRequest, callback) - - awaitClose { connectivityManager.unregisterNetworkCallback(callback) } - } - .stateIn(scope, started = Lazily, initialValue = state) - } -} - -/** contains info about network capabilities. */ -data class NetworkCapabilityInfo( - val network: Network, - val capabilities: NetworkCapabilities, -) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt index 012dde5fb8f2..43fbabca823f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt @@ -16,16 +16,26 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository +import android.annotation.SuppressLint +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.net.NetworkCapabilities.TRANSPORT_WIFI +import android.net.NetworkRequest +import android.net.wifi.WifiInfo import android.net.wifi.WifiManager import android.net.wifi.WifiManager.TrafficStateCallback import android.util.Log +import com.android.settingslib.Utils import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import java.util.concurrent.Executor import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -38,9 +48,9 @@ import kotlinx.coroutines.flow.flowOf */ interface WifiRepository { /** - * Observable for the current state of wifi; `null` when there is no active wifi. + * Observable for the current wifi network. */ - val wifiModel: Flow<WifiModel?> + val wifiNetwork: Flow<WifiNetworkModel> /** * Observable for the current wifi network activity. @@ -51,14 +61,57 @@ interface WifiRepository { /** Real implementation of [WifiRepository]. */ @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton +@SuppressLint("MissingPermission") class WifiRepositoryImpl @Inject constructor( + connectivityManager: ConnectivityManager, wifiManager: WifiManager?, @Main mainExecutor: Executor, logger: ConnectivityPipelineLogger, ) : WifiRepository { + override val wifiNetwork: Flow<WifiNetworkModel> = conflatedCallbackFlow { + var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT - // TODO(b/238425913): Actually implement the wifiModel flow. - override val wifiModel: Flow<WifiModel?> = flowOf(WifiModel(ssid = "AB")) + val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) { + override fun onCapabilitiesChanged( + network: Network, + networkCapabilities: NetworkCapabilities + ) { + logger.logOnCapabilitiesChanged(network, networkCapabilities) + + val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities) + if (wifiInfo?.isPrimary == true) { + val wifiNetworkModel = wifiInfoToModel(wifiInfo, network.getNetId()) + logger.logTransformation( + WIFI_NETWORK_CALLBACK_NAME, + oldValue = currentWifi, + newValue = wifiNetworkModel + ) + currentWifi = wifiNetworkModel + trySend(wifiNetworkModel) + } + } + + override fun onLost(network: Network) { + logger.logOnLost(network) + val wifi = currentWifi + if (wifi is WifiNetworkModel.Active && wifi.networkId == network.getNetId()) { + val newNetworkModel = WifiNetworkModel.Inactive + logger.logTransformation( + WIFI_NETWORK_CALLBACK_NAME, + oldValue = wifi, + newValue = newNetworkModel + ) + currentWifi = newNetworkModel + trySend(newNetworkModel) + } + } + } + + trySend(WIFI_NETWORK_DEFAULT) + connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback) + + awaitClose { connectivityManager.unregisterNetworkCallback(callback) } + } override val wifiActivity: Flow<WifiActivityModel> = if (wifiManager == null) { @@ -71,8 +124,8 @@ class WifiRepositoryImpl @Inject constructor( trySend(trafficStateToWifiActivityModel(state)) } - wifiManager.registerTrafficStateCallback(mainExecutor, callback) trySend(ACTIVITY_DEFAULT) + wifiManager.registerTrafficStateCallback(mainExecutor, callback) awaitClose { wifiManager.unregisterTrafficStateCallback(callback) } } @@ -80,6 +133,13 @@ class WifiRepositoryImpl @Inject constructor( companion object { val ACTIVITY_DEFAULT = WifiActivityModel(hasActivityIn = false, hasActivityOut = false) + // Start out with no known wifi network. + // Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an + // initial fetch to get a starting wifi network. But, it uses a deprecated API + // [WifiManager.getConnectionInfo()], and the deprecation doc indicates to just use + // [ConnectivityManager.NetworkCallback] results instead. So, for now we'll just rely on the + // NetworkCallback inside [wifiNetwork] for our wifi network information. + val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive private fun trafficStateToWifiActivityModel(state: Int): WifiActivityModel { return WifiActivityModel( @@ -90,6 +150,30 @@ class WifiRepositoryImpl @Inject constructor( ) } + private fun networkCapabilitiesToWifiInfo( + networkCapabilities: NetworkCapabilities + ): WifiInfo? { + return when { + networkCapabilities.hasTransport(TRANSPORT_WIFI) -> + networkCapabilities.transportInfo as WifiInfo? + 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) + else -> null + } + } + + private fun wifiInfoToModel(wifiInfo: WifiInfo, networkId: Int): WifiNetworkModel { + return WifiNetworkModel.Active( + networkId, + wifiInfo.ssid, + wifiInfo.isPasspointAp, + wifiInfo.isOsuAp, + wifiInfo.passpointProviderFriendlyName + ) + } + private fun prettyPrintActivity(activity: Int): String { return when (activity) { TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE" @@ -99,5 +183,15 @@ class WifiRepositoryImpl @Inject constructor( else -> "INVALID" } } + + private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest = + NetworkRequest.Builder() + .clearCapabilities() + .addCapability(NET_CAPABILITY_NOT_VPN) + .addTransportType(TRANSPORT_WIFI) + .addTransportType(TRANSPORT_CELLULAR) + .build() + + private const val WIFI_NETWORK_CALLBACK_NAME = "wifiNetworkModel" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt index f705399af85d..a9da63b43aa4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.wifi.domain.interactor import android.net.wifi.WifiManager import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -34,13 +35,15 @@ import kotlinx.coroutines.flow.map class WifiInteractor @Inject constructor( repository: WifiRepository, ) { - private val ssid: Flow<String?> = repository.wifiModel.map { info -> - when { - info == null -> null - info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint -> - info.passpointProviderFriendlyName - info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid - else -> null + private val ssid: Flow<String?> = repository.wifiNetwork.map { info -> + when (info) { + is WifiNetworkModel.Inactive -> null + is WifiNetworkModel.Active -> when { + info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint -> + info.passpointProviderFriendlyName + info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid + else -> null + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt deleted file mode 100644 index 7b492cb7ddd1..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt +++ /dev/null @@ -1,88 +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 - -import android.net.NetworkCapabilities -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo -import com.android.systemui.util.mockito.mock -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.InternalCoroutinesApi -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.yield -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.`when` as whenever - -@OptIn(InternalCoroutinesApi::class) -@SmallTest -@RunWith(AndroidTestingRunner::class) -class ConnectivityInfoProcessorTest : SysuiTestCase() { - - private val statusBarPipelineFlags = mock<StatusBarPipelineFlags>() - - @Before - fun setUp() { - whenever(statusBarPipelineFlags.isNewPipelineEnabled()).thenReturn(true) - } - - @Test - fun collectorInfoUpdated_processedInfoAlsoUpdated() = runBlocking { - // GIVEN a processor hooked up to a collector - val scope = CoroutineScope(Dispatchers.Unconfined) - val collector = FakeConnectivityInfoCollector() - val processor = ConnectivityInfoProcessor( - collector, - context, - scope, - statusBarPipelineFlags, - mock(), - ) - - var mostRecentValue: ProcessedConnectivityInfo? = null - val job = launch(start = CoroutineStart.UNDISPATCHED) { - processor.processedInfoFlow.collect { - mostRecentValue = it - } - } - - // WHEN the collector emits a value - val networkCapabilityInfo = mapOf( - 10 to NetworkCapabilityInfo(mock(), NetworkCapabilities.Builder().build()) - ) - collector.emitValue(RawConnectivityInfo(networkCapabilityInfo)) - // Because our job uses [CoroutineStart.UNDISPATCHED], it executes in the same thread as - // this test. So, our test needs to yield to let the job run. - // Note: Once we upgrade our Kotlin coroutines testing library, we won't need this. - yield() - - // THEN the processor receives it - assertThat(mostRecentValue?.networkCapabilityInfo).isEqualTo(networkCapabilityInfo) - - job.cancel() - scope.cancel() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt deleted file mode 100644 index 710e5f6eacd3..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt +++ /dev/null @@ -1,33 +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 - -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow - -/** - * A test-friendly implementation of [ConnectivityInfoCollector] that just emits whatever value it - * receives in [emitValue]. - */ -class FakeConnectivityInfoCollector : ConnectivityInfoCollector { - private val _rawConnectivityInfoFlow = MutableStateFlow(RawConnectivityInfo()) - override val rawConnectivityInfoFlow = _rawConnectivityInfoFlow.asStateFlow() - - suspend fun emitValue(value: RawConnectivityInfo) { - _rawConnectivityInfoFlow.emit(value) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt index df389bc52664..6b8d4aa7c51f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt @@ -17,21 +17,22 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow /** Fake implementation of [WifiRepository] exposing set methods for all the flows. */ class FakeWifiRepository : WifiRepository { - private val _wifiModel: MutableStateFlow<WifiModel?> = MutableStateFlow(null) - override val wifiModel: Flow<WifiModel?> = _wifiModel + private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> = + MutableStateFlow(WifiNetworkModel.Inactive) + override val wifiNetwork: Flow<WifiNetworkModel> = _wifiNetwork private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT) override val wifiActivity: Flow<WifiActivityModel> = _wifiActivity - fun setWifiModel(wifiModel: WifiModel?) { - _wifiModel.value = wifiModel + fun setWifiNetwork(wifiNetworkModel: WifiNetworkModel) { + _wifiNetwork.value = wifiNetworkModel } fun setWifiActivity(activity: WifiActivityModel) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt deleted file mode 100644 index 6edf76ce77c9..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt +++ /dev/null @@ -1,252 +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.wifi.data.repository - -import android.net.ConnectivityManager -import android.net.ConnectivityManager.NetworkCallback -import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED -import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED -import android.net.NetworkCapabilities.TRANSPORT_CELLULAR -import android.net.NetworkCapabilities.TRANSPORT_WIFI -import android.net.NetworkRequest -import android.test.suitebuilder.annotation.SmallTest -import android.testing.AndroidTestingRunner -import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.withArgCaptor -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.any -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever -import org.mockito.MockitoAnnotations - -// TODO(b/240619365): Update this test to use `runTest` when we update the testing library -@SmallTest -@RunWith(AndroidTestingRunner::class) -class NetworkCapabilitiesRepoTest : SysuiTestCase() { - @Mock private lateinit var connectivityManager: ConnectivityManager - @Mock private lateinit var logger: ConnectivityPipelineLogger - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - } - - @Test - fun testOnCapabilitiesChanged_oneNewNetwork_networkStored() = runBlocking { - // GIVEN a repo hooked up to [ConnectivityManager] - val scope = CoroutineScope(Dispatchers.Unconfined) - val repo = NetworkCapabilitiesRepo( - connectivityManager = connectivityManager, - scope = scope, - logger = logger, - ) - - val job = launch(start = CoroutineStart.UNDISPATCHED) { - repo.dataStream.collect { - } - } - - val callback: NetworkCallback = withArgCaptor { - verify(connectivityManager) - .registerNetworkCallback(any(NetworkRequest::class.java), capture()) - } - - // WHEN a new network is added - callback.onCapabilitiesChanged(NET_1, NET_1_CAPS) - - val currentMap = repo.dataStream.value - - // THEN it is emitted from the flow - assertThat(currentMap[NET_1_ID]?.network).isEqualTo(NET_1) - assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_1_CAPS) - - job.cancel() - scope.cancel() - } - - @Test - fun testOnCapabilitiesChanged_twoNewNetworks_bothStored() = runBlocking { - // GIVEN a repo hooked up to [ConnectivityManager] - val scope = CoroutineScope(Dispatchers.Unconfined) - val repo = NetworkCapabilitiesRepo( - connectivityManager = connectivityManager, - scope = scope, - logger = logger, - ) - - val job = launch(start = CoroutineStart.UNDISPATCHED) { - repo.dataStream.collect { - } - } - - val callback: NetworkCallback = withArgCaptor { - verify(connectivityManager) - .registerNetworkCallback(any(NetworkRequest::class.java), capture()) - } - - // WHEN two new networks are added - callback.onCapabilitiesChanged(NET_1, NET_1_CAPS) - callback.onCapabilitiesChanged(NET_2, NET_2_CAPS) - - val currentMap = repo.dataStream.value - - // THEN the current state of the flow reflects 2 networks - assertThat(currentMap[NET_1_ID]?.network).isEqualTo(NET_1) - assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_1_CAPS) - assertThat(currentMap[NET_2_ID]?.network).isEqualTo(NET_2) - assertThat(currentMap[NET_2_ID]?.capabilities).isEqualTo(NET_2_CAPS) - - job.cancel() - scope.cancel() - } - - @Test - fun testOnCapabilitesChanged_newCapabilitiesForExistingNetwork_areCaptured() = runBlocking { - // GIVEN a repo hooked up to [ConnectivityManager] - val scope = CoroutineScope(Dispatchers.Unconfined) - val repo = NetworkCapabilitiesRepo( - connectivityManager = connectivityManager, - scope = scope, - logger = logger, - ) - - val job = launch(start = CoroutineStart.UNDISPATCHED) { - repo.dataStream.collect { - } - } - - val callback: NetworkCallback = withArgCaptor { - verify(connectivityManager) - .registerNetworkCallback(any(NetworkRequest::class.java), capture()) - } - - // WHEN a network is added, and then its capabilities are changed - callback.onCapabilitiesChanged(NET_1, NET_1_CAPS) - callback.onCapabilitiesChanged(NET_1, NET_2_CAPS) - - val currentMap = repo.dataStream.value - - // THEN the current state of the flow reflects the new capabilities - assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_2_CAPS) - - job.cancel() - scope.cancel() - } - - @Test - fun testOnLost_networkIsRemoved() = runBlocking { - // GIVEN a repo hooked up to [ConnectivityManager] - val scope = CoroutineScope(Dispatchers.Unconfined) - val repo = NetworkCapabilitiesRepo( - connectivityManager = connectivityManager, - scope = scope, - logger = logger, - ) - - val job = launch(start = CoroutineStart.UNDISPATCHED) { - repo.dataStream.collect { - } - } - - val callback: NetworkCallback = withArgCaptor { - verify(connectivityManager) - .registerNetworkCallback(any(NetworkRequest::class.java), capture()) - } - - // WHEN two new networks are added, and one is removed - callback.onCapabilitiesChanged(NET_1, NET_1_CAPS) - callback.onCapabilitiesChanged(NET_2, NET_2_CAPS) - callback.onLost(NET_1) - - val currentMap = repo.dataStream.value - - // THEN the current state of the flow reflects only the remaining network - assertThat(currentMap[NET_1_ID]).isNull() - assertThat(currentMap[NET_2_ID]?.network).isEqualTo(NET_2) - assertThat(currentMap[NET_2_ID]?.capabilities).isEqualTo(NET_2_CAPS) - - job.cancel() - scope.cancel() - } - - @Test - fun testOnLost_noNetworks_doesNotCrash() = runBlocking { - // GIVEN a repo hooked up to [ConnectivityManager] - val scope = CoroutineScope(Dispatchers.Unconfined) - val repo = NetworkCapabilitiesRepo( - connectivityManager = connectivityManager, - scope = scope, - logger = logger, - ) - - val job = launch(start = CoroutineStart.UNDISPATCHED) { - repo.dataStream.collect { - } - } - - val callback: NetworkCallback = withArgCaptor { - verify(connectivityManager) - .registerNetworkCallback(any(NetworkRequest::class.java), capture()) - } - - // WHEN no networks are added, and one is removed - callback.onLost(NET_1) - - val currentMap = repo.dataStream.value - - // THEN the current state of the flow shows no networks - assertThat(currentMap).isEmpty() - - job.cancel() - scope.cancel() - } - - private val NET_1_ID = 100 - private val NET_1 = mock<Network>().also { - whenever(it.getNetId()).thenReturn(NET_1_ID) - } - private val NET_2_ID = 200 - private val NET_2 = mock<Network>().also { - whenever(it.getNetId()).thenReturn(NET_2_ID) - } - - private val NET_1_CAPS = NetworkCapabilities.Builder() - .addTransportType(TRANSPORT_CELLULAR) - .addCapability(NET_CAPABILITY_VALIDATED) - .build() - - private val NET_2_CAPS = NetworkCapabilities.Builder() - .addTransportType(TRANSPORT_WIFI) - .addCapability(NET_CAPABILITY_NOT_METERED) - .addCapability(NET_CAPABILITY_VALIDATED) - .build() -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt index 8b61364a2ac9..d0a38084af76 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt @@ -16,16 +16,26 @@ package com.android.systemui.statusbar.pipeline.wifi.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_WIFI +import android.net.vcn.VcnTransportInfo +import android.net.wifi.WifiInfo import android.net.wifi.WifiManager import android.net.wifi.WifiManager.TrafficStateCallback import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.mock import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.util.concurrent.Executor @@ -38,6 +48,7 @@ import org.junit.Before import org.junit.Test import org.mockito.Mock import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @@ -47,6 +58,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { private lateinit var underTest: WifiRepositoryImpl @Mock private lateinit var logger: ConnectivityPipelineLogger + @Mock private lateinit var connectivityManager: ConnectivityManager @Mock private lateinit var wifiManager: WifiManager private lateinit var executor: Executor @@ -54,11 +66,347 @@ class WifiRepositoryImplTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) executor = FakeExecutor(FakeSystemClock()) + + underTest = WifiRepositoryImpl( + connectivityManager, + wifiManager, + executor, + logger, + ) + } + + @Test + fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT) + + job.cancel() + } + + @Test + fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val wifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(true) + } + val network = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(NETWORK_ID) + } + + getNetworkCallback().onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(SSID) + + job.cancel() + } + + @Test + fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val wifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(false) + } + + getNetworkCallback().onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val capabilities = mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO)) + } + + getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(SSID) + + job.cancel() + } + + @Test + fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val wifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(false) + } + val capabilities = mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(VcnTransportInfo(wifiInfo)) + } + + getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val capabilities = mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(mock()) + } + + getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + // Start with the original network + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + + // WHEN we update to a new primary network + val newNetworkId = 456 + val newNetwork = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(newNetworkId) + } + val newSsid = "CD" + val newWifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(newSsid) + whenever(this.isPrimary).thenReturn(true) + } + + getNetworkCallback().onCapabilitiesChanged( + newNetwork, createWifiNetworkCapabilities(newWifiInfo) + ) + + // THEN we use the new network + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(newNetworkId) + assertThat(latestActive.ssid).isEqualTo(newSsid) + + job.cancel() + } + + @Test + fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + // Start with the original network + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + + // WHEN we notify of a new but non-primary network + val newNetworkId = 456 + val newNetwork = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(newNetworkId) + } + val newSsid = "EF" + val newWifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(newSsid) + whenever(this.isPrimary).thenReturn(false) + } + + getNetworkCallback().onCapabilitiesChanged( + newNetwork, createWifiNetworkCapabilities(newWifiInfo) + ) + + // THEN we still use the original network + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(SSID) + + job.cancel() + } + + @Test + fun wifiNetwork_newNetworkCapabilities_flowHasNewData() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val wifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(true) + } + + // Start with the original network + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + + // WHEN we keep the same network ID but change the SSID + val newSsid = "CD" + val newWifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(newSsid) + whenever(this.isPrimary).thenReturn(true) + } + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(newWifiInfo)) + + // THEN we've updated to the new SSID + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(newSsid) + + job.cancel() + } + + @Test + fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + // WHEN we receive #onLost without any #onCapabilitiesChanged beforehand + getNetworkCallback().onLost(NETWORK) + + // THEN there's no crash and we still have no network + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_currentNetworkLost_flowHasNoNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID) + + // WHEN we lose our current network + getNetworkCallback().onLost(NETWORK) + + // THEN we update to no network + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID) + + // WHEN we lose an unknown network + val unknownNetwork = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(543) + } + getNetworkCallback().onLost(unknownNetwork) + + // THEN we still have our previous network + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(SSID) + + job.cancel() + } + + @Test + fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID) + + // WHEN we update to a new network... + val newNetworkId = 89 + val newNetwork = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(newNetworkId) + } + getNetworkCallback().onCapabilitiesChanged( + newNetwork, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO) + ) + // ...and lose the old network + getNetworkCallback().onLost(NETWORK) + + // THEN we still have the new network + assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(newNetworkId) + + job.cancel() } @Test fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) { underTest = WifiRepositoryImpl( + connectivityManager, wifiManager = null, executor, logger, @@ -77,12 +425,6 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiActivity_callbackGivesNone_activityFlowHasNone() = runBlocking(IMMEDIATE) { - underTest = WifiRepositoryImpl( - wifiManager, - executor, - logger, - ) - var latest: WifiActivityModel? = null val job = underTest .wifiActivity @@ -100,12 +442,6 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiActivity_callbackGivesIn_activityFlowHasIn() = runBlocking(IMMEDIATE) { - underTest = WifiRepositoryImpl( - wifiManager, - executor, - logger, - ) - var latest: WifiActivityModel? = null val job = underTest .wifiActivity @@ -123,12 +459,6 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiActivity_callbackGivesOut_activityFlowHasOut() = runBlocking(IMMEDIATE) { - underTest = WifiRepositoryImpl( - wifiManager, - executor, - logger, - ) - var latest: WifiActivityModel? = null val job = underTest .wifiActivity @@ -146,12 +476,6 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = runBlocking(IMMEDIATE) { - underTest = WifiRepositoryImpl( - wifiManager, - executor, - logger, - ) - var latest: WifiActivityModel? = null val job = underTest .wifiActivity @@ -170,6 +494,30 @@ class WifiRepositoryImplTest : SysuiTestCase() { verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture()) return callbackCaptor.value!! } + + private fun getNetworkCallback(): ConnectivityManager.NetworkCallback { + val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>() + verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture()) + return callbackCaptor.value!! + } + + private fun createWifiNetworkCapabilities(wifiInfo: WifiInfo) = + mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(this.transportInfo).thenReturn(wifiInfo) + } + + private companion object { + const val NETWORK_ID = 45 + val NETWORK = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(NETWORK_ID) + } + const val SSID = "AB" + val PRIMARY_WIFI_INFO: WifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(true) + } + } } private val IMMEDIATE = Dispatchers.Main.immediate diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt index c52f347a605a..5f1b1dbb19dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.pipeline.wifi.domain.interactor import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers @@ -47,7 +47,7 @@ class WifiInteractorTest : SysuiTestCase() { @Test fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) { - repository.setWifiModel(WifiModel(ssid = "AB")) + repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false)) var latest: Boolean? = null @@ -63,7 +63,7 @@ class WifiInteractorTest : SysuiTestCase() { @Test fun hasActivityIn_onlyOut_outputsFalse() = runBlocking(IMMEDIATE) { - repository.setWifiModel(WifiModel(ssid = "AB")) + repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true)) var latest: Boolean? = null @@ -79,7 +79,7 @@ class WifiInteractorTest : SysuiTestCase() { @Test fun hasActivityIn_onlyIn_outputsTrue() = runBlocking(IMMEDIATE) { - repository.setWifiModel(WifiModel(ssid = "AB")) + repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false)) var latest: Boolean? = null @@ -95,7 +95,7 @@ class WifiInteractorTest : SysuiTestCase() { @Test fun hasActivityIn_inAndOut_outputsTrue() = runBlocking(IMMEDIATE) { - repository.setWifiModel(WifiModel(ssid = "AB")) + repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true)) var latest: Boolean? = null @@ -111,7 +111,7 @@ class WifiInteractorTest : SysuiTestCase() { @Test fun hasActivityIn_ssidNull_outputsFalse() = runBlocking(IMMEDIATE) { - repository.setWifiModel(WifiModel(ssid = null)) + repository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = null)) repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true)) var latest: Boolean? = null @@ -127,7 +127,7 @@ class WifiInteractorTest : SysuiTestCase() { @Test fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) { - repository.setWifiModel(WifiModel(ssid = "AB")) + repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) var latest: Boolean? = null val job = underTest @@ -158,6 +158,10 @@ class WifiInteractorTest : SysuiTestCase() { job.cancel() } + + companion object { + val VALID_WIFI_NETWORK_MODEL = WifiNetworkModel.Active(networkId = 1, ssid = "AB") + } } private val IMMEDIATE = Dispatchers.Main.immediate diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt index e9259b071e0b..fae4b1055968 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt @@ -20,7 +20,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants @@ -61,7 +61,7 @@ class WifiViewModelTest : SysuiTestCase() { ) // Set up with a valid SSID - repository.setWifiModel(WifiModel(ssid = "AB")) + repository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = "AB")) } @Test |