Add a NetworkAgent API to indicate that a network will be replaced.
This is useful for link layers that disconnect but know they will reconnect to a similar network soon, and do not want the device to switch to another network until the reconnect happens. An example is wifi switching to another network that is on a different subnet without the device switching to cellular data. This works by immediately destroying the network, so the link layer can reuse the same interface name for the new network. It would be possible to delay destroying the network until the new network connects, but in practice this does not seem useful, because the if the link layer reuses the interface, then the interface will be undergoing reconfiguration, and will likely not be usable for app traffic. This CL also moves the call to onNetworkDestroyed into destroyNativeNetwork. This is needed to ensure that the new API calls onNetworkDestroyed even though most teardown operations have not happened. This causes onNetworkDestroyed to happen before the netId is marked free, but that shouldn't cause any behavioural changes because netId allocation is an implementation detail of ConnectivityService and is not observable by apps or system components. Bug: 216567577 Test: builds, boots Test: atest FrameworksNetTests FrameworksNetIntegrationTests Test: atest CtsNetTestCases:android.net.cts.ConnectivityManagerTest Test: atest CtsNetTestCases:android.net.cts.NetworkAgentTest#testDestroyAndAwaitReplacement Change-Id: I9f9e022fef66b31a29cce560413321075e992756
This commit is contained in:
@@ -236,6 +236,7 @@ package android.net {
|
||||
public abstract class NetworkAgent {
|
||||
ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
|
||||
ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkScore, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
|
||||
method public void destroyAndAwaitReplacement(@IntRange(from=0, to=0x1388) int);
|
||||
method @Nullable public android.net.Network getNetwork();
|
||||
method public void markConnected();
|
||||
method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
|
||||
|
||||
@@ -47,4 +47,5 @@ oneway interface INetworkAgentRegistry {
|
||||
void sendAddDscpPolicy(in DscpPolicy policy);
|
||||
void sendRemoveDscpPolicy(int policyId);
|
||||
void sendRemoveAllDscpPolicies();
|
||||
void sendDestroyAndAwaitReplacement(int timeoutMillis);
|
||||
}
|
||||
|
||||
@@ -434,6 +434,14 @@ public abstract class NetworkAgent {
|
||||
*/
|
||||
public static final int CMD_DSCP_POLICY_STATUS = BASE + 28;
|
||||
|
||||
/**
|
||||
* Sent by the NetworkAgent to ConnectivityService to notify that this network is expected to be
|
||||
* replaced within the specified time by a similar network.
|
||||
* arg1 = timeout in milliseconds
|
||||
* @hide
|
||||
*/
|
||||
public static final int EVENT_DESTROY_AND_AWAIT_REPLACEMENT = BASE + 29;
|
||||
|
||||
private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
|
||||
final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
|
||||
config.legacyTypeName, config.legacySubTypeName);
|
||||
@@ -942,6 +950,45 @@ public abstract class NetworkAgent {
|
||||
queueOrSendMessage(reg -> reg.sendTeardownDelayMs(teardownDelayMillis));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that this agent will likely soon be replaced by another agent for a very similar
|
||||
* network (e.g., same Wi-Fi SSID).
|
||||
*
|
||||
* If the network is not currently satisfying any {@link NetworkRequest}s, it will be torn down.
|
||||
* If it is satisfying requests, then the native network corresponding to the agent will be
|
||||
* destroyed immediately, but the agent will remain registered and will continue to satisfy
|
||||
* requests until {@link #unregister} is called, the network is replaced by an equivalent or
|
||||
* better network, or the specified timeout expires. During this time:
|
||||
*
|
||||
* <ul>
|
||||
* <li>The agent may not send any further updates, for example by calling methods
|
||||
* such as {@link #sendNetworkCapabilities}, {@link #sendLinkProperties},
|
||||
* {@link #sendNetworkScore(NetworkScore)} and so on. Any such updates will be ignored.
|
||||
* <li>The network will remain connected and continue to satisfy any requests that it would
|
||||
* otherwise satisfy (including, possibly, the default request).
|
||||
* <li>The validation state of the network will not change, and calls to
|
||||
* {@link ConnectivityManager#reportNetworkConnectivity(Network, boolean)} will be ignored.
|
||||
* </ul>
|
||||
*
|
||||
* Once this method is called, it is not possible to restore the agent to a functioning state.
|
||||
* If a replacement network becomes available, then a new agent must be registered. When that
|
||||
* replacement network is fully capable of replacing this network (including, possibly, being
|
||||
* validated), this agent will no longer be needed and will be torn down. Otherwise, this agent
|
||||
* can be disconnected by calling {@link #unregister}. If {@link #unregister} is not called,
|
||||
* this agent will automatically be unregistered when the specified timeout expires. Any
|
||||
* teardown delay previously set using{@link #setTeardownDelayMillis} is ignored.
|
||||
*
|
||||
* <p>This method has no effect if {@link #markConnected} has not yet been called.
|
||||
* <p>This method may only be called once.
|
||||
*
|
||||
* @param timeoutMillis the timeout after which this network will be unregistered even if
|
||||
* {@link #unregister} was not called.
|
||||
*/
|
||||
public void destroyAndAwaitReplacement(
|
||||
@IntRange(from = 0, to = MAX_TEARDOWN_DELAY_MS) int timeoutMillis) {
|
||||
queueOrSendMessage(reg -> reg.sendDestroyAndAwaitReplacement(timeoutMillis));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the legacy subtype of this network agent.
|
||||
*
|
||||
|
||||
@@ -3502,6 +3502,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isDisconnectRequest(Message msg) {
|
||||
if (msg.what != NetworkAgent.EVENT_NETWORK_INFO_CHANGED) return false;
|
||||
final NetworkInfo info = (NetworkInfo) ((Pair) msg.obj).second;
|
||||
return info.getState() == NetworkInfo.State.DISCONNECTED;
|
||||
}
|
||||
|
||||
// must be stateless - things change under us.
|
||||
private class NetworkStateTrackerHandler extends Handler {
|
||||
public NetworkStateTrackerHandler(Looper looper) {
|
||||
@@ -3518,6 +3524,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
return;
|
||||
}
|
||||
|
||||
// If the network has been destroyed, the only thing that it can do is disconnect.
|
||||
if (nai.destroyed && !isDisconnectRequest(msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.what) {
|
||||
case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
|
||||
final NetworkCapabilities networkCapabilities = new NetworkCapabilities(
|
||||
@@ -3619,12 +3630,60 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NetworkAgent.EVENT_DESTROY_AND_AWAIT_REPLACEMENT: {
|
||||
// If nai is not yet created, or is already destroyed, ignore.
|
||||
if (!shouldDestroyNativeNetwork(nai)) break;
|
||||
|
||||
final int timeoutMs = (int) arg.second;
|
||||
if (timeoutMs < 0 || timeoutMs > NetworkAgent.MAX_TEARDOWN_DELAY_MS) {
|
||||
Log.e(TAG, "Invalid network replacement timer " + timeoutMs
|
||||
+ ", must be between 0 and " + NetworkAgent.MAX_TEARDOWN_DELAY_MS);
|
||||
}
|
||||
|
||||
// Marking a network awaiting replacement is used to ensure that any requests
|
||||
// satisfied by the network do not switch to another network until a
|
||||
// replacement is available or the wait for a replacement times out.
|
||||
// If the network is inactive (i.e., nascent or lingering), then there are no
|
||||
// such requests, and there is no point keeping it. Just tear it down.
|
||||
// Note that setLingerDuration(0) cannot be used to do this because the network
|
||||
// could be nascent.
|
||||
nai.clearInactivityState();
|
||||
if (unneeded(nai, UnneededFor.TEARDOWN)) {
|
||||
Log.d(TAG, nai.toShortString()
|
||||
+ " marked awaiting replacement is unneeded, tearing down instead");
|
||||
teardownUnneededNetwork(nai);
|
||||
break;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Marking " + nai.toShortString()
|
||||
+ " destroyed, awaiting replacement within " + timeoutMs + "ms");
|
||||
destroyNativeNetwork(nai);
|
||||
|
||||
// TODO: deduplicate this call with the one in disconnectAndDestroyNetwork.
|
||||
// This is not trivial because KeepaliveTracker#handleStartKeepalive does not
|
||||
// consider the fact that the network could already have disconnected or been
|
||||
// destroyed. Fix the code to send ERROR_INVALID_NETWORK when this happens
|
||||
// (taking care to ensure no dup'd FD leaks), then remove the code duplication
|
||||
// and move this code to a sensible location (destroyNativeNetwork perhaps?).
|
||||
mKeepaliveTracker.handleStopAllKeepalives(nai,
|
||||
SocketKeepalive.ERROR_INVALID_NETWORK);
|
||||
|
||||
nai.updateScoreForNetworkAgentUpdate();
|
||||
// This rematch is almost certainly not going to result in any changes, because
|
||||
// the destroyed flag is only just above the "current satisfier wins"
|
||||
// tie-breaker. But technically anything that affects scoring should rematch.
|
||||
rematchAllNetworksAndRequests();
|
||||
mHandler.postDelayed(() -> nai.disconnect(), timeoutMs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean maybeHandleNetworkMonitorMessage(Message msg) {
|
||||
final int netId = msg.arg2;
|
||||
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
|
||||
// If a network has already been destroyed, all NetworkMonitor updates are ignored.
|
||||
if (nai != null && nai.destroyed) return true;
|
||||
switch (msg.what) {
|
||||
default:
|
||||
return false;
|
||||
@@ -4124,6 +4183,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldDestroyNativeNetwork(@NonNull NetworkAgentInfo nai) {
|
||||
return nai.created && !nai.destroyed;
|
||||
}
|
||||
|
||||
private void handleNetworkAgentDisconnected(Message msg) {
|
||||
NetworkAgentInfo nai = (NetworkAgentInfo) msg.obj;
|
||||
disconnectAndDestroyNetwork(nai);
|
||||
@@ -4230,7 +4293,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
}
|
||||
|
||||
private void destroyNetwork(NetworkAgentInfo nai) {
|
||||
if (nai.created) {
|
||||
if (shouldDestroyNativeNetwork(nai)) {
|
||||
// Tell netd to clean up the configuration for this network
|
||||
// (routing rules, DNS, etc).
|
||||
// This may be slow as it requires a lot of netd shelling out to ip and
|
||||
@@ -4239,15 +4302,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
// network or service a new request from an app), so network traffic isn't interrupted
|
||||
// for an unnecessarily long time.
|
||||
destroyNativeNetwork(nai);
|
||||
mDnsManager.removeNetwork(nai.network);
|
||||
|
||||
// clean up tc police filters on interface.
|
||||
if (nai.everConnected && canNetworkBeRateLimited(nai) && mIngressRateLimit >= 0) {
|
||||
mDeps.disableIngressRateLimit(nai.linkProperties.getInterfaceName());
|
||||
}
|
||||
if (!nai.created && !SdkLevel.isAtLeastT()) {
|
||||
// Backwards compatibility: send onNetworkDestroyed even if network was never created.
|
||||
// This can never run if the code above runs because shouldDestroyNativeNetwork is
|
||||
// false if the network was never created.
|
||||
// TODO: delete when S is no longer supported.
|
||||
nai.onNetworkDestroyed();
|
||||
}
|
||||
mNetIdManager.releaseNetId(nai.network.getNetId());
|
||||
nai.onNetworkDestroyed();
|
||||
}
|
||||
|
||||
private boolean createNativeNetwork(@NonNull NetworkAgentInfo nai) {
|
||||
@@ -4290,6 +4353,18 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
} catch (RemoteException | ServiceSpecificException e) {
|
||||
loge("Exception destroying network: " + e);
|
||||
}
|
||||
// TODO: defer calling this until the network is removed from mNetworkAgentInfos.
|
||||
// Otherwise, a private DNS configuration update for a destroyed network, or one that never
|
||||
// gets created, could add data to DnsManager data structures that will never get deleted.
|
||||
mDnsManager.removeNetwork(nai.network);
|
||||
|
||||
// clean up tc police filters on interface.
|
||||
if (nai.everConnected && canNetworkBeRateLimited(nai) && mIngressRateLimit >= 0) {
|
||||
mDeps.disableIngressRateLimit(nai.linkProperties.getInterfaceName());
|
||||
}
|
||||
|
||||
nai.destroyed = true;
|
||||
nai.onNetworkDestroyed();
|
||||
}
|
||||
|
||||
// If this method proves to be too slow then we can maintain a separate
|
||||
@@ -8542,11 +8617,19 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
log(" accepting network in place of " + previousSatisfier.toShortString());
|
||||
}
|
||||
previousSatisfier.removeRequest(previousRequest.requestId);
|
||||
if (canSupportGracefulNetworkSwitch(previousSatisfier, newSatisfier)) {
|
||||
if (canSupportGracefulNetworkSwitch(previousSatisfier, newSatisfier)
|
||||
&& !previousSatisfier.destroyed) {
|
||||
// If this network switch can't be supported gracefully, the request is not
|
||||
// lingered. This allows letting go of the network sooner to reclaim some
|
||||
// performance on the new network, since the radio can't do both at the same
|
||||
// time while preserving good performance.
|
||||
//
|
||||
// Also don't linger the request if the old network has been destroyed.
|
||||
// A destroyed network does not provide actual network connectivity, so
|
||||
// lingering it is not useful. In particular this ensures that a destroyed
|
||||
// network is outscored by its replacement,
|
||||
// then it is torn down immediately instead of being lingered, and any apps that
|
||||
// were using it immediately get onLost and can connect using the new network.
|
||||
previousSatisfier.lingerRequest(previousRequest.requestId, now);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -132,7 +132,7 @@ public class Nat464Xlat {
|
||||
final boolean skip464xlat = (nai.netAgentConfig() != null)
|
||||
&& nai.netAgentConfig().skip464xlat;
|
||||
|
||||
return supported && connected && isIpv6OnlyNetwork && !skip464xlat
|
||||
return supported && connected && isIpv6OnlyNetwork && !skip464xlat && !nai.destroyed
|
||||
&& (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
|
||||
? isCellular464XlatEnabled() : true);
|
||||
}
|
||||
|
||||
@@ -732,6 +732,12 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRa
|
||||
mHandler.obtainMessage(NetworkAgent.EVENT_REMOVE_ALL_DSCP_POLICIES,
|
||||
new Pair<>(NetworkAgentInfo.this, null)).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendDestroyAndAwaitReplacement(final int timeoutMillis) {
|
||||
mHandler.obtainMessage(NetworkAgent.EVENT_DESTROY_AND_AWAIT_REPLACEMENT,
|
||||
new Pair<>(NetworkAgentInfo.this, timeoutMillis)).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -976,7 +982,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRa
|
||||
/**
|
||||
* Update the ConnectivityService-managed bits in the score.
|
||||
*
|
||||
* Call this after updating the network agent config.
|
||||
* Call this after changing any data that might affect the score (e.g., agent config).
|
||||
*/
|
||||
public void updateScoreForNetworkAgentUpdate() {
|
||||
mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig,
|
||||
@@ -1256,6 +1262,8 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRa
|
||||
+ "network{" + network + "} handle{" + network.getNetworkHandle() + "} ni{"
|
||||
+ networkInfo.toShortString() + "} "
|
||||
+ mScore + " "
|
||||
+ (created ? " created" : "")
|
||||
+ (destroyed ? " destroyed" : "")
|
||||
+ (isNascent() ? " nascent" : (isLingering() ? " lingering" : ""))
|
||||
+ (everValidated ? " everValidated" : "")
|
||||
+ (lastValidated ? " lastValidated" : "")
|
||||
|
||||
@@ -19,6 +19,7 @@ import android.Manifest.permission.NETWORK_SETTINGS
|
||||
import android.app.Instrumentation
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.EthernetNetworkSpecifier
|
||||
import android.net.INetworkAgent
|
||||
import android.net.INetworkAgentRegistry
|
||||
import android.net.InetAddresses
|
||||
@@ -35,6 +36,7 @@ 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_RESTRICTED
|
||||
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
|
||||
@@ -42,7 +44,9 @@ 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.NET_CAPABILITY_VALIDATED
|
||||
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
|
||||
import android.net.NetworkCapabilities.TRANSPORT_TEST
|
||||
import android.net.NetworkCapabilities.TRANSPORT_WIFI
|
||||
import android.net.NetworkCapabilities.TRANSPORT_VPN
|
||||
import android.net.NetworkInfo
|
||||
import android.net.NetworkProvider
|
||||
@@ -100,6 +104,7 @@ import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnStopSocketKeep
|
||||
import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnUnregisterQosCallback
|
||||
import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus
|
||||
import com.android.testutils.TestableNetworkCallback
|
||||
import com.android.testutils.assertThrows
|
||||
import org.junit.After
|
||||
import org.junit.Assume.assumeFalse
|
||||
import org.junit.Before
|
||||
@@ -112,6 +117,8 @@ import org.mockito.Mockito.doReturn
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.timeout
|
||||
import org.mockito.Mockito.verify
|
||||
import java.io.IOException
|
||||
import java.net.DatagramSocket
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Socket
|
||||
@@ -249,14 +256,10 @@ class NetworkAgentTest {
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun createNetworkAgent(
|
||||
context: Context = realContext,
|
||||
private fun makeTestNetworkCapabilities(
|
||||
specifier: String? = null,
|
||||
initialNc: NetworkCapabilities? = null,
|
||||
initialLp: LinkProperties? = null,
|
||||
initialConfig: NetworkAgentConfig? = null
|
||||
): TestableNetworkAgent {
|
||||
val nc = initialNc ?: NetworkCapabilities().apply {
|
||||
transports: IntArray = intArrayOf()
|
||||
) = NetworkCapabilities().apply {
|
||||
addTransportType(TRANSPORT_TEST)
|
||||
removeCapability(NET_CAPABILITY_TRUSTED)
|
||||
removeCapability(NET_CAPABILITY_INTERNET)
|
||||
@@ -269,7 +272,20 @@ class NetworkAgentTest {
|
||||
if (null != specifier) {
|
||||
setNetworkSpecifier(CompatUtil.makeEthernetNetworkSpecifier(specifier))
|
||||
}
|
||||
for (t in transports) { addTransportType(t) }
|
||||
// Most transports are not allowed on test networks unless the network is marked restricted.
|
||||
// This test does not need
|
||||
if (transports.size > 0) removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
|
||||
}
|
||||
|
||||
private fun createNetworkAgent(
|
||||
context: Context = realContext,
|
||||
specifier: String? = null,
|
||||
initialNc: NetworkCapabilities? = null,
|
||||
initialLp: LinkProperties? = null,
|
||||
initialConfig: NetworkAgentConfig? = null
|
||||
): TestableNetworkAgent {
|
||||
val nc = initialNc ?: makeTestNetworkCapabilities(specifier)
|
||||
val lp = initialLp ?: LinkProperties().apply {
|
||||
addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
|
||||
addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
|
||||
@@ -284,12 +300,14 @@ class NetworkAgentTest {
|
||||
context: Context = realContext,
|
||||
specifier: String? = UUID.randomUUID().toString(),
|
||||
initialConfig: NetworkAgentConfig? = null,
|
||||
expectedInitSignalStrengthThresholds: IntArray? = intArrayOf()
|
||||
expectedInitSignalStrengthThresholds: IntArray? = intArrayOf(),
|
||||
transports: IntArray = intArrayOf()
|
||||
): Pair<TestableNetworkAgent, TestableNetworkCallback> {
|
||||
val callback = TestableNetworkCallback()
|
||||
// Ensure this NetworkAgent is never unneeded by filing a request with its specifier.
|
||||
requestNetwork(makeTestNetworkRequest(specifier = specifier), callback)
|
||||
val agent = createNetworkAgent(context, specifier, initialConfig = initialConfig)
|
||||
val nc = makeTestNetworkCapabilities(specifier, transports)
|
||||
val agent = createNetworkAgent(context, initialConfig = initialConfig, initialNc = nc)
|
||||
agent.setTeardownDelayMillis(0)
|
||||
// Connect the agent and verify initial status callbacks.
|
||||
agent.register()
|
||||
@@ -301,6 +319,15 @@ class NetworkAgentTest {
|
||||
return agent to callback
|
||||
}
|
||||
|
||||
private fun connectNetwork(vararg transports: Int): Pair<TestableNetworkAgent, Network> {
|
||||
val (agent, callback) = createConnectedNetworkAgent(transports = transports)
|
||||
val network = agent.network!!
|
||||
// createConnectedNetworkAgent internally files a request; release it so that the network
|
||||
// will be torn down if unneeded.
|
||||
mCM.unregisterNetworkCallback(callback)
|
||||
return agent to network
|
||||
}
|
||||
|
||||
private fun createNetworkAgentWithFakeCS() = createNetworkAgent().also {
|
||||
mFakeConnectivityService.connect(it.registerForTest(Network(FAKE_NET_ID)))
|
||||
}
|
||||
@@ -1123,4 +1150,138 @@ class NetworkAgentTest {
|
||||
remoteAddresses
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDestroyAndAwaitReplacement() {
|
||||
// Keeps an eye on all test networks.
|
||||
val matchAllCallback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
|
||||
registerNetworkCallback(makeTestNetworkRequest(), matchAllCallback)
|
||||
|
||||
// File a request that matches and keeps up the best-scoring test network.
|
||||
val testCallback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
|
||||
requestNetwork(makeTestNetworkRequest(), testCallback)
|
||||
|
||||
// Connect the first network. This should satisfy the request.
|
||||
val (agent1, network1) = connectNetwork()
|
||||
matchAllCallback.expectAvailableThenValidatedCallbacks(network1)
|
||||
testCallback.expectAvailableThenValidatedCallbacks(network1)
|
||||
// Check that network1 exists by binding a socket to it and getting no exceptions.
|
||||
network1.bindSocket(DatagramSocket())
|
||||
|
||||
// Connect a second agent. network1 is preferred because it was already registered, so
|
||||
// testCallback will not see any events. agent2 is be torn down because it has no requests.
|
||||
val (agent2, network2) = connectNetwork()
|
||||
matchAllCallback.expectAvailableThenValidatedCallbacks(network2)
|
||||
matchAllCallback.expectCallback<Lost>(network2)
|
||||
agent2.expectCallback<OnNetworkUnwanted>()
|
||||
agent2.expectCallback<OnNetworkDestroyed>()
|
||||
assertNull(mCM.getLinkProperties(network2))
|
||||
|
||||
// Mark the first network as awaiting replacement. This should destroy the underlying
|
||||
// native network and send onNetworkDestroyed, but will not send any NetworkCallbacks,
|
||||
// because for callback and scoring purposes network1 is still connected.
|
||||
agent1.destroyAndAwaitReplacement(5_000 /* timeoutMillis */)
|
||||
agent1.expectCallback<OnNetworkDestroyed>()
|
||||
assertThrows(IOException::class.java) { network1.bindSocket(DatagramSocket()) }
|
||||
assertNotNull(mCM.getLinkProperties(network1))
|
||||
|
||||
// Calling destroyAndAwaitReplacement more than once has no effect.
|
||||
// If it did, this test would fail because the 1ms timeout means that the network would be
|
||||
// torn down before the replacement arrives.
|
||||
agent1.destroyAndAwaitReplacement(1 /* timeoutMillis */)
|
||||
|
||||
// Connect a third network. Because network1 is awaiting replacement, network3 is preferred
|
||||
// as soon as it validates (until then, it is outscored by network1).
|
||||
// The fact that the first events seen by matchAllCallback is the connection of network3
|
||||
// implicitly ensures that no callbacks are sent since network1 was lost.
|
||||
val (agent3, network3) = connectNetwork()
|
||||
matchAllCallback.expectAvailableThenValidatedCallbacks(network3)
|
||||
testCallback.expectAvailableDoubleValidatedCallbacks(network3)
|
||||
|
||||
// As soon as the replacement arrives, network1 is disconnected.
|
||||
// Check that this happens before the replacement timeout (5 seconds) fires.
|
||||
matchAllCallback.expectCallback<Lost>(network1, 2_000 /* timeoutMs */)
|
||||
agent1.expectCallback<OnNetworkUnwanted>()
|
||||
|
||||
// Test lingering:
|
||||
// - Connect a higher-scoring network and check that network3 starts lingering.
|
||||
// - Mark network3 awaiting replacement.
|
||||
// - Check that network3 is torn down immediately without waiting for the linger timer or
|
||||
// the replacement timer to fire. This is a regular teardown, so it results in
|
||||
// onNetworkUnwanted before onNetworkDestroyed.
|
||||
val (agent4, agent4callback) = createConnectedNetworkAgent()
|
||||
val network4 = agent4.network!!
|
||||
matchAllCallback.expectAvailableThenValidatedCallbacks(network4)
|
||||
agent4.sendNetworkScore(NetworkScore.Builder().setTransportPrimary(true).build())
|
||||
matchAllCallback.expectCallback<Losing>(network3)
|
||||
testCallback.expectAvailableCallbacks(network4, validated = true)
|
||||
mCM.unregisterNetworkCallback(agent4callback)
|
||||
agent3.destroyAndAwaitReplacement(5_000)
|
||||
agent3.expectCallback<OnNetworkUnwanted>()
|
||||
matchAllCallback.expectCallback<Lost>(network3, 1000L)
|
||||
agent3.expectCallback<OnNetworkDestroyed>()
|
||||
|
||||
// Now mark network4 awaiting replacement with a low timeout, and check that if no
|
||||
// replacement arrives, it is torn down.
|
||||
agent4.destroyAndAwaitReplacement(100 /* timeoutMillis */)
|
||||
matchAllCallback.expectCallback<Lost>(network4, 1000L /* timeoutMs */)
|
||||
testCallback.expectCallback<Lost>(network4, 1000L /* timeoutMs */)
|
||||
agent4.expectCallback<OnNetworkDestroyed>()
|
||||
agent4.expectCallback<OnNetworkUnwanted>()
|
||||
|
||||
// If a network that is awaiting replacement is unregistered, it disconnects immediately,
|
||||
// before the replacement timeout fires.
|
||||
val (agent5, network5) = connectNetwork()
|
||||
matchAllCallback.expectAvailableThenValidatedCallbacks(network5)
|
||||
testCallback.expectAvailableThenValidatedCallbacks(network5)
|
||||
agent5.destroyAndAwaitReplacement(5_000 /* timeoutMillis */)
|
||||
agent5.unregister()
|
||||
matchAllCallback.expectCallback<Lost>(network5, 1000L /* timeoutMs */)
|
||||
testCallback.expectCallback<Lost>(network5, 1000L /* timeoutMs */)
|
||||
agent5.expectCallback<OnNetworkDestroyed>()
|
||||
agent5.expectCallback<OnNetworkUnwanted>()
|
||||
|
||||
// If wifi is replaced within the timeout, the device does not switch to cellular.
|
||||
val (cellAgent, cellNetwork) = connectNetwork(TRANSPORT_CELLULAR)
|
||||
testCallback.expectAvailableThenValidatedCallbacks(cellNetwork)
|
||||
matchAllCallback.expectAvailableThenValidatedCallbacks(cellNetwork)
|
||||
|
||||
val (wifiAgent, wifiNetwork) = connectNetwork(TRANSPORT_WIFI)
|
||||
testCallback.expectAvailableCallbacks(wifiNetwork, validated = true)
|
||||
testCallback.expectCapabilitiesThat(wifiNetwork) {
|
||||
it.hasCapability(NET_CAPABILITY_VALIDATED)
|
||||
}
|
||||
matchAllCallback.expectAvailableCallbacks(wifiNetwork, validated = false)
|
||||
matchAllCallback.expectCallback<Losing>(cellNetwork)
|
||||
matchAllCallback.expectCapabilitiesThat(wifiNetwork) {
|
||||
it.hasCapability(NET_CAPABILITY_VALIDATED)
|
||||
}
|
||||
|
||||
wifiAgent.destroyAndAwaitReplacement(5_000 /* timeoutMillis */)
|
||||
wifiAgent.expectCallback<OnNetworkDestroyed>()
|
||||
|
||||
// Once the network is awaiting replacement, changing LinkProperties, NetworkCapabilities or
|
||||
// score, or calling reportNetworkConnectivity, have no effect.
|
||||
val wifiSpecifier = mCM.getNetworkCapabilities(wifiNetwork)!!.networkSpecifier
|
||||
assertNotNull(wifiSpecifier)
|
||||
assertTrue(wifiSpecifier is EthernetNetworkSpecifier)
|
||||
|
||||
val wifiNc = makeTestNetworkCapabilities(wifiSpecifier.interfaceName,
|
||||
intArrayOf(TRANSPORT_WIFI))
|
||||
wifiAgent.sendNetworkCapabilities(wifiNc)
|
||||
val wifiLp = mCM.getLinkProperties(wifiNetwork)!!
|
||||
val newRoute = RouteInfo(IpPrefix("192.0.2.42/24"))
|
||||
assertFalse(wifiLp.getRoutes().contains(newRoute))
|
||||
wifiLp.addRoute(newRoute)
|
||||
wifiAgent.sendLinkProperties(wifiLp)
|
||||
mCM.reportNetworkConnectivity(wifiNetwork, false)
|
||||
// The test implicitly checks that no callbacks are sent here, because the next events seen
|
||||
// by the callbacks are for the new network connecting.
|
||||
|
||||
val (newWifiAgent, newWifiNetwork) = connectNetwork(TRANSPORT_WIFI)
|
||||
testCallback.expectAvailableCallbacks(newWifiNetwork, validated = true)
|
||||
matchAllCallback.expectAvailableThenValidatedCallbacks(newWifiNetwork)
|
||||
matchAllCallback.expectCallback<Lost>(wifiNetwork)
|
||||
wifiAgent.expectCallback<OnNetworkUnwanted>()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user