blob: 03b961bc4ba6c916f582388e82edb1a2563c507a [file] [log] [blame]
/*
* 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.KeepalivePacketData
import android.net.LinkAddress
import android.net.LinkProperties
import android.net.Network
import android.net.NetworkAgent
import android.net.NetworkAgent.CMD_ADD_KEEPALIVE_PACKET_FILTER
import android.net.NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT
import android.net.NetworkAgent.CMD_REMOVE_KEEPALIVE_PACKET_FILTER
import android.net.NetworkAgent.CMD_REPORT_NETWORK_STATUS
import android.net.NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED
import android.net.NetworkAgent.CMD_START_SOCKET_KEEPALIVE
import android.net.NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE
import android.net.NetworkAgent.INVALID_NETWORK
import android.net.NetworkAgent.VALID_NETWORK
import android.net.NetworkAgentConfig
import android.net.NetworkCapabilities
import android.net.NetworkProvider
import android.net.NetworkRequest
import android.net.SocketKeepalive
import android.net.StringNetworkSpecifier
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.os.Message
import android.os.Messenger
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 androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import com.android.internal.util.AsyncChannel
import com.android.testutils.ArrayTrackRecord
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.TestableNetworkCallback
import java.util.UUID
import org.junit.After
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.net.InetAddress
import java.time.Duration
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
// 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 context: 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(AndroidJUnit4::class)
class NetworkAgentTest {
@Rule @JvmField
val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q)
private val LOCAL_IPV4_ADDRESS = InetAddress.parseNumericAddress("192.0.2.1")
private val REMOTE_IPV4_ADDRESS = InetAddress.parseNumericAddress("192.0.2.2")
private val mCM = context.getSystemService(ConnectivityManager::class.java)
private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread")
private val mFakeConnectivityService by lazy { FakeConnectivityService(mHandlerThread.looper) }
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(looper: Looper) {
private val CMD_EXPECT_DISCONNECT = 1
private var disconnectExpected = false
private val msgHistory = ArrayTrackRecord<Message>().newReadHead()
private val asyncChannel = AsyncChannel()
private val handler = object : Handler(looper) {
override fun handleMessage(msg: Message) {
msgHistory.add(Message.obtain(msg)) // make a copy as the original will be recycled
when (msg.what) {
CMD_EXPECT_DISCONNECT -> disconnectExpected = true
AsyncChannel.CMD_CHANNEL_HALF_CONNECTED ->
asyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION)
AsyncChannel.CMD_CHANNEL_DISCONNECTED ->
if (!disconnectExpected) {
fail("Agent unexpectedly disconnected")
} else {
disconnectExpected = false
}
}
}
}
fun connect(agentMsngr: Messenger) = asyncChannel.connect(context, handler, agentMsngr)
fun disconnect() = asyncChannel.disconnect()
fun sendMessage(what: Int, arg1: Int = 0, arg2: Int = 0, obj: Any? = null) =
asyncChannel.sendMessage(Message(what, arg1, arg2, obj))
fun expectMessage(what: Int) =
assertNotNull(msgHistory.poll(DEFAULT_TIMEOUT_MS) { it.what == what })
fun willExpectDisconnectOnce() = handler.sendEmptyMessage(CMD_EXPECT_DISCONNECT)
}
private open class TestableNetworkAgent(
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()
}
fun getName(): String? = (nc.getNetworkSpecifier() as? StringNetworkSpecifier)?.specifier
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(name: String? = null): TestableNetworkAgent {
val nc = NetworkCapabilities().apply {
addTransportType(NetworkCapabilities.TRANSPORT_TEST)
removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
if (null != name) {
setNetworkSpecifier(StringNetworkSpecifier(name))
}
}
val lp = LinkProperties().apply {
addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 0))
}
val config = NetworkAgentConfig.Builder().build()
return TestableNetworkAgent(mHandlerThread.looper, nc, lp, config).also {
agentsToCleanUp.add(it)
}
}
private fun createConnectedNetworkAgent(name: String? = null):
Pair<TestableNetworkAgent, TestableNetworkCallback> {
val request: NetworkRequest = NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_TEST)
.build()
val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
requestNetwork(request, callback)
val agent = createNetworkAgent(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(NetworkCapabilities.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 = object : KeepalivePacketData(
LOCAL_IPV4_ADDRESS /* srcAddress */, 1234 /* srcPort */,
REMOTE_IPV4_ADDRESS /* dstAddress */, 4567 /* dstPort */,
ByteArray(100 /* size */) { it.toByte() /* init */ }) {}
val slot = 4
val interval = 37
mFakeConnectivityService.sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER,
arg1 = slot, obj = packet)
mFakeConnectivityService.sendMessage(CMD_START_SOCKET_KEEPALIVE,
arg1 = slot, arg2 = interval, obj = 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)
mFakeConnectivityService.expectMessage(NetworkAgent.EVENT_SOCKET_KEEPALIVE).let() {
assertEquals(slot, it.arg1)
assertEquals(SocketKeepalive.ERROR_UNSUPPORTED, it.arg2)
}
mFakeConnectivityService.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, arg1 = slot)
mFakeConnectivityService.sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, arg1 = 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(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
agent.sendNetworkCapabilities(nc)
callback.expectCapabilitiesThat(agent.network) {
it.hasCapability(NetworkCapabilities.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(NetworkCapabilities.TRANSPORT_TEST)
.setNetworkSpecifier(StringNetworkSpecifier(name1))
.build()
val request2 = NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_TEST)
.setNetworkSpecifier(StringNetworkSpecifier(name2))
.build()
val callback1 = TestableNetworkCallback()
val callback2 = TestableNetworkCallback()
requestNetwork(request1, callback1)
requestNetwork(request2, callback2)
// Then file the interesting request
val request = NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_TEST)
.build()
val callback = TestableNetworkCallback()
requestNetwork(request, callback)
// Connect the first Network
createConnectedNetworkAgent(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(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
}
@Test
fun testSetAcceptUnvalidated() {
createNetworkAgentWithFakeCS().let { agent ->
mFakeConnectivityService.sendMessage(CMD_SAVE_ACCEPT_UNVALIDATED, 1)
agent.expectCallback<OnSaveAcceptUnvalidated>().let {
assertTrue(it.accept)
}
agent.assertNoCallback()
}
}
@Test
fun testSetAcceptUnvalidatedPreventAutomaticReconnect() {
createNetworkAgentWithFakeCS().let { agent ->
mFakeConnectivityService.sendMessage(CMD_SAVE_ACCEPT_UNVALIDATED, 0)
mFakeConnectivityService.sendMessage(CMD_PREVENT_AUTOMATIC_RECONNECT)
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 sends a disconnect. This in turn causes the agent
// to send a DISCONNECTED message to CS.
mFakeConnectivityService.willExpectDisconnectOnce()
mFakeConnectivityService.disconnect()
mFakeConnectivityService.expectMessage(AsyncChannel.CMD_CHANNEL_DISCONNECTED)
agent.expectCallback<OnNetworkUnwanted>()
}
}
@Test
fun testPreventAutomaticReconnect() {
createNetworkAgentWithFakeCS().let { agent ->
mFakeConnectivityService.sendMessage(CMD_PREVENT_AUTOMATIC_RECONNECT)
agent.expectCallback<OnAutomaticReconnectDisabled>()
agent.assertNoCallback()
mFakeConnectivityService.willExpectDisconnectOnce()
mFakeConnectivityService.disconnect()
mFakeConnectivityService.expectMessage(AsyncChannel.CMD_CHANNEL_DISCONNECTED)
agent.expectCallback<OnNetworkUnwanted>()
}
}
@Test
fun testValidationStatus() = createNetworkAgentWithFakeCS().let { agent ->
val uri = Uri.parse("http://www.google.com")
val bundle = Bundle().apply {
putString(NetworkAgent.REDIRECT_URL_KEY, uri.toString())
}
mFakeConnectivityService.sendMessage(CMD_REPORT_NETWORK_STATUS,
arg1 = VALID_NETWORK, obj = bundle)
agent.expectCallback<OnValidationStatus>().let {
assertEquals(it.status, VALID_NETWORK)
assertEquals(it.uri, uri)
}
mFakeConnectivityService.sendMessage(CMD_REPORT_NETWORK_STATUS,
arg1 = INVALID_NETWORK, obj = Bundle())
agent.expectCallback<OnValidationStatus>().let {
assertEquals(it.status, INVALID_NETWORK)
assertNull(it.uri)
}
}
}