Merge "Flow-based connectivity listener" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
4ca3922aec
@@ -255,6 +255,16 @@ public class LogModule {
|
|||||||
return factory.create("MediaCarouselCtlrLog", 20);
|
return factory.create("MediaCarouselCtlrLog", 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a {@link LogBuffer} for use in the status bar connectivity pipeline
|
||||||
|
*/
|
||||||
|
@Provides
|
||||||
|
@SysUISingleton
|
||||||
|
@StatusBarConnectivityLog
|
||||||
|
public static LogBuffer provideStatusBarConnectivityBuffer(LogBufferFactory factory) {
|
||||||
|
return factory.create("StatusBarConnectivityLog", 64);
|
||||||
|
}
|
||||||
|
|
||||||
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
|
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
|
||||||
@Provides
|
@Provides
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* 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.log.dagger;
|
||||||
|
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
import com.android.systemui.log.LogBuffer;
|
||||||
|
import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
|
||||||
|
import javax.inject.Qualifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link LogBuffer} for events processed by {@link ConnectivityInfoProcessor}
|
||||||
|
*/
|
||||||
|
@Qualifier
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
public @interface StatusBarConnectivityLog {
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* 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.Network
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
|
import com.android.systemui.log.LogBuffer
|
||||||
|
import com.android.systemui.log.LogLevel
|
||||||
|
import com.android.systemui.log.dagger.StatusBarConnectivityLog
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@SysUISingleton
|
||||||
|
class ConnectivityPipelineLogger @Inject constructor(
|
||||||
|
@StatusBarConnectivityLog private val buffer: LogBuffer,
|
||||||
|
) {
|
||||||
|
fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||||
|
buffer.log(
|
||||||
|
TAG,
|
||||||
|
LogLevel.INFO,
|
||||||
|
{
|
||||||
|
int1 = network.getNetId()
|
||||||
|
str1 = networkCapabilities.toString()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"onCapabilitiesChanged: net=$int1 capabilities=$str1"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logOnLost(network: Network) {
|
||||||
|
buffer.log(
|
||||||
|
TAG,
|
||||||
|
LogLevel.INFO,
|
||||||
|
{
|
||||||
|
int1 = network.getNetId()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"onLost: net=$int1"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val TAG = "SbConnectivityPipeline"
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.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 [NetworkCapabilites] for the current networks */
|
||||||
|
@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,
|
||||||
|
)
|
||||||
|
|
||||||
|
private const val TAG = "ConnectivityRepository"
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* 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.Network
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.dump.DumpManager
|
||||||
|
import com.android.systemui.log.LogBufferFactory
|
||||||
|
import com.android.systemui.log.LogcatEchoTracker
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import java.io.PrintWriter
|
||||||
|
import java.io.StringWriter
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.Mockito.mock
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
class ConnectivityPipelineLoggerTest : SysuiTestCase() {
|
||||||
|
private val buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
|
||||||
|
.create("buffer", 10)
|
||||||
|
private val logger = ConnectivityPipelineLogger(buffer)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLogNetworkCapsChange_bufferHasInfo() {
|
||||||
|
logger.logOnCapabilitiesChanged(NET_1, NET_1_CAPS)
|
||||||
|
|
||||||
|
val stringWriter = StringWriter()
|
||||||
|
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
|
||||||
|
val actualString = stringWriter.toString()
|
||||||
|
|
||||||
|
val expectedNetId = NET_1_ID.toString()
|
||||||
|
val expectedCaps = NET_1_CAPS.toString()
|
||||||
|
|
||||||
|
assertThat(actualString).contains(expectedNetId)
|
||||||
|
assertThat(actualString).contains(expectedCaps)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLogOnLost_bufferHasNetIdOfLostNetwork() {
|
||||||
|
logger.logOnLost(NET_1)
|
||||||
|
|
||||||
|
val stringWriter = StringWriter()
|
||||||
|
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
|
||||||
|
val actualString = stringWriter.toString()
|
||||||
|
|
||||||
|
val expectedNetId = NET_1_ID.toString()
|
||||||
|
|
||||||
|
assertThat(actualString).contains(expectedNetId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val NET_1_ID = 100
|
||||||
|
private val NET_1 = com.android.systemui.util.mockito.mock<Network>().also {
|
||||||
|
Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
|
||||||
|
}
|
||||||
|
private val NET_1_CAPS = NetworkCapabilities.Builder()
|
||||||
|
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||||
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.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()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user