| /* |
| * Copyright (C) 2020 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 android.net.cts |
| |
| import android.app.Instrumentation |
| import android.content.Context |
| import android.net.ConnectivityManager |
| import android.net.InetAddresses |
| import android.net.IpPrefix |
| import android.net.KeepalivePacketData |
| import android.net.LinkAddress |
| import android.net.LinkProperties |
| import android.net.NattKeepalivePacketData |
| import android.net.Network |
| import android.net.NetworkAgent |
| import android.net.NetworkAgent.INVALID_NETWORK |
| import android.net.NetworkAgent.VALID_NETWORK |
| import android.net.NetworkAgentConfig |
| import android.net.NetworkCapabilities |
| import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET |
| import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED |
| import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED |
| import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING |
| import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED |
| import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED |
| import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN |
| import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED |
| import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED |
| import android.net.NetworkCapabilities.TRANSPORT_TEST |
| import android.net.NetworkCapabilities.TRANSPORT_VPN |
| import android.net.NetworkInfo |
| import android.net.NetworkProvider |
| import android.net.NetworkRequest |
| import android.net.RouteInfo |
| import android.net.SocketKeepalive |
| import android.net.Uri |
| import android.net.VpnManager |
| import android.net.VpnTransportInfo |
| import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnAddKeepalivePacketFilter |
| import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnAutomaticReconnectDisabled |
| import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnBandwidthUpdateRequested |
| import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkUnwanted |
| import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnRemoveKeepalivePacketFilter |
| import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnSaveAcceptUnvalidated |
| import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnSignalStrengthThresholdsUpdated |
| import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnStartSocketKeepalive |
| import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnStopSocketKeepalive |
| import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnValidationStatus |
| import android.os.Build |
| import android.os.HandlerThread |
| import android.os.Looper |
| import android.os.Message |
| import android.util.DebugUtils.valueToString |
| import androidx.test.InstrumentationRegistry |
| import com.android.connectivity.aidl.INetworkAgent |
| import com.android.connectivity.aidl.INetworkAgentRegistry |
| import com.android.modules.utils.build.SdkLevel |
| import com.android.net.module.util.ArrayTrackRecord |
| import com.android.testutils.CompatUtil |
| import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo |
| import com.android.testutils.DevSdkIgnoreRunner |
| import com.android.testutils.RecorderCallback.CallbackEntry.Available |
| import com.android.testutils.RecorderCallback.CallbackEntry.Lost |
| import com.android.testutils.TestableNetworkCallback |
| import org.junit.After |
| import org.junit.Assert.assertArrayEquals |
| import org.junit.Before |
| import org.junit.Test |
| import org.junit.runner.RunWith |
| import org.mockito.ArgumentMatchers.any |
| import org.mockito.ArgumentMatchers.anyInt |
| import org.mockito.ArgumentMatchers.argThat |
| import org.mockito.ArgumentMatchers.eq |
| import org.mockito.Mockito.doReturn |
| import org.mockito.Mockito.mock |
| import org.mockito.Mockito.timeout |
| import org.mockito.Mockito.verify |
| import java.time.Duration |
| import java.util.Arrays |
| import java.util.UUID |
| import kotlin.test.assertEquals |
| import kotlin.test.assertFailsWith |
| import kotlin.test.assertFalse |
| import kotlin.test.assertNotNull |
| import kotlin.test.assertNull |
| import kotlin.test.assertTrue |
| import kotlin.test.fail |
| |
| // This test doesn't really have a constraint on how fast the methods should return. If it's |
| // going to fail, it will simply wait forever, so setting a high timeout lowers the flake ratio |
| // without affecting the run time of successful runs. Thus, set a very high timeout. |
| private const val DEFAULT_TIMEOUT_MS = 5000L |
| // When waiting for a NetworkCallback to determine there was no timeout, waiting is the |
| // only possible thing (the relevant handler is the one in the real ConnectivityService, |
| // and then there is the Binder call), so have a short timeout for this as it will be |
| // exhausted every time. |
| private const val NO_CALLBACK_TIMEOUT = 200L |
| // Any legal score (0~99) for the test network would do, as it is going to be kept up by the |
| // requests filed by the test and should never match normal internet requests. 70 is the default |
| // score of Ethernet networks, it's as good a value as any other. |
| private const val TEST_NETWORK_SCORE = 70 |
| private const val BETTER_NETWORK_SCORE = 75 |
| private const val FAKE_NET_ID = 1098 |
| private val instrumentation: Instrumentation |
| get() = InstrumentationRegistry.getInstrumentation() |
| private val realContext: Context |
| get() = InstrumentationRegistry.getContext() |
| private fun Message(what: Int, arg1: Int, arg2: Int, obj: Any?) = Message.obtain().also { |
| it.what = what |
| it.arg1 = arg1 |
| it.arg2 = arg2 |
| it.obj = obj |
| } |
| |
| @RunWith(DevSdkIgnoreRunner::class) |
| // NetworkAgent is not updatable in R-, so this test does not need to be compatible with older |
| // versions. NetworkAgent was also based on AsyncChannel before S so cannot be tested the same way. |
| @IgnoreUpTo(Build.VERSION_CODES.R) |
| class NetworkAgentTest { |
| private val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1") |
| private val REMOTE_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.2") |
| |
| private val mCM = realContext.getSystemService(ConnectivityManager::class.java) |
| private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread") |
| private val mFakeConnectivityService = FakeConnectivityService() |
| |
| private class Provider(context: Context, looper: Looper) : |
| NetworkProvider(context, looper, "NetworkAgentTest NetworkProvider") |
| |
| private val agentsToCleanUp = mutableListOf<NetworkAgent>() |
| private val callbacksToCleanUp = mutableListOf<TestableNetworkCallback>() |
| |
| @Before |
| fun setUp() { |
| instrumentation.getUiAutomation().adoptShellPermissionIdentity() |
| mHandlerThread.start() |
| } |
| |
| @After |
| fun tearDown() { |
| agentsToCleanUp.forEach { it.unregister() } |
| callbacksToCleanUp.forEach { mCM.unregisterNetworkCallback(it) } |
| mHandlerThread.quitSafely() |
| instrumentation.getUiAutomation().dropShellPermissionIdentity() |
| } |
| |
| /** |
| * A fake that helps simulating ConnectivityService talking to a harnessed agent. |
| * This fake only supports speaking to one harnessed agent at a time because it |
| * only keeps track of one async channel. |
| */ |
| private class FakeConnectivityService { |
| val mockRegistry = mock(INetworkAgentRegistry::class.java) |
| private var agentField: INetworkAgent? = null |
| private val registry = object : INetworkAgentRegistry.Stub(), |
| INetworkAgentRegistry by mockRegistry { |
| // asBinder has implementations in both INetworkAgentRegistry.Stub and mockRegistry, so |
| // it needs to be disambiguated. Just fail the test as it should be unused here. |
| // asBinder is used when sending the registry in binder transactions, so not in this |
| // test (the test just uses in-process direct calls). If it were used across processes, |
| // using the Stub super.asBinder() implementation would allow sending the registry in |
| // binder transactions, while recording incoming calls on the other mockito-generated |
| // methods. |
| override fun asBinder() = fail("asBinder should be unused in this test") |
| } |
| |
| val agent: INetworkAgent |
| get() = agentField ?: fail("No INetworkAgent") |
| |
| fun connect(agent: INetworkAgent) { |
| this.agentField = agent |
| agent.onRegistered(registry) |
| } |
| |
| fun disconnect() = agent.onDisconnected() |
| } |
| |
| private open class TestableNetworkAgent( |
| context: Context, |
| looper: Looper, |
| val nc: NetworkCapabilities, |
| val lp: LinkProperties, |
| conf: NetworkAgentConfig |
| ) : NetworkAgent(context, looper, TestableNetworkAgent::class.java.simpleName /* tag */, |
| nc, lp, TEST_NETWORK_SCORE, conf, Provider(context, looper)) { |
| private val history = ArrayTrackRecord<CallbackEntry>().newReadHead() |
| |
| sealed class CallbackEntry { |
| object OnBandwidthUpdateRequested : CallbackEntry() |
| object OnNetworkUnwanted : CallbackEntry() |
| data class OnAddKeepalivePacketFilter( |
| val slot: Int, |
| val packet: KeepalivePacketData |
| ) : CallbackEntry() |
| data class OnRemoveKeepalivePacketFilter(val slot: Int) : CallbackEntry() |
| data class OnStartSocketKeepalive( |
| val slot: Int, |
| val interval: Int, |
| val packet: KeepalivePacketData |
| ) : CallbackEntry() |
| data class OnStopSocketKeepalive(val slot: Int) : CallbackEntry() |
| data class OnSaveAcceptUnvalidated(val accept: Boolean) : CallbackEntry() |
| object OnAutomaticReconnectDisabled : CallbackEntry() |
| data class OnValidationStatus(val status: Int, val uri: Uri?) : CallbackEntry() |
| data class OnSignalStrengthThresholdsUpdated(val thresholds: IntArray) : CallbackEntry() |
| } |
| |
| override fun onBandwidthUpdateRequested() { |
| history.add(OnBandwidthUpdateRequested) |
| } |
| |
| override fun onNetworkUnwanted() { |
| history.add(OnNetworkUnwanted) |
| } |
| |
| override fun onAddKeepalivePacketFilter(slot: Int, packet: KeepalivePacketData) { |
| history.add(OnAddKeepalivePacketFilter(slot, packet)) |
| } |
| |
| override fun onRemoveKeepalivePacketFilter(slot: Int) { |
| history.add(OnRemoveKeepalivePacketFilter(slot)) |
| } |
| |
| override fun onStartSocketKeepalive( |
| slot: Int, |
| interval: Duration, |
| packet: KeepalivePacketData |
| ) { |
| history.add(OnStartSocketKeepalive(slot, interval.seconds.toInt(), packet)) |
| } |
| |
| override fun onStopSocketKeepalive(slot: Int) { |
| history.add(OnStopSocketKeepalive(slot)) |
| } |
| |
| override fun onSaveAcceptUnvalidated(accept: Boolean) { |
| history.add(OnSaveAcceptUnvalidated(accept)) |
| } |
| |
| override fun onAutomaticReconnectDisabled() { |
| history.add(OnAutomaticReconnectDisabled) |
| } |
| |
| override fun onSignalStrengthThresholdsUpdated(thresholds: IntArray) { |
| history.add(OnSignalStrengthThresholdsUpdated(thresholds)) |
| } |
| |
| fun expectEmptySignalStrengths() { |
| expectCallback<OnSignalStrengthThresholdsUpdated>().let { |
| // intArrayOf() without arguments makes an empty array |
| assertArrayEquals(intArrayOf(), it.thresholds) |
| } |
| } |
| |
| override fun onValidationStatus(status: Int, uri: Uri?) { |
| history.add(OnValidationStatus(status, uri)) |
| } |
| |
| // Expects the initial validation event that always occurs immediately after registering |
| // a NetworkAgent whose network does not require validation (which test networks do |
| // not, since they lack the INTERNET capability). It always contains the default argument |
| // for the URI. |
| fun expectNoInternetValidationStatus() = expectCallback<OnValidationStatus>().let { |
| assertEquals(it.status, VALID_NETWORK) |
| // The returned Uri is parsed from the empty string, which means it's an |
| // instance of the (private) Uri.StringUri. There are no real good ways |
| // to check this, the least bad is to just convert it to a string and |
| // make sure it's empty. |
| assertEquals("", it.uri.toString()) |
| } |
| |
| inline fun <reified T : CallbackEntry> expectCallback(): T { |
| val foundCallback = history.poll(DEFAULT_TIMEOUT_MS) |
| assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback") |
| return foundCallback |
| } |
| |
| fun assertNoCallback() { |
| assertTrue(waitForIdle(DEFAULT_TIMEOUT_MS), |
| "Handler didn't became idle after ${DEFAULT_TIMEOUT_MS}ms") |
| assertNull(history.peek()) |
| } |
| } |
| |
| private fun requestNetwork(request: NetworkRequest, callback: TestableNetworkCallback) { |
| mCM.requestNetwork(request, callback) |
| callbacksToCleanUp.add(callback) |
| } |
| |
| private fun registerNetworkCallback( |
| request: NetworkRequest, |
| callback: TestableNetworkCallback |
| ) { |
| mCM.registerNetworkCallback(request, callback) |
| callbacksToCleanUp.add(callback) |
| } |
| |
| private fun createNetworkAgent( |
| context: Context = realContext, |
| name: String? = null, |
| initialNc: NetworkCapabilities? = null, |
| initialLp: LinkProperties? = null |
| ): TestableNetworkAgent { |
| val nc = initialNc ?: NetworkCapabilities().apply { |
| addTransportType(TRANSPORT_TEST) |
| removeCapability(NET_CAPABILITY_TRUSTED) |
| removeCapability(NET_CAPABILITY_INTERNET) |
| addCapability(NET_CAPABILITY_NOT_SUSPENDED) |
| addCapability(NET_CAPABILITY_NOT_ROAMING) |
| addCapability(NET_CAPABILITY_NOT_VPN) |
| if (SdkLevel.isAtLeastS()) { |
| addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) |
| } |
| if (null != name) { |
| setNetworkSpecifier(CompatUtil.makeEthernetNetworkSpecifier(name)) |
| } |
| } |
| val lp = initialLp ?: LinkProperties().apply { |
| addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32)) |
| addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null)) |
| } |
| val config = NetworkAgentConfig.Builder().build() |
| return TestableNetworkAgent(context, mHandlerThread.looper, nc, lp, config).also { |
| agentsToCleanUp.add(it) |
| } |
| } |
| |
| private fun createConnectedNetworkAgent(context: Context = realContext, name: String? = null): |
| Pair<TestableNetworkAgent, TestableNetworkCallback> { |
| val request: NetworkRequest = NetworkRequest.Builder() |
| .clearCapabilities() |
| .addTransportType(TRANSPORT_TEST) |
| .build() |
| val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS) |
| requestNetwork(request, callback) |
| val agent = createNetworkAgent(context, name) |
| agent.register() |
| agent.markConnected() |
| return agent to callback |
| } |
| |
| private fun createNetworkAgentWithFakeCS() = createNetworkAgent().also { |
| mFakeConnectivityService.connect(it.registerForTest(Network(FAKE_NET_ID))) |
| } |
| |
| @Test |
| fun testConnectAndUnregister() { |
| val (agent, callback) = createConnectedNetworkAgent() |
| callback.expectAvailableThenValidatedCallbacks(agent.network) |
| agent.expectEmptySignalStrengths() |
| agent.expectNoInternetValidationStatus() |
| agent.unregister() |
| callback.expectCallback<Lost>(agent.network) |
| agent.expectCallback<OnNetworkUnwanted>() |
| assertFailsWith<IllegalStateException>("Must not be able to register an agent twice") { |
| agent.register() |
| } |
| } |
| |
| @Test |
| fun testOnBandwidthUpdateRequested() { |
| val (agent, callback) = createConnectedNetworkAgent() |
| callback.expectAvailableThenValidatedCallbacks(agent.network) |
| agent.expectEmptySignalStrengths() |
| agent.expectNoInternetValidationStatus() |
| mCM.requestBandwidthUpdate(agent.network) |
| agent.expectCallback<OnBandwidthUpdateRequested>() |
| agent.unregister() |
| } |
| |
| @Test |
| fun testSignalStrengthThresholds() { |
| val thresholds = intArrayOf(30, 50, 65) |
| val callbacks = thresholds.map { strength -> |
| val request = NetworkRequest.Builder() |
| .clearCapabilities() |
| .addTransportType(TRANSPORT_TEST) |
| .setSignalStrength(strength) |
| .build() |
| TestableNetworkCallback(DEFAULT_TIMEOUT_MS).also { |
| registerNetworkCallback(request, it) |
| } |
| } |
| createConnectedNetworkAgent().let { (agent, callback) -> |
| callback.expectAvailableThenValidatedCallbacks(agent.network) |
| agent.expectCallback<OnSignalStrengthThresholdsUpdated>().let { |
| assertArrayEquals(it.thresholds, thresholds) |
| } |
| agent.expectNoInternetValidationStatus() |
| |
| // Send signal strength and check that the callbacks are called appropriately. |
| val nc = NetworkCapabilities(agent.nc) |
| nc.setSignalStrength(20) |
| agent.sendNetworkCapabilities(nc) |
| callbacks.forEach { it.assertNoCallback(NO_CALLBACK_TIMEOUT) } |
| |
| nc.setSignalStrength(40) |
| agent.sendNetworkCapabilities(nc) |
| callbacks[0].expectAvailableCallbacks(agent.network) |
| callbacks[1].assertNoCallback(NO_CALLBACK_TIMEOUT) |
| callbacks[2].assertNoCallback(NO_CALLBACK_TIMEOUT) |
| |
| nc.setSignalStrength(80) |
| agent.sendNetworkCapabilities(nc) |
| callbacks[0].expectCapabilitiesThat(agent.network) { it.signalStrength == 80 } |
| callbacks[1].expectAvailableCallbacks(agent.network) |
| callbacks[2].expectAvailableCallbacks(agent.network) |
| |
| nc.setSignalStrength(55) |
| agent.sendNetworkCapabilities(nc) |
| callbacks[0].expectCapabilitiesThat(agent.network) { it.signalStrength == 55 } |
| callbacks[1].expectCapabilitiesThat(agent.network) { it.signalStrength == 55 } |
| callbacks[2].expectCallback<Lost>(agent.network) |
| } |
| callbacks.forEach { |
| mCM.unregisterNetworkCallback(it) |
| } |
| } |
| |
| @Test |
| fun testSocketKeepalive(): Unit = createNetworkAgentWithFakeCS().let { agent -> |
| val packet = NattKeepalivePacketData( |
| LOCAL_IPV4_ADDRESS /* srcAddress */, 1234 /* srcPort */, |
| REMOTE_IPV4_ADDRESS /* dstAddress */, 4567 /* dstPort */, |
| ByteArray(100 /* size */)) |
| val slot = 4 |
| val interval = 37 |
| |
| mFakeConnectivityService.agent.onAddNattKeepalivePacketFilter(slot, packet) |
| mFakeConnectivityService.agent.onStartNattSocketKeepalive(slot, interval, packet) |
| |
| agent.expectCallback<OnAddKeepalivePacketFilter>().let { |
| assertEquals(it.slot, slot) |
| assertEquals(it.packet, packet) |
| } |
| agent.expectCallback<OnStartSocketKeepalive>().let { |
| assertEquals(it.slot, slot) |
| assertEquals(it.interval, interval) |
| assertEquals(it.packet, packet) |
| } |
| |
| agent.assertNoCallback() |
| |
| // Check that when the agent sends a keepalive event, ConnectivityService receives the |
| // expected message. |
| agent.sendSocketKeepaliveEvent(slot, SocketKeepalive.ERROR_UNSUPPORTED) |
| verify(mFakeConnectivityService.mockRegistry, timeout(DEFAULT_TIMEOUT_MS)) |
| .sendSocketKeepaliveEvent(slot, SocketKeepalive.ERROR_UNSUPPORTED) |
| |
| mFakeConnectivityService.agent.onStopSocketKeepalive(slot) |
| mFakeConnectivityService.agent.onRemoveKeepalivePacketFilter(slot) |
| agent.expectCallback<OnStopSocketKeepalive>().let { |
| assertEquals(it.slot, slot) |
| } |
| agent.expectCallback<OnRemoveKeepalivePacketFilter>().let { |
| assertEquals(it.slot, slot) |
| } |
| } |
| |
| @Test |
| fun testSendUpdates(): Unit = createConnectedNetworkAgent().let { (agent, callback) -> |
| callback.expectAvailableThenValidatedCallbacks(agent.network) |
| agent.expectEmptySignalStrengths() |
| agent.expectNoInternetValidationStatus() |
| val ifaceName = "adhocIface" |
| val lp = LinkProperties(agent.lp) |
| lp.setInterfaceName(ifaceName) |
| agent.sendLinkProperties(lp) |
| callback.expectLinkPropertiesThat(agent.network) { |
| it.getInterfaceName() == ifaceName |
| } |
| val nc = NetworkCapabilities(agent.nc) |
| nc.addCapability(NET_CAPABILITY_NOT_METERED) |
| agent.sendNetworkCapabilities(nc) |
| callback.expectCapabilitiesThat(agent.network) { |
| it.hasCapability(NET_CAPABILITY_NOT_METERED) |
| } |
| } |
| |
| @Test |
| fun testSendScore() { |
| // This test will create two networks and check that the one with the stronger |
| // score wins out for a request that matches them both. |
| // First create requests to make sure both networks are kept up, using the |
| // specifier so they are specific to each network |
| val name1 = UUID.randomUUID().toString() |
| val name2 = UUID.randomUUID().toString() |
| val request1 = NetworkRequest.Builder() |
| .clearCapabilities() |
| .addTransportType(TRANSPORT_TEST) |
| .setNetworkSpecifier(CompatUtil.makeEthernetNetworkSpecifier(name1)) |
| .build() |
| val request2 = NetworkRequest.Builder() |
| .clearCapabilities() |
| .addTransportType(TRANSPORT_TEST) |
| .setNetworkSpecifier(CompatUtil.makeEthernetNetworkSpecifier(name2)) |
| .build() |
| val callback1 = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS) |
| val callback2 = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS) |
| requestNetwork(request1, callback1) |
| requestNetwork(request2, callback2) |
| |
| // Then file the interesting request |
| val request = NetworkRequest.Builder() |
| .clearCapabilities() |
| .addTransportType(TRANSPORT_TEST) |
| .build() |
| val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS) |
| requestNetwork(request, callback) |
| |
| // Connect the first Network |
| createConnectedNetworkAgent(name = name1).let { (agent1, _) -> |
| callback.expectAvailableThenValidatedCallbacks(agent1.network) |
| // Upgrade agent1 to a better score so that there is no ambiguity when |
| // agent2 connects that agent1 is still better |
| agent1.sendNetworkScore(BETTER_NETWORK_SCORE - 1) |
| // Connect the second agent |
| createConnectedNetworkAgent(name = name2).let { (agent2, _) -> |
| agent2.markConnected() |
| // The callback should not see anything yet |
| callback.assertNoCallback(NO_CALLBACK_TIMEOUT) |
| // Now update the score and expect the callback now prefers agent2 |
| agent2.sendNetworkScore(BETTER_NETWORK_SCORE) |
| callback.expectCallback<Available>(agent2.network) |
| } |
| } |
| |
| // tearDown() will unregister the requests and agents |
| } |
| |
| private fun hasAllTransports(nc: NetworkCapabilities?, transports: IntArray) = |
| nc != null && transports.all { nc.hasTransport(it) } |
| |
| @Test |
| @IgnoreUpTo(Build.VERSION_CODES.R) |
| fun testSetUnderlyingNetworksAndVpnSpecifier() { |
| val request = NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_TEST) |
| .addTransportType(TRANSPORT_VPN) |
| .removeCapability(NET_CAPABILITY_NOT_VPN) |
| .removeCapability(NET_CAPABILITY_TRUSTED) // TODO: add to VPN! |
| .build() |
| val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS) |
| registerNetworkCallback(request, callback) |
| |
| val nc = NetworkCapabilities().apply { |
| addTransportType(TRANSPORT_TEST) |
| addTransportType(TRANSPORT_VPN) |
| removeCapability(NET_CAPABILITY_NOT_VPN) |
| setTransportInfo(VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE)) |
| if (SdkLevel.isAtLeastS()) { |
| addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) |
| } |
| } |
| val defaultNetwork = mCM.activeNetwork |
| assertNotNull(defaultNetwork) |
| val defaultNetworkCapabilities = mCM.getNetworkCapabilities(defaultNetwork) |
| val defaultNetworkTransports = defaultNetworkCapabilities.transportTypes |
| |
| val agent = createNetworkAgent(initialNc = nc) |
| agent.register() |
| agent.markConnected() |
| callback.expectAvailableThenValidatedCallbacks(agent.network!!) |
| |
| // Check that the default network's transport is propagated to the VPN. |
| var vpnNc = mCM.getNetworkCapabilities(agent.network) |
| assertNotNull(vpnNc) |
| assertEquals(VpnManager.TYPE_VPN_SERVICE, |
| (vpnNc.transportInfo as VpnTransportInfo).type) |
| |
| val testAndVpn = intArrayOf(TRANSPORT_TEST, TRANSPORT_VPN) |
| assertTrue(hasAllTransports(vpnNc, testAndVpn)) |
| assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_VPN)) |
| assertTrue(hasAllTransports(vpnNc, defaultNetworkTransports), |
| "VPN transports ${Arrays.toString(vpnNc.transportTypes)}" + |
| " lacking transports from ${Arrays.toString(defaultNetworkTransports)}") |
| |
| // Check that when no underlying networks are announced the underlying transport disappears. |
| agent.setUnderlyingNetworks(listOf<Network>()) |
| callback.expectCapabilitiesThat(agent.network!!) { |
| it.transportTypes.size == 2 && hasAllTransports(it, testAndVpn) |
| } |
| |
| // Put the underlying network back and check that the underlying transport reappears. |
| val expectedTransports = (defaultNetworkTransports.toSet() + TRANSPORT_TEST + TRANSPORT_VPN) |
| .toIntArray() |
| agent.setUnderlyingNetworks(null) |
| callback.expectCapabilitiesThat(agent.network!!) { |
| it.transportTypes.size == expectedTransports.size && |
| hasAllTransports(it, expectedTransports) |
| } |
| |
| // Check that some underlying capabilities are propagated. |
| // This is not very accurate because the test does not control the capabilities of the |
| // underlying networks, and because not congested, not roaming, and not suspended are the |
| // default anyway. It's still useful as an extra check though. |
| vpnNc = mCM.getNetworkCapabilities(agent.network) |
| for (cap in listOf(NET_CAPABILITY_NOT_CONGESTED, |
| NET_CAPABILITY_NOT_ROAMING, |
| NET_CAPABILITY_NOT_SUSPENDED)) { |
| val capStr = valueToString(NetworkCapabilities::class.java, "NET_CAPABILITY_", cap) |
| if (defaultNetworkCapabilities.hasCapability(cap) && !vpnNc.hasCapability(cap)) { |
| fail("$capStr not propagated from underlying: $defaultNetworkCapabilities") |
| } |
| } |
| |
| agent.unregister() |
| callback.expectCallback<Lost>(agent.network) |
| } |
| |
| @Test |
| @IgnoreUpTo(Build.VERSION_CODES.R) |
| fun testAgentStartsInConnecting() { |
| val mockContext = mock(Context::class.java) |
| val mockCm = mock(ConnectivityManager::class.java) |
| doReturn(mockCm).`when`(mockContext).getSystemService(Context.CONNECTIVITY_SERVICE) |
| createConnectedNetworkAgent(mockContext) |
| verify(mockCm).registerNetworkAgent(any(), |
| argThat<NetworkInfo> { it.detailedState == NetworkInfo.DetailedState.CONNECTING }, |
| any(LinkProperties::class.java), |
| any(NetworkCapabilities::class.java), |
| anyInt() /* score */, |
| any(NetworkAgentConfig::class.java), |
| eq(NetworkProvider.ID_NONE)) |
| } |
| |
| @Test |
| fun testSetAcceptUnvalidated() { |
| createNetworkAgentWithFakeCS().let { agent -> |
| mFakeConnectivityService.agent.onSaveAcceptUnvalidated(true) |
| agent.expectCallback<OnSaveAcceptUnvalidated>().let { |
| assertTrue(it.accept) |
| } |
| agent.assertNoCallback() |
| } |
| } |
| |
| @Test |
| fun testSetAcceptUnvalidatedPreventAutomaticReconnect() { |
| createNetworkAgentWithFakeCS().let { agent -> |
| mFakeConnectivityService.agent.onSaveAcceptUnvalidated(false) |
| mFakeConnectivityService.agent.onPreventAutomaticReconnect() |
| agent.expectCallback<OnSaveAcceptUnvalidated>().let { |
| assertFalse(it.accept) |
| } |
| agent.expectCallback<OnAutomaticReconnectDisabled>() |
| agent.assertNoCallback() |
| // When automatic reconnect is turned off, the network is torn down and |
| // ConnectivityService disconnects. As part of the disconnect, ConnectivityService will |
| // also send itself a message to unregister the NetworkAgent from its internal |
| // structure. |
| mFakeConnectivityService.disconnect() |
| agent.expectCallback<OnNetworkUnwanted>() |
| } |
| } |
| |
| @Test |
| fun testPreventAutomaticReconnect() { |
| createNetworkAgentWithFakeCS().let { agent -> |
| mFakeConnectivityService.agent.onPreventAutomaticReconnect() |
| agent.expectCallback<OnAutomaticReconnectDisabled>() |
| agent.assertNoCallback() |
| mFakeConnectivityService.disconnect() |
| agent.expectCallback<OnNetworkUnwanted>() |
| } |
| } |
| |
| @Test |
| fun testValidationStatus() = createNetworkAgentWithFakeCS().let { agent -> |
| val uri = Uri.parse("http://www.google.com") |
| mFakeConnectivityService.agent.onValidationStatusChanged(VALID_NETWORK, |
| uri.toString()) |
| agent.expectCallback<OnValidationStatus>().let { |
| assertEquals(it.status, VALID_NETWORK) |
| assertEquals(it.uri, uri) |
| } |
| |
| mFakeConnectivityService.agent.onValidationStatusChanged(INVALID_NETWORK, null) |
| agent.expectCallback<OnValidationStatus>().let { |
| assertEquals(it.status, INVALID_NETWORK) |
| assertNull(it.uri) |
| } |
| } |
| |
| @Test |
| fun testTemporarilyUnmeteredCapability() { |
| // This test will create a networks with/without NET_CAPABILITY_TEMPORARILY_NOT_METERED |
| // and check that the callback reflects the capability changes. |
| // First create a request to make sure the network is kept up |
| val request1 = NetworkRequest.Builder() |
| .clearCapabilities() |
| .addTransportType(TRANSPORT_TEST) |
| .build() |
| val callback1 = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS).also { |
| registerNetworkCallback(request1, it) |
| } |
| requestNetwork(request1, callback1) |
| |
| // Then file the interesting request |
| val request = NetworkRequest.Builder() |
| .clearCapabilities() |
| .addTransportType(TRANSPORT_TEST) |
| .build() |
| val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS) |
| requestNetwork(request, callback) |
| |
| // Connect the network |
| createConnectedNetworkAgent().let { (agent, _) -> |
| callback.expectAvailableThenValidatedCallbacks(agent.network) |
| |
| // Send TEMP_NOT_METERED and check that the callback is called appropriately. |
| val nc1 = NetworkCapabilities(agent.nc) |
| .addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED) |
| agent.sendNetworkCapabilities(nc1) |
| callback.expectCapabilitiesThat(agent.network) { |
| it.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED) |
| } |
| |
| // Remove TEMP_NOT_METERED and check that the callback is called appropriately. |
| val nc2 = NetworkCapabilities(agent.nc) |
| .removeCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED) |
| agent.sendNetworkCapabilities(nc2) |
| callback.expectCapabilitiesThat(agent.network) { |
| !it.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED) |
| } |
| } |
| |
| // tearDown() will unregister the requests and agents |
| } |
| } |