Merge "Add NetworkStackModuleTest annotation on the specific test case."
diff --git a/netd/Android.bp b/netd/Android.bp
index 473460d..4325d89 100644
--- a/netd/Android.bp
+++ b/netd/Android.bp
@@ -35,6 +35,9 @@
"BpfHandler.cpp",
"NetdUpdatable.cpp",
],
+ static_libs: [
+ "libmodules-utils-build",
+ ],
shared_libs: [
"libbase",
"liblog",
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 8081d12..6409374 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -21,6 +21,7 @@
#include <linux/bpf.h>
#include <android-base/unique_fd.h>
+#include <android-modules-utils/sdk_level.h>
#include <bpf/WaitForProgsLoaded.h>
#include <log/log.h>
#include <netdutils/UidConstants.h>
@@ -74,9 +75,11 @@
}
static Status initPrograms(const char* cg2_path) {
+ if (modules::sdklevel::IsAtLeastU() && !!strcmp(cg2_path, "/sys/fs/cgroup")) abort();
+
unique_fd cg_fd(open(cg2_path, O_DIRECTORY | O_RDONLY | O_CLOEXEC));
if (cg_fd == -1) {
- int ret = errno;
+ const int ret = errno;
ALOGE("Failed to open the cgroup directory: %s", strerror(ret));
return statusFromErrno(ret, "Open the cgroup directory failed");
}
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index c47c572..47a1022 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -619,6 +619,7 @@
final MdnsSearchOptions.Builder optionsBuilder =
MdnsSearchOptions.newBuilder()
.setNetwork(info.getNetwork())
+ .setRemoveExpiredService(true)
.setIsPassiveMode(true);
if (typeAndSubtype.second != null) {
// The parsing ensures subtype starts with an underscore.
@@ -813,6 +814,7 @@
.setNetwork(info.getNetwork())
.setIsPassiveMode(true)
.setResolveInstanceName(info.getServiceName())
+ .setRemoveExpiredService(true)
.build();
mMdnsDiscoveryManager.registerListener(
resolveServiceType, listener, options);
@@ -906,6 +908,7 @@
.setNetwork(info.getNetwork())
.setIsPassiveMode(true)
.setResolveInstanceName(info.getServiceName())
+ .setRemoveExpiredService(true)
.build();
mMdnsDiscoveryManager.registerListener(
resolveServiceType, listener, options);
diff --git a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
index 866ecba..84faf12 100644
--- a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
+++ b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
@@ -24,6 +24,7 @@
import android.util.Pair;
import com.android.server.connectivity.mdns.util.MdnsLogger;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
import java.lang.ref.WeakReference;
@@ -75,6 +76,8 @@
private final boolean sendDiscoveryQueries;
@NonNull
private final List<MdnsResponse> servicesToResolve;
+ @NonNull
+ private final MdnsResponseDecoder.Clock clock;
EnqueueMdnsQueryCallable(
@NonNull MdnsSocketClientBase requestSender,
@@ -85,7 +88,8 @@
int transactionId,
@Nullable Network network,
boolean sendDiscoveryQueries,
- @NonNull Collection<MdnsResponse> servicesToResolve) {
+ @NonNull Collection<MdnsResponse> servicesToResolve,
+ @NonNull MdnsResponseDecoder.Clock clock) {
weakRequestSender = new WeakReference<>(requestSender);
this.packetWriter = packetWriter;
serviceTypeLabels = TextUtils.split(serviceType, "\\.");
@@ -95,6 +99,7 @@
this.network = network;
this.sendDiscoveryQueries = sendDiscoveryQueries;
this.servicesToResolve = new ArrayList<>(servicesToResolve);
+ this.clock = clock;
}
// Incompatible return type for override of Callable#call().
@@ -119,22 +124,24 @@
// List of (name, type) to query
final ArrayList<Pair<String[], Integer>> missingKnownAnswerRecords = new ArrayList<>();
+ final long now = clock.elapsedRealtime();
for (MdnsResponse response : servicesToResolve) {
- // TODO: also send queries to renew record TTL (as per RFC6762 7.1 no need to query
- // if remaining TTL is more than half the original one, so send the queries if half
- // the TTL has passed).
- if (response.isComplete()) continue;
final String[] serviceName = response.getServiceName();
if (serviceName == null) continue;
- if (!response.hasTextRecord()) {
+ if (!response.hasTextRecord() || MdnsUtils.isRecordRenewalNeeded(
+ response.getTextRecord(), now)) {
missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_TXT));
}
- if (!response.hasServiceRecord()) {
+ if (!response.hasServiceRecord() || MdnsUtils.isRecordRenewalNeeded(
+ response.getServiceRecord(), now)) {
missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_SRV));
// The hostname is not yet known, so queries for address records will be sent
// the next time the EnqueueMdnsQueryCallable is enqueued if the reply does not
// contain them. In practice, advertisers should include the address records
// when queried for SRV, although it's not a MUST requirement (RFC6763 12.2).
+ // TODO: Figure out how to renew the A/AAAA record. Usually A/AAAA record will
+ // be included in the response to the SRV record so in high chances there is
+ // no need to renew them individually.
} else if (!response.hasInet4AddressRecord() && !response.hasInet6AddressRecord()) {
final String[] host = response.getServiceRecord().getServiceHost();
missingKnownAnswerRecords.add(new Pair<>(host, MdnsRecord.TYPE_A));
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsConfigs.java b/service-t/src/com/android/server/connectivity/mdns/MdnsConfigs.java
index 75c7e6c..761c477 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsConfigs.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsConfigs.java
@@ -94,10 +94,6 @@
return false;
}
- public static boolean allowSearchOptionsToRemoveExpiredService() {
- return false;
- }
-
public static boolean allowNetworkInterfaceIndexPropagation() {
return true;
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index c2c0db2..809750d 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -66,9 +66,6 @@
private final Map<String, MdnsResponse> instanceNameToResponse = new HashMap<>();
private final boolean removeServiceAfterTtlExpires =
MdnsConfigs.removeServiceAfterTtlExpires();
- private final boolean allowSearchOptionsToRemoveExpiredService =
- MdnsConfigs.allowSearchOptionsToRemoveExpiredService();
-
private final MdnsResponseDecoder.Clock clock;
@Nullable private MdnsSearchOptions searchOptions;
@@ -374,9 +371,7 @@
if (removeServiceAfterTtlExpires) {
return true;
}
- return allowSearchOptionsToRemoveExpiredService
- && searchOptions != null
- && searchOptions.removeExpiredService();
+ return searchOptions != null && searchOptions.removeExpiredService();
}
@VisibleForTesting
@@ -537,7 +532,8 @@
config.transactionId,
config.network,
sendDiscoveryQueries,
- servicesToResolve)
+ servicesToResolve,
+ clock)
.call();
} catch (RuntimeException e) {
sharedLog.e(String.format("Failed to run EnqueueMdnsQueryCallable for subtype: %s",
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
index 2fa1ae4..dc09bef 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
@@ -25,7 +25,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkAddress;
@@ -35,6 +38,9 @@
import android.net.NetworkRequest;
import android.net.TetheringManager;
import android.net.TetheringManager.TetheringEventCallback;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pInfo;
+import android.net.wifi.p2p.WifiP2pManager;
import android.os.Handler;
import android.os.Looper;
import android.util.ArrayMap;
@@ -51,6 +57,7 @@
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* The {@link MdnsSocketProvider} manages the multiple sockets for mDns.
@@ -92,6 +99,60 @@
private final byte[] mPacketReadBuffer = new byte[READ_BUFFER_SIZE];
private boolean mMonitoringSockets = false;
private boolean mRequestStop = false;
+ private String mWifiP2pTetherInterface = null;
+
+ private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String newP2pIface = getWifiP2pInterface(intent);
+
+ if (!mMonitoringSockets || !hasAllNetworksRequest()) {
+ mWifiP2pTetherInterface = newP2pIface;
+ return;
+ }
+
+ // If already serving from the correct interface, nothing to do.
+ if (Objects.equals(mWifiP2pTetherInterface, newP2pIface)) return;
+
+ if (mWifiP2pTetherInterface != null) {
+ if (newP2pIface != null) {
+ Log.wtf(TAG, "Wifi p2p interface is changed from " + mWifiP2pTetherInterface
+ + " to " + newP2pIface + " without null broadcast");
+ }
+ // Remove the socket.
+ removeTetherInterfaceSocket(mWifiP2pTetherInterface);
+ }
+
+ // Update mWifiP2pTetherInterface
+ mWifiP2pTetherInterface = newP2pIface;
+
+ // Check whether the socket for wifi p2p interface is created or not.
+ final boolean socketAlreadyExists = mTetherInterfaceSockets.get(newP2pIface) != null;
+ if (newP2pIface != null && !socketAlreadyExists) {
+ // Create a socket for wifi p2p interface.
+ final int ifaceIndex =
+ mDependencies.getNetworkInterfaceIndexByName(newP2pIface);
+ createSocket(LOCAL_NET, createLPForTetheredInterface(newP2pIface, ifaceIndex));
+ }
+ }
+ };
+
+ @Nullable
+ private static String getWifiP2pInterface(final Intent intent) {
+ final WifiP2pGroup group =
+ intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP);
+ final WifiP2pInfo p2pInfo =
+ intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO);
+ if (group == null || p2pInfo == null) {
+ return null;
+ }
+
+ if (!p2pInfo.groupFormed) {
+ return null;
+ } else {
+ return group.getInterface();
+ }
+ }
public MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper,
@NonNull SharedLog sharedLog) {
@@ -138,6 +199,18 @@
mSocketNetlinkMonitor = mDependencies.createSocketNetlinkMonitor(mHandler,
mSharedLog.forSubComponent("NetlinkMonitor"), new NetLinkMessageProcessor());
+
+ // Register a intent receiver to listen wifi p2p interface changes.
+ // Note: The wifi p2p interface change is only notified via
+ // TetheringEventCallback#onLocalOnlyInterfacesChanged if the device is the wifi p2p group
+ // owner. In this case, MdnsSocketProvider will receive duplicate interface changes and must
+ // ignore the later notification because the socket has already been created. There is only
+ // one notification from the wifi p2p connection change intent if the device is not the wifi
+ // p2p group owner.
+ final IntentFilter intentFilter =
+ new IntentFilter(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+ mContext.registerReceiver(
+ mIntentReceiver, intentFilter, null /* broadcastPermission */, mHandler);
}
/**
@@ -376,17 +449,28 @@
if (!hasAllNetworksRequest()) {
// Currently, the network for tethering can not be requested, so the sockets for
// tethering are only created if there is a request for all networks (interfaces).
- // Therefore, this change can skip if there is no such request.
+ // Therefore, only update the interface list and skip this change if no such request.
if (DBG) {
Log.d(TAG, "Ignore tether interfaces change. There is no request for all"
+ " networks.");
}
+ current.clear();
+ current.addAll(updated);
return;
}
final CompareResult<String> interfaceDiff = new CompareResult<>(
current, updated);
for (String name : interfaceDiff.added) {
+ // Check if a socket has been created for the interface
+ final SocketInfo socketInfo = mTetherInterfaceSockets.get(name);
+ if (socketInfo != null) {
+ if (DBG) {
+ mSharedLog.i("Socket is existed for interface:" + name);
+ }
+ continue;
+ }
+
int ifaceIndex = mDependencies.getNetworkInterfaceIndexByName(name);
createSocket(LOCAL_NET, createLPForTetheredInterface(name, ifaceIndex));
}
@@ -580,6 +664,11 @@
for (String tetheredInterface : mTetheredInterfaces) {
retrieveAndNotifySocketFromInterface(tetheredInterface, cb);
}
+
+ if (mWifiP2pTetherInterface != null
+ && !mLocalOnlyInterfaces.contains(mWifiP2pTetherInterface)) {
+ retrieveAndNotifySocketFromInterface(mWifiP2pTetherInterface, cb);
+ }
} else {
retrieveAndNotifySocketFromNetwork(network, cb);
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
index bc94869..eb12b9a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
+++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
@@ -152,4 +152,15 @@
encoder.encode(CharBuffer.wrap(originalName), out, true /* endOfInput */);
return new String(out.array(), 0, out.position(), utf8);
}
+
+ /**
+ * Checks if the MdnsRecord needs to be renewed or not.
+ *
+ * <p>As per RFC6762 7.1 no need to query if remaining TTL is more than half the original one,
+ * so send the queries if half the TTL has passed.
+ */
+ public static boolean isRecordRenewalNeeded(@NonNull MdnsRecord mdnsRecord, final long now) {
+ return mdnsRecord.getTtl() > 0
+ && mdnsRecord.getRemainingTTL(now) <= mdnsRecord.getTtl() / 2;
+ }
}
\ No newline at end of file
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 98ad861..c95295c 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -861,8 +861,7 @@
// A helper object to track the current default HTTP proxy. ConnectivityService needs to tell
// the world when it changes.
- @VisibleForTesting
- protected final ProxyTracker mProxyTracker;
+ private final ProxyTracker mProxyTracker;
final private SettingsObserver mSettingsObserver;
@@ -1866,6 +1865,13 @@
mHandler.sendEmptyMessage(EVENT_INGRESS_RATE_LIMIT_CHANGED);
}
+ @VisibleForTesting
+ void simulateUpdateProxyInfo(@Nullable final Network network,
+ @NonNull final ProxyInfo proxyInfo) {
+ Message.obtain(mHandler, EVENT_PROXY_HAS_CHANGED,
+ new Pair<>(network, proxyInfo)).sendToTarget();
+ }
+
private void handleAlwaysOnNetworkRequest(
NetworkRequest networkRequest, String settingName, boolean defaultValue) {
final boolean enable = toBool(Settings.Global.getInt(
diff --git a/service/src/com/android/server/connectivity/ProxyTracker.java b/service/src/com/android/server/connectivity/ProxyTracker.java
index bc0929c..b3cbb2a 100644
--- a/service/src/com/android/server/connectivity/ProxyTracker.java
+++ b/service/src/com/android/server/connectivity/ProxyTracker.java
@@ -330,7 +330,7 @@
public void setDefaultProxy(@Nullable ProxyInfo proxyInfo) {
synchronized (mProxyLock) {
if (Objects.equals(mDefaultProxy, proxyInfo)) return;
- if (proxyInfo != null && !proxyInfo.isValid()) {
+ if (proxyInfo != null && !proxyInfo.isValid()) {
if (DBG) Log.d(TAG, "Invalid proxy properties, ignoring: " + proxyInfo);
return;
}
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index 8e47235..7b5c298 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -31,7 +31,7 @@
// (currently, CTS 10, 11, and 12).
java_defaults {
name: "ConnectivityTestsLatestSdkDefaults",
- target_sdk_version: "33",
+ target_sdk_version: "34",
}
java_library {
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 9808137..6c411cf 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -548,8 +548,9 @@
assertTrue(resolvedService.attributes.containsKey("nullBinaryDataAttr"))
assertNull(resolvedService.attributes["nullBinaryDataAttr"])
assertTrue(resolvedService.attributes.containsKey("emptyBinaryDataAttr"))
- // TODO: change the check to target SDK U when this is what the code implements
- if (isAtLeastU()) {
+ if (isAtLeastU() || CompatChanges.isChangeEnabled(
+ ConnectivityCompatChanges.ENABLE_PLATFORM_MDNS_BACKEND
+ )) {
assertArrayEquals(byteArrayOf(), resolvedService.attributes["emptyBinaryDataAttr"])
} else {
assertNull(resolvedService.attributes["emptyBinaryDataAttr"])
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 904e4bd..c79c295 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -11332,6 +11332,212 @@
assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
}
+ /*
+ * Note for maintainers about how PAC proxies are implemented in Android.
+ *
+ * Generally, a proxy is just a hostname and a port to which requests are sent, instead of
+ * sending them directly. Most HTTP libraries know to use this protocol, and the Java
+ * language has properties to help handling these :
+ * https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html
+ * Unfortunately these properties are very old and do not take multi-networking into account.
+ *
+ * A PAC proxy consists of a javascript file stored on a server, and the client is expected to
+ * download the file and interpret the javascript for each HTTP request to know where to direct
+ * it. The device must therefore run code (namely, a javascript interpreter) to interpret the
+ * PAC file correctly. Most HTTP libraries do not know how to do this, since they do not
+ * embark a javascript interpreter (and it would be generally unreasonable for them to do
+ * so). Some apps, notably browsers, do know how to do this, but they are the exception rather
+ * than the rule.
+ * So to support most apps, Android embarks the javascript interpreter. When a network is
+ * configured to have a PAC proxy, Android will first set the ProxyInfo object to an object
+ * that contains the PAC URL (to communicate that to apps that know how to use it), then
+ * download the PAC file and start a native process which will open a server on localhost,
+ * and uses the interpreter inside WebView to interpret the PAC file. This server takes
+ * a little bit of time to start and will listen on a random port. When the port is known,
+ * the framework receives a notification and it updates the ProxyInfo in LinkProperties
+ * as well as send a broadcast to inform apps. This new ProxyInfo still contains the PAC URL,
+ * but it also contains "localhost" as the host and the port that the server listens to as
+ * the port. This will let HTTP libraries that don't have a javascript interpreter work,
+ * because they'll send the requests to this server running on localhost on the correct port,
+ * and this server will do what is appropriate with each request according to the PAC file.
+ *
+ * Note that at the time of this writing, Android does not support starting multiple local
+ * PAC servers, though this would be possible in theory by just starting multiple instances
+ * of this process running their server on different ports. As a stopgap measure, Android
+ * keeps one local server which is always the one for the default network. If a non-default
+ * network has a PAC proxy, it will have a LinkProperties with a port of -1, which means it
+ * could still be used by apps that know how to use the PAC URL directly, but not by HTTP
+ * libs that don't know how to do that. When a network with a PAC proxy becomes the default,
+ * the local server is started. When a network without a PAC proxy becomes the default, the
+ * local server is stopped if it is running (and the LP for the old default network should
+ * be reset to have a port of -1).
+ *
+ * In principle, each network can be configured with a different proxy (typically in the
+ * network settings for a Wi-Fi network). These end up exposed in the LinkProperties of the
+ * relevant network.
+ * Android also exposes ConnectivityManager#getDefaultProxy, which is simply the proxy for
+ * the default network. This was retrofitted from a time where Android did not support multiple
+ * concurrent networks, hence the difficult architecture.
+ * Note that there is also a "global" proxy that can be set by the DPM. When this is set,
+ * it overrides the proxy settings for every single network at the same time – that is, the
+ * system behaves as if the global proxy is the configured proxy for each network.
+ *
+ * Generally there are four ways for apps to look up the proxy.
+ * - Looking up the ProxyInfo in the LinkProperties for a network.
+ * - Listening to the PROXY_CHANGED_ACTION broadcast
+ * - Calling ConnectivityManager#getDefaultProxy, or ConnectivityManager#getProxyForNetwork
+ * which can be used to retrieve the proxy for any given network or the default network by
+ * passing null.
+ * - Reading the standard JVM properties (http{s,}.proxy{Host,Port}). See the Java
+ * distribution documentation for details on how these are supposed to work :
+ * https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html
+ * In Android, these are set by ActivityThread in each process in response to the broadcast.
+ * Many apps actually use these, and it's important they work because it's part of the
+ * Java standard, meaning they need to be set for existing Java libs to work on Android.
+ */
+ @Test
+ public void testPacProxy() throws Exception {
+ final Uri pacUrl = Uri.parse("https://pac-url");
+ mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellAgent.connect(true);
+
+ final TestNetworkCallback wifiCallback = new TestNetworkCallback();
+ final NetworkRequest wifiRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI).build();
+ mCm.registerNetworkCallback(wifiRequest, wifiCallback);
+
+ mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiAgent.connect(true);
+ wifiCallback.expectAvailableThenValidatedCallbacks(mWiFiAgent);
+
+ final ProxyInfo initialProxyInfo = ProxyInfo.buildPacProxy(pacUrl);
+ final LinkProperties testLinkProperties = new LinkProperties();
+ testLinkProperties.setHttpProxy(initialProxyInfo);
+ mWiFiAgent.sendLinkProperties(testLinkProperties);
+ wifiCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mWiFiAgent);
+
+ // At first the local PAC proxy server is unstarted (see the description of what the local
+ // server is in the comment at the top of this method). It will contain the PAC URL and a
+ // port of -1 because it is unstarted. Check that all ways of getting that proxy info
+ // returns the same object that was initially created.
+ final ProxyInfo unstartedDefaultProxyInfo = mService.getProxyForNetwork(null);
+ final ProxyInfo unstartedWifiProxyInfo = mService.getProxyForNetwork(
+ mWiFiAgent.getNetwork());
+ final LinkProperties unstartedLp =
+ mService.getLinkProperties(mWiFiAgent.getNetwork());
+
+ assertEquals(initialProxyInfo, unstartedDefaultProxyInfo);
+ assertEquals(initialProxyInfo, unstartedWifiProxyInfo);
+ assertEquals(initialProxyInfo, unstartedLp.getHttpProxy());
+
+ // Make sure the cell network is unaffected. The LP are per-network and each network has
+ // its own configuration. The default proxy and broadcast are system-wide, and the JVM
+ // properties are per-process, and therefore can have only one value at any given time,
+ // so the code sets them to the proxy of the default network (TODO : really, since the
+ // default process is per-network, the JVM properties (http.proxyHost family – see
+ // the comment at the top of the method for details about these) also should be per-network
+ // and even the broadcast contents should be but none of this is implemented). The LP are
+ // still per-network, and a process that wants to use a non-default network is supposed to
+ // look up the proxy in its LP and it has to be correct.
+ assertNull(mService.getLinkProperties(mCellAgent.getNetwork()).getHttpProxy());
+ assertNull(mService.getProxyForNetwork(mCellAgent.getNetwork()));
+
+ // Simulate PacManager sending the notification that the local server has started
+ final ProxyInfo servingProxyInfo = new ProxyInfo(pacUrl, 2097);
+ final ExpectedBroadcast b1 = expectProxyChangeAction(servingProxyInfo);
+ mService.simulateUpdateProxyInfo(mWiFiAgent.getNetwork(), servingProxyInfo);
+// wifiCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mWiFiAgent);
+ b1.expectBroadcast();
+
+ final ProxyInfo startedDefaultProxyInfo = mService.getProxyForNetwork(null);
+ final ProxyInfo startedWifiProxyInfo = mService.getProxyForNetwork(
+ mWiFiAgent.getNetwork());
+ final LinkProperties startedLp = mService.getLinkProperties(mWiFiAgent.getNetwork());
+ // TODO : activate these tests when b/138810051 is fixed.
+// assertEquals(servingProxyInfo, startedDefaultProxyInfo);
+// assertEquals(servingProxyInfo, startedWifiProxyInfo);
+// assertEquals(servingProxyInfo, startedLp.getHttpProxy());
+// // Make sure the cell network is still unaffected
+// assertNull(mService.getLinkProperties(mCellAgent.getNetwork()).getHttpProxy());
+// assertNull(mService.getProxyForNetwork(mCellAgent.getNetwork()));
+//
+// final Uri ethPacUrl = Uri.parse("https://ethernet-pac-url");
+// final TestableNetworkCallback ethernetCallback = new TestableNetworkCallback();
+// final NetworkRequest ethernetRequest = new NetworkRequest.Builder()
+// .addTransportType(TRANSPORT_ETHERNET).build();
+// mEthernetAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
+// mCm.registerNetworkCallback(ethernetRequest, ethernetCallback);
+// mEthernetAgent.connect(true);
+// ethernetCallback.expectAvailableThenValidatedCallbacks(mEthernetAgent);
+// // Wifi is no longer the default, so it should get a callback to LP changed with a PAC
+// // proxy but a port of -1 (since the local proxy doesn't serve wifi now)
+// wifiCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent,
+// lp -> lp.getLp().getHttpProxy().getPort() == -1
+// && lp.getLp().getHttpProxy().isPacProxy());
+// wifiCallback.expect(CallbackEntry.LOSING, mWiFiAgent);
+//
+// final ProxyInfo ethProxy = ProxyInfo.buildPacProxy(ethPacUrl);
+// final LinkProperties ethLinkProperties = new LinkProperties();
+// ethLinkProperties.setHttpProxy(ethProxy);
+// mEthernetAgent.sendLinkProperties(ethLinkProperties);
+// ethernetCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mEthernetAgent);
+// // Default network is Ethernet
+// assertEquals(ethProxy, mService.getProxyForNetwork(null));
+// assertEquals(ethProxy, mService.getProxyForNetwork(mEthernetAgent.getNetwork()));
+// // Proxy info for WiFi ideally should be the old one with the old server still running,
+// // but as the PAC code only handles one server at any given time, this can't work. Wifi
+// // having null as a proxy also won't work (apps using WiFi will try to access the network
+// // without proxy) but is not as bad as having the new proxy (that would send requests
+// // over the default network).
+// assertEquals(initialProxyInfo, mService.getProxyForNetwork(mWiFiAgent.getNetwork()));
+// assertNull(mService.getProxyForNetwork(mCellAgent.getNetwork()));
+//
+// final ProxyInfo servingEthProxy = new ProxyInfo(pacUrl, 2099);
+// final ExpectedBroadcast b2 = expectProxyChangeAction(servingEthProxy);
+// final ExpectedBroadcast b3 = expectProxyChangeAction(servingProxyInfo);
+//
+// mService.simulateUpdateProxyInfo(mEthernetAgent.getNetwork(), servingEthProxy);
+// ethernetCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mEthernetAgent);
+// assertEquals(servingEthProxy, mService.getProxyForNetwork(null));
+// assertEquals(servingEthProxy, mService.getProxyForNetwork(
+// mEthernetAgent.getNetwork()));
+// assertEquals(initialProxyInfo,
+// mService.getProxyForNetwork(mWiFiAgent.getNetwork()));
+// assertNull(mService.getProxyForNetwork(mCellAgent.getNetwork()));
+// b2.expectBroadcast();
+//
+// // Ethernet disconnects, back to WiFi
+// mEthernetAgent.disconnect();
+// ethernetCallback.expect(CallbackEntry.LOST, mEthernetAgent);
+// wifiCallback.assertNoCallback();
+//
+// assertEquals(initialProxyInfo, mService.getProxyForNetwork(null));
+// assertEquals(initialProxyInfo,
+// mService.getProxyForNetwork(mWiFiAgent.getNetwork()));
+// assertNull(mService.getProxyForNetwork(mCellAgent.getNetwork()));
+//
+// // After a while the PAC file for wifi is resolved again
+// mService.simulateUpdateProxyInfo(mWiFiAgent.getNetwork(), servingProxyInfo);
+// wifiCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mWiFiAgent);
+// assertEquals(servingProxyInfo, mService.getProxyForNetwork(null));
+// assertEquals(servingProxyInfo,
+// mService.getProxyForNetwork(mWiFiAgent.getNetwork()));
+// assertNull(mService.getProxyForNetwork(mCellAgent.getNetwork()));
+// b3.expectBroadcast();
+//
+// // Expect a broadcast after wifi disconnected. The proxy might be default proxy or an
+// // empty proxy built by buildDirectProxy. See {@link ProxyTracker.sendProxyBroadcast}.
+// // Thus here expects a broadcast will be received but does not verify the content of the
+// // proxy.
+// final ExpectedBroadcast b4 = expectProxyChangeAction();
+// mWiFiAgent.disconnect();
+// b4.expectBroadcast();
+// wifiCallback.expect(CallbackEntry.LOST, mWiFiAgent);
+// assertNull(mService.getProxyForNetwork(null));
+ assertNull(mService.getLinkProperties(mCellAgent.getNetwork()).getHttpProxy());
+ assertNull(mService.getGlobalProxy());
+ }
+
@Test
public void testGetProxyForVPN() throws Exception {
final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index f15e8ff..da51240 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -705,34 +705,8 @@
}
@Test
- public void processResponse_notAllowRemoveSearch_shouldNotRemove() throws Exception {
- final String serviceInstanceName = "service-instance-1";
- client.startSendAndReceive(
- mockListenerOne,
- MdnsSearchOptions.newBuilder().build());
- Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
-
- // Process the initial response.
- client.processResponse(createResponse(
- serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
- Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
-
- // Clear the scheduled runnable.
- currentThreadExecutor.getAndClearLastScheduledRunnable();
-
- // Simulate the case where the response is after TTL.
- doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
- firstMdnsTask.run();
-
- // Verify removed callback was not called.
- verifyServiceRemovedNoCallback(mockListenerOne);
- }
-
- @Test
- @Ignore("MdnsConfigs is not configurable currently.")
- public void processResponse_allowSearchOptionsToRemoveExpiredService_shouldRemove()
+ public void processResponse_searchOptionsEnableServiceRemoval_shouldRemove()
throws Exception {
- //MdnsConfigsFlagsImpl.allowSearchOptionsToRemoveExpiredService.override(true);
final String serviceInstanceName = "service-instance-1";
client =
new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
@@ -742,7 +716,9 @@
return mockPacketWriter;
}
};
- client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
+ MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder().setRemoveExpiredService(
+ true).build();
+ client.startSendAndReceive(mockListenerOne, searchOptions);
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
// Process the initial response.
@@ -956,15 +932,11 @@
final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
new MdnsPacketReader(srvTxtQueryCaptor.getValue()));
- final List<MdnsRecord> srvTxtQuestions = srvTxtQueryPacket.questions;
- final String[] serviceName = Stream.concat(Stream.of(instanceName),
- Arrays.stream(SERVICE_TYPE_LABELS)).toArray(String[]::new);
- assertFalse(srvTxtQuestions.stream().anyMatch(q -> q.getType() == MdnsRecord.TYPE_PTR));
- assertTrue(srvTxtQuestions.stream().anyMatch(q ->
- q.getType() == MdnsRecord.TYPE_SRV && Arrays.equals(q.name, serviceName)));
- assertTrue(srvTxtQuestions.stream().anyMatch(q ->
- q.getType() == MdnsRecord.TYPE_TXT && Arrays.equals(q.name, serviceName)));
+ final String[] serviceName = getTestServiceName(instanceName);
+ assertFalse(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_PTR));
+ assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_SRV, serviceName));
+ assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_TXT, serviceName));
// Process a response with SRV+TXT
final MdnsPacket srvTxtResponse = new MdnsPacket(
@@ -991,11 +963,8 @@
final MdnsPacket addressQueryPacket = MdnsPacket.parse(
new MdnsPacketReader(addressQueryCaptor.getValue()));
- final List<MdnsRecord> addressQueryQuestions = addressQueryPacket.questions;
- assertTrue(addressQueryQuestions.stream().anyMatch(q ->
- q.getType() == MdnsRecord.TYPE_A && Arrays.equals(q.name, hostname)));
- assertTrue(addressQueryQuestions.stream().anyMatch(q ->
- q.getType() == MdnsRecord.TYPE_AAAA && Arrays.equals(q.name, hostname)));
+ assertTrue(hasQuestion(addressQueryPacket, MdnsRecord.TYPE_A, hostname));
+ assertTrue(hasQuestion(addressQueryPacket, MdnsRecord.TYPE_AAAA, hostname));
// Process a response with address records
final MdnsPacket addressResponse = new MdnsPacket(
@@ -1028,6 +997,81 @@
}
@Test
+ public void testRenewTxtSrvInResolve() throws Exception {
+ client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+ mockDecoderClock, mockNetwork, mockSharedLog);
+
+ final String instanceName = "service-instance";
+ final String[] hostname = new String[] { "testhost "};
+ final String ipV4Address = "192.0.2.0";
+ final String ipV6Address = "2001:db8::";
+
+ final MdnsSearchOptions resolveOptions = MdnsSearchOptions.newBuilder()
+ .setResolveInstanceName(instanceName).build();
+
+ client.startSendAndReceive(mockListenerOne, resolveOptions);
+ InOrder inOrder = inOrder(mockListenerOne, mockSocketClient);
+
+ // Get the query for SRV/TXT
+ final ArgumentCaptor<DatagramPacket> srvTxtQueryCaptor =
+ ArgumentCaptor.forClass(DatagramPacket.class);
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+ // Send twice for IPv4 and IPv6
+ inOrder.verify(mockSocketClient, times(2)).sendUnicastPacket(srvTxtQueryCaptor.capture(),
+ eq(mockNetwork));
+
+ final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
+ new MdnsPacketReader(srvTxtQueryCaptor.getValue()));
+
+ final String[] serviceName = getTestServiceName(instanceName);
+ assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_SRV, serviceName));
+ assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_TXT, serviceName));
+
+ // Process a response with all records
+ final MdnsPacket srvTxtResponse = new MdnsPacket(
+ 0 /* flags */,
+ Collections.emptyList() /* questions */,
+ List.of(
+ new MdnsServiceRecord(serviceName, TEST_ELAPSED_REALTIME,
+ true /* cacheFlush */, TEST_TTL, 0 /* servicePriority */,
+ 0 /* serviceWeight */, 1234 /* servicePort */, hostname),
+ new MdnsTextRecord(serviceName, TEST_ELAPSED_REALTIME,
+ true /* cacheFlush */, TEST_TTL,
+ Collections.emptyList() /* entries */),
+ new MdnsInetAddressRecord(hostname, TEST_ELAPSED_REALTIME,
+ true /* cacheFlush */, TEST_TTL,
+ InetAddresses.parseNumericAddress(ipV4Address)),
+ new MdnsInetAddressRecord(hostname, TEST_ELAPSED_REALTIME,
+ true /* cacheFlush */, TEST_TTL,
+ InetAddresses.parseNumericAddress(ipV6Address))),
+ Collections.emptyList() /* authorityRecords */,
+ Collections.emptyList() /* additionalRecords */);
+ client.processResponse(srvTxtResponse, INTERFACE_INDEX, mockNetwork);
+ inOrder.verify(mockListenerOne).onServiceNameDiscovered(any());
+ inOrder.verify(mockListenerOne).onServiceFound(any());
+
+ // Expect no query on the next run
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+ inOrder.verifyNoMoreInteractions();
+
+ // Advance time so 75% of TTL passes and re-execute
+ doReturn(TEST_ELAPSED_REALTIME + (long) (TEST_TTL * 0.75))
+ .when(mockDecoderClock).elapsedRealtime();
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+
+ // Expect a renewal query
+ final ArgumentCaptor<DatagramPacket> renewalQueryCaptor =
+ ArgumentCaptor.forClass(DatagramPacket.class);
+ // Second and later sends are sent as "expect multicast response" queries
+ inOrder.verify(mockSocketClient, times(2)).sendMulticastPacket(renewalQueryCaptor.capture(),
+ eq(mockNetwork));
+ final MdnsPacket renewalPacket = MdnsPacket.parse(
+ new MdnsPacketReader(renewalQueryCaptor.getValue()));
+ assertTrue(hasQuestion(renewalPacket, MdnsRecord.TYPE_SRV, serviceName));
+ assertTrue(hasQuestion(renewalPacket, MdnsRecord.TYPE_TXT, serviceName));
+ }
+
+ @Test
public void testProcessResponse_ResolveExcludesOtherServices() {
client = new MdnsServiceTypeClient(
SERVICE_TYPE, mockSocketClient, currentThreadExecutor, mockNetwork, mockSharedLog);
@@ -1270,6 +1314,20 @@
}
}
+ private static String[] getTestServiceName(String instanceName) {
+ return Stream.concat(Stream.of(instanceName),
+ Arrays.stream(SERVICE_TYPE_LABELS)).toArray(String[]::new);
+ }
+
+ private static boolean hasQuestion(MdnsPacket packet, int type) {
+ return hasQuestion(packet, type, null);
+ }
+
+ private static boolean hasQuestion(MdnsPacket packet, int type, @Nullable String[] name) {
+ return packet.questions.stream().anyMatch(q -> q.getType() == type
+ && (name == null || Arrays.equals(q.name, name)));
+ }
+
// A fake ScheduledExecutorService that keeps tracking the last scheduled Runnable and its delay
// time.
private class FakeExecutor extends ScheduledThreadPoolExecutor {
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
index 4b87556..4f56857 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
@@ -31,6 +31,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
@@ -40,7 +41,9 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkAddress;
@@ -49,6 +52,9 @@
import android.net.NetworkCapabilities;
import android.net.TetheringManager;
import android.net.TetheringManager.TetheringEventCallback;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pInfo;
+import android.net.wifi.p2p.WifiP2pManager;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
@@ -88,6 +94,7 @@
private static final String TAG = MdnsSocketProviderTest.class.getSimpleName();
private static final String TEST_IFACE_NAME = "test";
private static final String LOCAL_ONLY_IFACE_NAME = "local_only";
+ private static final String WIFI_P2P_IFACE_NAME = "p2p_wifi";
private static final String TETHERED_IFACE_NAME = "tethered";
private static final int TETHERED_IFACE_IDX = 32;
private static final long DEFAULT_TIMEOUT = 2000L;
@@ -136,11 +143,15 @@
doReturn(true).when(mTetheredIfaceWrapper).supportsMulticast();
doReturn(mLocalOnlyIfaceWrapper).when(mDeps)
.getNetworkInterfaceByName(LOCAL_ONLY_IFACE_NAME);
+ doReturn(mLocalOnlyIfaceWrapper).when(mDeps)
+ .getNetworkInterfaceByName(WIFI_P2P_IFACE_NAME);
doReturn(mTetheredIfaceWrapper).when(mDeps).getNetworkInterfaceByName(TETHERED_IFACE_NAME);
doReturn(mock(MdnsInterfaceSocket.class))
.when(mDeps).createMdnsInterfaceSocket(any(), anyInt(), any(), any());
doReturn(TETHERED_IFACE_IDX).when(mDeps).getNetworkInterfaceIndexByName(
TETHERED_IFACE_NAME);
+ doReturn(789).when(mDeps).getNetworkInterfaceIndexByName(
+ WIFI_P2P_IFACE_NAME);
final HandlerThread thread = new HandlerThread("MdnsSocketProviderTest");
thread.start();
mHandler = new Handler(thread.getLooper());
@@ -157,22 +168,41 @@
mSocketProvider = new MdnsSocketProvider(mContext, thread.getLooper(), mDeps, mLog);
}
+ private void runOnHandler(Runnable r) {
+ mHandler.post(r);
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ }
+
+ private BroadcastReceiver expectWifiP2PChangeBroadcastReceiver() {
+ final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mContext, times(1)).registerReceiver(receiverCaptor.capture(),
+ argThat(filter -> filter.hasAction(
+ WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)),
+ any(), any());
+ final BroadcastReceiver originalReceiver = receiverCaptor.getValue();
+ return new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ runOnHandler(() -> originalReceiver.onReceive(context, intent));
+ }
+ };
+ }
+
private void startMonitoringSockets() {
final ArgumentCaptor<NetworkCallback> nwCallbackCaptor =
ArgumentCaptor.forClass(NetworkCallback.class);
final ArgumentCaptor<TetheringEventCallback> teCallbackCaptor =
ArgumentCaptor.forClass(TetheringEventCallback.class);
- mHandler.post(mSocketProvider::startMonitoringSockets);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(mSocketProvider::startMonitoringSockets);
verify(mCm).registerNetworkCallback(any(), nwCallbackCaptor.capture(), any());
verify(mTm).registerTetheringEventCallback(any(), teCallbackCaptor.capture());
mNetworkCallback = nwCallbackCaptor.getValue();
mTetheringEventCallback = teCallbackCaptor.getValue();
- mHandler.post(mSocketProvider::startNetLinkMonitor);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(mSocketProvider::startNetLinkMonitor);
}
private static class TestNetlinkMonitor extends SocketNetlinkMonitor {
@@ -281,9 +311,8 @@
testLp.setInterfaceName(TEST_IFACE_NAME);
testLp.setLinkAddresses(List.of(LINKADDRV4));
final NetworkCapabilities testNc = makeCapabilities(transports);
- mHandler.post(() -> mNetworkCallback.onCapabilitiesChanged(TEST_NETWORK, testNc));
- mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mNetworkCallback.onCapabilitiesChanged(TEST_NETWORK, testNc));
+ runOnHandler(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
}
@Test
@@ -291,62 +320,53 @@
startMonitoringSockets();
final TestSocketCallback testCallback1 = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback1));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback1));
testCallback1.expectedNoCallback();
postNetworkAvailable(TRANSPORT_WIFI);
testCallback1.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
final TestSocketCallback testCallback2 = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback2));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback2));
testCallback1.expectedNoCallback();
testCallback2.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
final TestSocketCallback testCallback3 = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(null /* network */, testCallback3));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(null /* network */, testCallback3));
testCallback1.expectedNoCallback();
testCallback2.expectedNoCallback();
testCallback3.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
- mHandler.post(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(
+ runOnHandler(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(
List.of(LOCAL_ONLY_IFACE_NAME)));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mLocalOnlyIfaceWrapper).getNetworkInterface();
testCallback1.expectedNoCallback();
testCallback2.expectedNoCallback();
testCallback3.expectedSocketCreatedForNetwork(null /* network */, List.of());
- mHandler.post(() -> mTetheringEventCallback.onTetheredInterfacesChanged(
+ runOnHandler(() -> mTetheringEventCallback.onTetheredInterfacesChanged(
List.of(TETHERED_IFACE_NAME)));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mTetheredIfaceWrapper).getNetworkInterface();
testCallback1.expectedNoCallback();
testCallback2.expectedNoCallback();
testCallback3.expectedSocketCreatedForNetwork(null /* network */, List.of());
- mHandler.post(() -> mSocketProvider.unrequestSocket(testCallback1));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.unrequestSocket(testCallback1));
testCallback1.expectedNoCallback();
testCallback2.expectedNoCallback();
testCallback3.expectedNoCallback();
- mHandler.post(() -> mNetworkCallback.onLost(TEST_NETWORK));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mNetworkCallback.onLost(TEST_NETWORK));
testCallback1.expectedNoCallback();
testCallback2.expectedInterfaceDestroyedForNetwork(TEST_NETWORK);
testCallback3.expectedInterfaceDestroyedForNetwork(TEST_NETWORK);
- mHandler.post(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(List.of()));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(List.of()));
testCallback1.expectedNoCallback();
testCallback2.expectedNoCallback();
testCallback3.expectedInterfaceDestroyedForNetwork(null /* network */);
- mHandler.post(() -> mSocketProvider.unrequestSocket(testCallback3));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.unrequestSocket(testCallback3));
testCallback1.expectedNoCallback();
testCallback2.expectedNoCallback();
// There was still a tethered interface, but no callback should be sent once unregistered
@@ -376,8 +396,7 @@
public void testDownstreamNetworkAddressUpdateFromNetlink() {
startMonitoringSockets();
final TestSocketCallback testCallbackAll = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(null /* network */, testCallbackAll));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(null /* network */, testCallbackAll));
// Address add message arrived before the interface is created.
RtNetlinkAddressMessage addIpv4AddrMsg = createNetworkAddressUpdateNetLink(
@@ -385,15 +404,13 @@
LINKADDRV4,
TETHERED_IFACE_IDX,
0 /* flags */);
- mHandler.post(
+ runOnHandler(
() -> mTestSocketNetLinkMonitor.processNetlinkMessage(addIpv4AddrMsg,
0 /* whenMs */));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
// Interface is created.
- mHandler.post(() -> mTetheringEventCallback.onTetheredInterfacesChanged(
+ runOnHandler(() -> mTetheringEventCallback.onTetheredInterfacesChanged(
List.of(TETHERED_IFACE_NAME)));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mTetheredIfaceWrapper).getNetworkInterface();
testCallbackAll.expectedSocketCreatedForNetwork(null /* network */, List.of(LINKADDRV4));
@@ -403,10 +420,9 @@
LINKADDRV4,
TETHERED_IFACE_IDX,
0 /* flags */);
- mHandler.post(
+ runOnHandler(
() -> mTestSocketNetLinkMonitor.processNetlinkMessage(removeIpv4AddrMsg,
0 /* whenMs */));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
testCallbackAll.expectedAddressesChangedForNetwork(null /* network */, List.of());
// New address added.
@@ -415,9 +431,8 @@
LINKADDRV6,
TETHERED_IFACE_IDX,
0 /* flags */);
- mHandler.post(() -> mTestSocketNetLinkMonitor.processNetlinkMessage(addIpv6AddrMsg,
+ runOnHandler(() -> mTestSocketNetLinkMonitor.processNetlinkMessage(addIpv6AddrMsg,
0 /* whenMs */));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
testCallbackAll.expectedAddressesChangedForNetwork(null /* network */, List.of(LINKADDRV6));
// Address updated
@@ -426,10 +441,9 @@
LINKADDRV6,
TETHERED_IFACE_IDX,
1 /* flags */);
- mHandler.post(
+ runOnHandler(
() -> mTestSocketNetLinkMonitor.processNetlinkMessage(updateIpv6AddrMsg,
0 /* whenMs */));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
testCallbackAll.expectedAddressesChangedForNetwork(null /* network */,
List.of(LINKADDRV6_FLAG_CHANGE));
}
@@ -439,8 +453,7 @@
startMonitoringSockets();
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
testCallback.expectedNoCallback();
postNetworkAvailable(TRANSPORT_WIFI);
@@ -449,8 +462,7 @@
final LinkProperties newTestLp = new LinkProperties();
newTestLp.setInterfaceName(TEST_IFACE_NAME);
newTestLp.setLinkAddresses(List.of(LINKADDRV4, LINKADDRV6));
- mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, newTestLp));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, newTestLp));
testCallback.expectedAddressesChangedForNetwork(
TEST_NETWORK, List.of(LINKADDRV4, LINKADDRV6));
}
@@ -458,8 +470,7 @@
@Test
public void testStartAndStopMonitoringSockets() {
// Stop monitoring sockets before start. Should not unregister any network callback.
- mHandler.post(mSocketProvider::requestStopWhenInactive);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(mSocketProvider::requestStopWhenInactive);
verify(mCm, never()).unregisterNetworkCallback(any(NetworkCallback.class));
verify(mTm, never()).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
@@ -467,39 +478,32 @@
startMonitoringSockets();
// Request a socket then unrequest it. Expect no network callback unregistration.
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
testCallback.expectedNoCallback();
- mHandler.post(()-> mSocketProvider.unrequestSocket(testCallback));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(()-> mSocketProvider.unrequestSocket(testCallback));
verify(mCm, never()).unregisterNetworkCallback(any(NetworkCallback.class));
verify(mTm, never()).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
// Request stop and it should unregister network callback immediately because there is no
// socket request.
- mHandler.post(mSocketProvider::requestStopWhenInactive);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(mSocketProvider::requestStopWhenInactive);
verify(mCm, times(1)).unregisterNetworkCallback(any(NetworkCallback.class));
verify(mTm, times(1)).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
// Start sockets monitoring and request a socket again.
- mHandler.post(mSocketProvider::startMonitoringSockets);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(mSocketProvider::startMonitoringSockets);
verify(mCm, times(2)).registerNetworkCallback(any(), any(NetworkCallback.class), any());
verify(mTm, times(2)).registerTetheringEventCallback(
any(), any(TetheringEventCallback.class));
final TestSocketCallback testCallback2 = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback2));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback2));
testCallback2.expectedNoCallback();
// Try to stop monitoring sockets but should be ignored and wait until all socket are
// unrequested.
- mHandler.post(mSocketProvider::requestStopWhenInactive);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(mSocketProvider::requestStopWhenInactive);
verify(mCm, times(1)).unregisterNetworkCallback(any(NetworkCallback.class));
verify(mTm, times(1)).unregisterTetheringEventCallback(any());
// Unrequest the socket then network callbacks should be unregistered.
- mHandler.post(()-> mSocketProvider.unrequestSocket(testCallback2));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(()-> mSocketProvider.unrequestSocket(testCallback2));
verify(mCm, times(2)).unregisterNetworkCallback(any(NetworkCallback.class));
verify(mTm, times(2)).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
}
@@ -510,24 +514,20 @@
// Request a socket with null network.
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(null, testCallback));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(null, testCallback));
testCallback.expectedNoCallback();
// Notify a LinkPropertiesChanged with TEST_NETWORK.
final LinkProperties testLp = new LinkProperties();
testLp.setInterfaceName(TEST_IFACE_NAME);
testLp.setLinkAddresses(List.of(LINKADDRV4));
- mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
verify(mTestNetworkIfaceWrapper, times(1)).getNetworkInterface();
testCallback.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
// Try to stop monitoring and unrequest the socket.
- mHandler.post(mSocketProvider::requestStopWhenInactive);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
- mHandler.post(()-> mSocketProvider.unrequestSocket(testCallback));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(mSocketProvider::requestStopWhenInactive);
+ runOnHandler(()-> mSocketProvider.unrequestSocket(testCallback));
// No callback sent when unregistered
testCallback.expectedNoCallback();
verify(mCm, times(1)).unregisterNetworkCallback(any(NetworkCallback.class));
@@ -535,13 +535,11 @@
// Start sockets monitoring and request a socket again. Expected no socket created callback
// because all saved LinkProperties has been cleared.
- mHandler.post(mSocketProvider::startMonitoringSockets);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(mSocketProvider::startMonitoringSockets);
verify(mCm, times(2)).registerNetworkCallback(any(), any(NetworkCallback.class), any());
verify(mTm, times(2)).registerTetheringEventCallback(
any(), any(TetheringEventCallback.class));
- mHandler.post(() -> mSocketProvider.requestSocket(null, testCallback));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(null, testCallback));
testCallback.expectedNoCallback();
// Notify a LinkPropertiesChanged with another network.
@@ -550,8 +548,7 @@
final Network otherNetwork = new Network(456);
otherLp.setInterfaceName("test2");
otherLp.setLinkAddresses(List.of(otherAddress));
- mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(otherNetwork, otherLp));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mNetworkCallback.onLinkPropertiesChanged(otherNetwork, otherLp));
verify(mTestNetworkIfaceWrapper, times(2)).getNetworkInterface();
testCallback.expectedSocketCreatedForNetwork(otherNetwork, List.of(otherAddress));
}
@@ -561,7 +558,7 @@
startMonitoringSockets();
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
postNetworkAvailable(TRANSPORT_CELLULAR);
testCallback.expectedNoCallback();
@@ -573,7 +570,7 @@
startMonitoringSockets();
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
postNetworkAvailable(TRANSPORT_BLUETOOTH);
testCallback.expectedNoCallback();
@@ -585,7 +582,7 @@
startMonitoringSockets();
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
postNetworkAvailable(TRANSPORT_BLUETOOTH);
testCallback.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
@@ -597,7 +594,7 @@
startMonitoringSockets();
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
postNetworkAvailable(TRANSPORT_BLUETOOTH);
testCallback.expectedNoCallback();
@@ -611,7 +608,7 @@
startMonitoringSockets();
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
postNetworkAvailable(TRANSPORT_VPN, TRANSPORT_WIFI);
testCallback.expectedNoCallback();
@@ -623,9 +620,146 @@
startMonitoringSockets();
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
postNetworkAvailable(TRANSPORT_WIFI);
testCallback.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
}
+
+ private Intent buildWifiP2PConnectionChangedIntent(boolean groupFormed) {
+ final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+ final WifiP2pInfo formedInfo = new WifiP2pInfo();
+ formedInfo.groupFormed = groupFormed;
+ final WifiP2pGroup group;
+ if (groupFormed) {
+ group = mock(WifiP2pGroup.class);
+ doReturn(WIFI_P2P_IFACE_NAME).when(group).getInterface();
+ } else {
+ group = null;
+ }
+ intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, formedInfo);
+ intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, group);
+ return intent;
+ }
+
+ @Test
+ public void testWifiP2PInterfaceChange() {
+ final BroadcastReceiver receiver = expectWifiP2PChangeBroadcastReceiver();
+ startMonitoringSockets();
+
+ // Request a socket with null network.
+ final TestSocketCallback testCallback = new TestSocketCallback();
+ runOnHandler(() -> mSocketProvider.requestSocket(null /* network */, testCallback));
+
+ // Wifi p2p is connected and the interface is up. Get a wifi p2p change intent then expect
+ // a socket creation.
+ final Intent formedIntent = buildWifiP2PConnectionChangedIntent(true /* groupFormed */);
+ receiver.onReceive(mContext, formedIntent);
+ verify(mLocalOnlyIfaceWrapper).getNetworkInterface();
+ testCallback.expectedSocketCreatedForNetwork(null /* network */, List.of());
+
+ // Wifi p2p is disconnected. Get a wifi p2p change intent then expect the socket destroy.
+ final Intent unformedIntent = buildWifiP2PConnectionChangedIntent(false /* groupFormed */);
+ receiver.onReceive(mContext, unformedIntent);
+ testCallback.expectedInterfaceDestroyedForNetwork(null /* network */);
+ }
+
+ @Test
+ public void testWifiP2PInterfaceChangeBeforeStartMonitoringSockets() {
+ final BroadcastReceiver receiver = expectWifiP2PChangeBroadcastReceiver();
+
+ // Get a wifi p2p change intent before start monitoring sockets.
+ final Intent formedIntent = buildWifiP2PConnectionChangedIntent(true /* groupFormed */);
+ receiver.onReceive(mContext, formedIntent);
+
+ // Start monitoring sockets and request a socket with null network.
+ startMonitoringSockets();
+ final TestSocketCallback testCallback = new TestSocketCallback();
+ runOnHandler(() -> mSocketProvider.requestSocket(null /* network */, testCallback));
+ verify(mLocalOnlyIfaceWrapper).getNetworkInterface();
+ testCallback.expectedSocketCreatedForNetwork(null /* network */, List.of());
+ }
+
+ @Test
+ public void testWifiP2PInterfaceChangeBeforeGetAllNetworksRequest() {
+ final BroadcastReceiver receiver = expectWifiP2PChangeBroadcastReceiver();
+ startMonitoringSockets();
+
+ // Get a wifi p2p change intent before request socket for all networks.
+ final Intent formedIntent = buildWifiP2PConnectionChangedIntent(true /* groupFormed */);
+ receiver.onReceive(mContext, formedIntent);
+
+ // Request a socket with null network.
+ final TestSocketCallback testCallback = new TestSocketCallback();
+ runOnHandler(() -> mSocketProvider.requestSocket(null /* network */, testCallback));
+ verify(mLocalOnlyIfaceWrapper).getNetworkInterface();
+ testCallback.expectedSocketCreatedForNetwork(null /* network */, List.of());
+ }
+
+ @Test
+ public void testNoDuplicatedSocketCreation() {
+ final BroadcastReceiver receiver = expectWifiP2PChangeBroadcastReceiver();
+ startMonitoringSockets();
+
+ // Request a socket with null network.
+ final TestSocketCallback testCallback = new TestSocketCallback();
+ runOnHandler(() -> mSocketProvider.requestSocket(null, testCallback));
+ testCallback.expectedNoCallback();
+
+ // Receive an interface added change for the wifi p2p interface. Expect a socket creation
+ // callback.
+ runOnHandler(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(
+ List.of(WIFI_P2P_IFACE_NAME)));
+ verify(mLocalOnlyIfaceWrapper, times(1)).getNetworkInterface();
+ testCallback.expectedSocketCreatedForNetwork(null /* network */, List.of());
+
+ // Receive a wifi p2p connected intent. Expect no callback because the socket is created.
+ final Intent formedIntent = buildWifiP2PConnectionChangedIntent(true /* groupFormed */);
+ receiver.onReceive(mContext, formedIntent);
+ testCallback.expectedNoCallback();
+
+ // Request other socket with null network. Should receive socket created callback once.
+ final TestSocketCallback testCallback2 = new TestSocketCallback();
+ runOnHandler(() -> mSocketProvider.requestSocket(null, testCallback2));
+ testCallback2.expectedSocketCreatedForNetwork(null /* network */, List.of());
+ testCallback2.expectedNoCallback();
+
+ // Receive a wifi p2p disconnected intent. Expect a socket destroy callback.
+ final Intent unformedIntent = buildWifiP2PConnectionChangedIntent(false /* groupFormed */);
+ receiver.onReceive(mContext, unformedIntent);
+ testCallback.expectedInterfaceDestroyedForNetwork(null /* network */);
+
+ // Receive an interface removed change for the wifi p2p interface. Expect no callback
+ // because the socket is destroyed.
+ runOnHandler(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(List.of()));
+ testCallback.expectedNoCallback();
+
+ // Receive a wifi p2p connected intent again. Expect a socket creation callback.
+ receiver.onReceive(mContext, formedIntent);
+ verify(mLocalOnlyIfaceWrapper, times(2)).getNetworkInterface();
+ testCallback.expectedSocketCreatedForNetwork(null /* network */, List.of());
+
+ // Receive an interface added change for the wifi p2p interface again. Expect no callback
+ // because the socket is created.
+ runOnHandler(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(
+ List.of(WIFI_P2P_IFACE_NAME)));
+ testCallback.expectedNoCallback();
+ }
+
+ @Test
+ public void testTetherInterfacesChangedBeforeGetAllNetworksRequest() {
+ startMonitoringSockets();
+
+ // Receive an interface added change for the wifi p2p interface. Expect a socket creation
+ // callback.
+ runOnHandler(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(
+ List.of(TETHERED_IFACE_NAME)));
+ verify(mTetheredIfaceWrapper, never()).getNetworkInterface();
+
+ // Request a socket with null network.
+ final TestSocketCallback testCallback = new TestSocketCallback();
+ runOnHandler(() -> mSocketProvider.requestSocket(null /* network */, testCallback));
+ verify(mTetheredIfaceWrapper).getNetworkInterface();
+ testCallback.expectedSocketCreatedForNetwork(null /* network */, List.of());
+ }
}