blob: d0a38084af76191384f78cb85eeef4686699d050 [file] [log] [blame]
/*
* 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.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
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
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)
@SmallTest
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
@Before
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,
)
var latest: WifiActivityModel? = null
val job = underTest
.wifiActivity
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isEqualTo(ACTIVITY_DEFAULT)
job.cancel()
}
@Test
fun wifiActivity_callbackGivesNone_activityFlowHasNone() = runBlocking(IMMEDIATE) {
var latest: WifiActivityModel? = null
val job = underTest
.wifiActivity
.onEach { latest = it }
.launchIn(this)
getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE)
assertThat(latest).isEqualTo(
WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
)
job.cancel()
}
@Test
fun wifiActivity_callbackGivesIn_activityFlowHasIn() = runBlocking(IMMEDIATE) {
var latest: WifiActivityModel? = null
val job = underTest
.wifiActivity
.onEach { latest = it }
.launchIn(this)
getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN)
assertThat(latest).isEqualTo(
WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
)
job.cancel()
}
@Test
fun wifiActivity_callbackGivesOut_activityFlowHasOut() = runBlocking(IMMEDIATE) {
var latest: WifiActivityModel? = null
val job = underTest
.wifiActivity
.onEach { latest = it }
.launchIn(this)
getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT)
assertThat(latest).isEqualTo(
WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
)
job.cancel()
}
@Test
fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = runBlocking(IMMEDIATE) {
var latest: WifiActivityModel? = null
val job = underTest
.wifiActivity
.onEach { latest = it }
.launchIn(this)
getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT)
assertThat(latest).isEqualTo(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
job.cancel()
}
private fun getTrafficStateCallback(): TrafficStateCallback {
val callbackCaptor = argumentCaptor<TrafficStateCallback>()
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