Merge "[SB Refactor] Implement the wifi network flow using the NetworkCallback." into tm-qpr-dev

This commit is contained in:
Caitlin Shkuratov
2022-08-23 15:43:53 +00:00
committed by Android (Google) Code Review
18 changed files with 591 additions and 690 deletions

View File

@@ -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

View File

@@ -1,49 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.pipeline
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(),
)

View File

@@ -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())
}

View File

@@ -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(),
)

View File

@@ -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
@@ -35,11 +33,6 @@ abstract class StatusBarPipelineModule {
@ClassKey(ConnectivityInfoProcessor::class)
abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable
@Binds
abstract fun provideConnectivityInfoCollector(
impl: ConnectivityInfoCollectorImpl
): ConnectivityInfoCollector
@Binds
abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
}

View File

@@ -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,

View File

@@ -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,
)

View File

@@ -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()
}

View File

@@ -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,
)

View File

@@ -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"
}
}

View File

@@ -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
}
}
}

View File

@@ -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()
}
}

View File

@@ -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)
}
}

View File

@@ -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) {

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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

View File

@@ -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