Merge "Update record receipt time on records updated"
diff --git a/Cronet/tests/cts/Android.bp b/Cronet/tests/cts/Android.bp
index 1e0adef..a0b2434 100644
--- a/Cronet/tests/cts/Android.bp
+++ b/Cronet/tests/cts/Android.bp
@@ -37,6 +37,7 @@
"junit",
"kotlin-test",
"mockito-target",
+ "net-tests-utils",
"truth",
],
libs: [
diff --git a/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt b/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
index bead1f8..0760e68 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
@@ -23,8 +23,10 @@
import android.net.http.cts.util.TestBidirectionalStreamCallback.ResponseStep
import android.net.http.cts.util.assumeOKStatusCode
import android.net.http.cts.util.skipIfNoInternetConnection
+import android.os.Build
import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
import kotlin.test.Test
import kotlin.test.assertEquals
import org.hamcrest.MatcherAssert
@@ -39,7 +41,8 @@
* This tests uses a non-hermetic server. Instead of asserting, assume the next callback. This way,
* if the request were to fail, the test would just be skipped instead of failing.
*/
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
class BidirectionalStreamTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val callback = TestBidirectionalStreamCallback()
diff --git a/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt b/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt
index 749389e..1405ed9 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt
@@ -23,8 +23,10 @@
import android.net.http.cts.util.TestUrlRequestCallback
import android.net.http.cts.util.TestUrlRequestCallback.FailureType
import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep
+import android.os.Build
import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs
@@ -32,7 +34,8 @@
import kotlin.test.assertTrue
import org.junit.runner.RunWith
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
class CallbackExceptionTest {
@Test
diff --git a/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt b/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt
index 219db61..10c7f3c 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt
@@ -19,12 +19,15 @@
import android.net.http.ConnectionMigrationOptions
import android.net.http.ConnectionMigrationOptions.MIGRATION_OPTION_ENABLED
import android.net.http.ConnectionMigrationOptions.MIGRATION_OPTION_UNSPECIFIED
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.os.Build
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
import kotlin.test.Test
import kotlin.test.assertEquals
import org.junit.runner.RunWith
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
class ConnectionMigrationOptionsTest {
@Test
diff --git a/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt b/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt
index 6f4a979..56802c6 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt
@@ -19,7 +19,9 @@
import android.net.http.DnsOptions
import android.net.http.DnsOptions.DNS_OPTION_ENABLED
import android.net.http.DnsOptions.DNS_OPTION_UNSPECIFIED
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.os.Build
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
import java.time.Duration
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -27,7 +29,8 @@
import kotlin.test.assertNull
import org.junit.runner.RunWith
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
class DnsOptionsTest {
@Test
diff --git a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
index 31990fb..9fc4389 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
@@ -40,9 +40,12 @@
import android.net.http.cts.util.HttpCtsTestServer;
import android.net.http.cts.util.TestUrlRequestCallback;
import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep;
+import android.os.Build;
import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.After;
import org.junit.Before;
@@ -55,7 +58,8 @@
import java.util.Calendar;
import java.util.Set;
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
public class HttpEngineTest {
private static final String HOST = "source.android.com";
private static final String URL = "https://" + HOST;
diff --git a/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt b/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt
index dd4cf0d..cff54b3 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt
@@ -19,8 +19,10 @@
import android.net.http.HttpEngine
import android.net.http.NetworkException
import android.net.http.cts.util.TestUrlRequestCallback
+import android.os.Build
import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertSame
@@ -28,7 +30,8 @@
import org.junit.Test
import org.junit.runner.RunWith
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
class NetworkExceptionTest {
@Test
diff --git a/Cronet/tests/cts/src/android/net/http/cts/QuicExceptionTest.kt b/Cronet/tests/cts/src/android/net/http/cts/QuicExceptionTest.kt
index 4b7aa14..2705fc3 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/QuicExceptionTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/QuicExceptionTest.kt
@@ -17,12 +17,15 @@
package android.net.http.cts
import android.net.http.QuicException
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.os.Build
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
import kotlin.test.Test
import kotlin.test.assertEquals
import org.junit.runner.RunWith
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
class QuicExceptionTest {
@Test
diff --git a/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt b/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt
index 0b02aa7..da0b15c 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt
@@ -16,7 +16,9 @@
package android.net.http.cts
import android.net.http.QuicOptions
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.os.Build
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
import com.google.common.truth.Truth.assertThat
import java.time.Duration
import kotlin.test.assertFailsWith
@@ -25,7 +27,8 @@
import org.junit.Test
import org.junit.runner.RunWith
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
class QuicOptionsTest {
@Test
fun testQuicOptions_defaultValues() {
diff --git a/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java b/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
index 422f4d5..07e7d45 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
@@ -42,12 +42,15 @@
import android.net.http.cts.util.TestUrlRequestCallback;
import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep;
import android.net.http.cts.util.UploadDataProviders;
+import android.os.Build;
import android.webkit.cts.CtsTestServer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import com.google.common.base.Strings;
@@ -70,7 +73,8 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
public class UrlRequestTest {
private static final Executor DIRECT_EXECUTOR = Runnable::run;
diff --git a/Cronet/tests/cts/src/android/net/http/cts/UrlResponseInfoTest.kt b/Cronet/tests/cts/src/android/net/http/cts/UrlResponseInfoTest.kt
index 38da9c5..f1b57c6 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/UrlResponseInfoTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/UrlResponseInfoTest.kt
@@ -21,15 +21,18 @@
import android.net.http.cts.util.HttpCtsTestServer
import android.net.http.cts.util.TestUrlRequestCallback
import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep
+import android.os.Build
import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import org.junit.runner.RunWith
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
class UrlResponseInfoTest {
@Test
diff --git a/TEST_MAPPING b/TEST_MAPPING
index c1d6a89..0326bf2 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -226,7 +226,12 @@
]
},
{
- "name": "CtsTetheringTestLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+ "name": "CtsTetheringTestLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.NetworkStackModuleTest"
+ }
+ ]
}
],
"mainline-postsubmit": [
@@ -236,8 +241,13 @@
"keywords": ["sim"]
},
{
- "name": "CtsTetheringTestLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
- "keywords": ["sim"]
+ "name": "CtsTetheringTestLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "keywords": ["sim"],
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.NetworkStackModuleTest"
+ }
+ ]
}
],
"imports": [
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 4ee5c42..5d57aa5 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -65,6 +65,7 @@
import com.android.net.module.util.structs.UdpHeader;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.NetworkStackModuleTest;
import com.android.testutils.TapPacketReader;
import org.junit.Rule;
@@ -860,6 +861,8 @@
(byte) 0x00, (byte) 0x08, (byte) 0x3a, (byte) 0xdf
};
+ // This test requires the update in NetworkStackModule(See b/269692093).
+ @NetworkStackModuleTest
@Test
public void testTetherZeroLengthDhcpPacket() throws Exception {
final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 6409374..3984249 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -19,6 +19,7 @@
#include "BpfHandler.h"
#include <linux/bpf.h>
+#include <inttypes.h>
#include <android-base/unique_fd.h>
#include <android-modules-utils/sdk_level.h>
@@ -246,6 +247,8 @@
mCookieTagMap.getMap().get());
return -res.error().code();
}
+ ALOGD("Socket with cookie %" PRIu64 " tagged successfully with tag %" PRIu32 " uid %u "
+ "and real uid %u", sock_cookie, tag, chargeUid, realUid);
return 0;
}
@@ -259,6 +262,7 @@
ALOGE("Failed to untag socket: %s", strerror(res.error().code()));
return -res.error().code();
}
+ ALOGD("Socket with cookie %" PRIu64 " untagged successfully.", sock_cookie);
return 0;
}
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 47a1022..25aa693 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -17,6 +17,8 @@
package com.android.server;
import static android.net.ConnectivityManager.NETID_UNSET;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.nsd.NsdManager.MDNS_DISCOVERY_MANAGER_EVENT;
import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT;
import static android.net.nsd.NsdManager.RESOLVE_SERVICE_SUCCEEDED;
@@ -27,6 +29,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
@@ -45,6 +48,7 @@
import android.net.nsd.MDnsManager;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
@@ -53,7 +57,9 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -62,6 +68,7 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
+import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.InetAddressUtils;
import com.android.net.module.util.PermissionUtils;
@@ -69,6 +76,7 @@
import com.android.server.connectivity.mdns.ExecutorProvider;
import com.android.server.connectivity.mdns.MdnsAdvertiser;
import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
+import com.android.server.connectivity.mdns.MdnsInterfaceSocket;
import com.android.server.connectivity.mdns.MdnsMultinetworkSocketClient;
import com.android.server.connectivity.mdns.MdnsSearchOptions;
import com.android.server.connectivity.mdns.MdnsServiceBrowserListener;
@@ -141,6 +149,14 @@
"mdns_advertiser_allowlist_";
private static final String MDNS_ALLOWLIST_FLAG_SUFFIX = "_version";
+ @VisibleForTesting
+ static final String MDNS_CONFIG_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF =
+ "mdns_config_running_app_active_importance_cutoff";
+ @VisibleForTesting
+ static final int DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF =
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+ private final int mRunningAppActiveImportanceCutoff;
+
public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private static final long CLEANUP_DELAY_MS = 10000;
private static final int IFACE_IDX_ANY = 0;
@@ -175,6 +191,16 @@
/* A map from unique id to client info */
private final SparseArray<ClientInfo> mIdToClientInfoMap= new SparseArray<>();
+ // Note this is not final to avoid depending on the Wi-Fi service starting before NsdService
+ @Nullable
+ private WifiManager.MulticastLock mHeldMulticastLock;
+ // Fulfilled network requests that require the Wi-Fi lock: key is the obtained Network
+ // (non-null), value is the requested Network (nullable)
+ @NonNull
+ private final ArraySet<Network> mWifiLockRequiredNetworks = new ArraySet<>();
+ @NonNull
+ private final ArraySet<Integer> mRunningAppActiveUids = new ArraySet<>();
+
private final long mCleanupDelayMs;
private static final int INVALID_ID = 0;
@@ -299,6 +325,104 @@
}
}
+ private class SocketRequestMonitor implements MdnsSocketProvider.SocketRequestMonitor {
+ @Override
+ public void onSocketRequestFulfilled(@Nullable Network socketNetwork,
+ @NonNull MdnsInterfaceSocket socket, @NonNull int[] transports) {
+ // The network may be null for Wi-Fi SoftAp interfaces (tethering), but there is no APF
+ // filtering on such interfaces, so taking the multicast lock is not necessary to
+ // disable APF filtering of multicast.
+ if (socketNetwork == null
+ || !CollectionUtils.contains(transports, TRANSPORT_WIFI)
+ || CollectionUtils.contains(transports, TRANSPORT_VPN)) {
+ return;
+ }
+
+ if (mWifiLockRequiredNetworks.add(socketNetwork)) {
+ updateMulticastLock();
+ }
+ }
+
+ @Override
+ public void onSocketDestroyed(@Nullable Network socketNetwork,
+ @NonNull MdnsInterfaceSocket socket) {
+ if (mWifiLockRequiredNetworks.remove(socketNetwork)) {
+ updateMulticastLock();
+ }
+ }
+ }
+
+ private class UidImportanceListener implements ActivityManager.OnUidImportanceListener {
+ private final Handler mHandler;
+
+ private UidImportanceListener(Handler handler) {
+ mHandler = handler;
+ }
+
+ @Override
+ public void onUidImportance(int uid, int importance) {
+ mHandler.post(() -> handleUidImportanceChanged(uid, importance));
+ }
+ }
+
+ private void handleUidImportanceChanged(int uid, int importance) {
+ // Lower importance values are more "important"
+ final boolean modified = importance <= mRunningAppActiveImportanceCutoff
+ ? mRunningAppActiveUids.add(uid)
+ : mRunningAppActiveUids.remove(uid);
+ if (modified) {
+ updateMulticastLock();
+ }
+ }
+
+ /**
+ * Take or release the lock based on updated internal state.
+ *
+ * This determines whether the lock needs to be held based on
+ * {@link #mWifiLockRequiredNetworks}, {@link #mRunningAppActiveUids} and
+ * {@link ClientInfo#mClientRequests}, so it must be called after any of the these have been
+ * updated.
+ */
+ private void updateMulticastLock() {
+ final int needsLockUid = getMulticastLockNeededUid();
+ if (needsLockUid >= 0 && mHeldMulticastLock == null) {
+ final WifiManager wm = mContext.getSystemService(WifiManager.class);
+ if (wm == null) {
+ Log.wtf(TAG, "Got a TRANSPORT_WIFI network without WifiManager");
+ return;
+ }
+ mHeldMulticastLock = wm.createMulticastLock(TAG);
+ mHeldMulticastLock.acquire();
+ mServiceLogs.log("Taking multicast lock for uid " + needsLockUid);
+ } else if (needsLockUid < 0 && mHeldMulticastLock != null) {
+ mHeldMulticastLock.release();
+ mHeldMulticastLock = null;
+ mServiceLogs.log("Released multicast lock");
+ }
+ }
+
+ /**
+ * @return The UID of an app requiring the multicast lock, or -1 if none.
+ */
+ private int getMulticastLockNeededUid() {
+ if (mWifiLockRequiredNetworks.size() == 0) {
+ // Return early if NSD is not active, or not on any relevant network
+ return -1;
+ }
+ for (int i = 0; i < mIdToClientInfoMap.size(); i++) {
+ final ClientInfo clientInfo = mIdToClientInfoMap.valueAt(i);
+ if (!mRunningAppActiveUids.contains(clientInfo.mUid)) {
+ // Ignore non-active UIDs
+ continue;
+ }
+
+ if (clientInfo.hasAnyJavaBackendRequestForNetworks(mWifiLockRequiredNetworks)) {
+ return clientInfo.mUid;
+ }
+ }
+ return -1;
+ }
+
/**
* Data class of mdns service callback information.
*/
@@ -404,7 +528,7 @@
try {
cb.asBinder().linkToDeath(arg.connector, 0);
final String tag = "Client" + arg.uid + "-" + mClientNumberId++;
- cInfo = new ClientInfo(cb, arg.useJavaBackend,
+ cInfo = new ClientInfo(cb, arg.uid, arg.useJavaBackend,
mServiceLogs.forSubComponent(tag));
mClients.put(arg.connector, cInfo);
} catch (RemoteException e) {
@@ -529,9 +653,11 @@
}
private void storeAdvertiserRequestMap(int clientId, int globalId,
- ClientInfo clientInfo) {
- clientInfo.mClientRequests.put(clientId, new AdvertiserClientRequest(globalId));
+ ClientInfo clientInfo, @Nullable Network requestedNetwork) {
+ clientInfo.mClientRequests.put(clientId,
+ new AdvertiserClientRequest(globalId, requestedNetwork));
mIdToClientInfoMap.put(globalId, clientInfo);
+ updateMulticastLock();
}
private void removeRequestMap(int clientId, int globalId, ClientInfo clientInfo) {
@@ -544,14 +670,17 @@
maybeScheduleStop();
} else {
maybeStopMonitoringSocketsIfNoActiveRequest();
+ updateMulticastLock();
}
}
private void storeDiscoveryManagerRequestMap(int clientId, int globalId,
- MdnsListener listener, ClientInfo clientInfo) {
+ MdnsListener listener, ClientInfo clientInfo,
+ @Nullable Network requestedNetwork) {
clientInfo.mClientRequests.put(clientId,
- new DiscoveryManagerRequest(globalId, listener));
+ new DiscoveryManagerRequest(globalId, listener, requestedNetwork));
mIdToClientInfoMap.put(globalId, clientInfo);
+ updateMulticastLock();
}
/**
@@ -628,7 +757,8 @@
}
mMdnsDiscoveryManager.registerListener(
listenServiceType, listener, optionsBuilder.build());
- storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo);
+ storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo,
+ info.getNetwork());
clientInfo.onDiscoverServicesStarted(clientId, info);
clientInfo.log("Register a DiscoveryListener " + id
+ " for service type:" + listenServiceType);
@@ -728,7 +858,8 @@
// Name._subtype._sub._type._tcp, which is incorrect
// (it should be Name._type._tcp).
mAdvertiser.addService(id, serviceInfo, typeSubtype.second);
- storeAdvertiserRequestMap(clientId, id, clientInfo);
+ storeAdvertiserRequestMap(clientId, id, clientInfo,
+ serviceInfo.getNetwork());
} else {
maybeStartDaemon();
if (registerService(id, serviceInfo)) {
@@ -818,7 +949,8 @@
.build();
mMdnsDiscoveryManager.registerListener(
resolveServiceType, listener, options);
- storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo);
+ storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo,
+ info.getNetwork());
clientInfo.log("Register a ResolutionListener " + id
+ " for service type:" + resolveServiceType);
} else {
@@ -912,7 +1044,8 @@
.build();
mMdnsDiscoveryManager.registerListener(
resolveServiceType, listener, options);
- storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo);
+ storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo,
+ info.getNetwork());
clientInfo.log("Register a ServiceInfoListener " + id
+ " for service type:" + resolveServiceType);
break;
@@ -1389,10 +1522,20 @@
mDeps = deps;
mMdnsSocketProvider = deps.makeMdnsSocketProvider(ctx, handler.getLooper(),
- LOGGER.forSubComponent("MdnsSocketProvider"));
+ LOGGER.forSubComponent("MdnsSocketProvider"), new SocketRequestMonitor());
// Netlink monitor starts on boot, and intentionally never stopped, to ensure that all
// address events are received.
handler.post(mMdnsSocketProvider::startNetLinkMonitor);
+
+ // NsdService is started after ActivityManager (startOtherServices in SystemServer, vs.
+ // startBootstrapServices).
+ mRunningAppActiveImportanceCutoff = mDeps.getDeviceConfigInt(
+ MDNS_CONFIG_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF,
+ DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF);
+ final ActivityManager am = ctx.getSystemService(ActivityManager.class);
+ am.addOnUidImportanceListener(new UidImportanceListener(handler),
+ mRunningAppActiveImportanceCutoff);
+
mMdnsSocketClient =
new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider);
mMdnsDiscoveryManager = deps.makeMdnsDiscoveryManager(new ExecutorProvider(),
@@ -1471,8 +1614,23 @@
* @see MdnsSocketProvider
*/
public MdnsSocketProvider makeMdnsSocketProvider(@NonNull Context context,
- @NonNull Looper looper, @NonNull SharedLog sharedLog) {
- return new MdnsSocketProvider(context, looper, sharedLog);
+ @NonNull Looper looper, @NonNull SharedLog sharedLog,
+ @NonNull MdnsSocketProvider.SocketRequestMonitor socketCreationCallback) {
+ return new MdnsSocketProvider(context, looper, sharedLog, socketCreationCallback);
+ }
+
+ /**
+ * @see DeviceConfig#getInt(String, String, int)
+ */
+ public int getDeviceConfigInt(@NonNull String config, int defaultValue) {
+ return DeviceConfig.getInt(NAMESPACE_TETHERING, config, defaultValue);
+ }
+
+ /**
+ * @see Binder#getCallingUid()
+ */
+ public int getCallingUid() {
+ return Binder.getCallingUid();
}
}
@@ -1626,7 +1784,7 @@
final INsdServiceConnector connector = new NsdServiceConnector();
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(NsdManager.REGISTER_CLIENT,
new ConnectorArgs((NsdServiceConnector) connector, cb, useJavaBackend,
- Binder.getCallingUid())));
+ mDeps.getCallingUid())));
return connector;
}
@@ -1857,18 +2015,34 @@
}
}
- private static class AdvertiserClientRequest extends ClientRequest {
- private AdvertiserClientRequest(int globalId) {
+ private abstract static class JavaBackendClientRequest extends ClientRequest {
+ @Nullable
+ private final Network mRequestedNetwork;
+
+ private JavaBackendClientRequest(int globalId, @Nullable Network requestedNetwork) {
super(globalId);
+ mRequestedNetwork = requestedNetwork;
+ }
+
+ @Nullable
+ public Network getRequestedNetwork() {
+ return mRequestedNetwork;
}
}
- private static class DiscoveryManagerRequest extends ClientRequest {
+ private static class AdvertiserClientRequest extends JavaBackendClientRequest {
+ private AdvertiserClientRequest(int globalId, @Nullable Network requestedNetwork) {
+ super(globalId, requestedNetwork);
+ }
+ }
+
+ private static class DiscoveryManagerRequest extends JavaBackendClientRequest {
@NonNull
private final MdnsListener mListener;
- private DiscoveryManagerRequest(int globalId, @NonNull MdnsListener listener) {
- super(globalId);
+ private DiscoveryManagerRequest(int globalId, @NonNull MdnsListener listener,
+ @Nullable Network requestedNetwork) {
+ super(globalId, requestedNetwork);
mListener = listener;
}
}
@@ -1886,13 +2060,16 @@
// The target SDK of this client < Build.VERSION_CODES.S
private boolean mIsPreSClient = false;
+ private final int mUid;
// The flag of using java backend if the client's target SDK >= U
private final boolean mUseJavaBackend;
// Store client logs
private final SharedLog mClientLogs;
- private ClientInfo(INsdManagerCallback cb, boolean useJavaBackend, SharedLog sharedLog) {
+ private ClientInfo(INsdManagerCallback cb, int uid, boolean useJavaBackend,
+ SharedLog sharedLog) {
mCb = cb;
+ mUid = uid;
mUseJavaBackend = useJavaBackend;
mClientLogs = sharedLog;
mClientLogs.log("New client. useJavaBackend=" + useJavaBackend);
@@ -1903,6 +2080,8 @@
StringBuilder sb = new StringBuilder();
sb.append("mResolvedService ").append(mResolvedService).append("\n");
sb.append("mIsLegacy ").append(mIsPreSClient).append("\n");
+ sb.append("mUseJavaBackend ").append(mUseJavaBackend).append("\n");
+ sb.append("mUid ").append(mUid).append("\n");
for (int i = 0; i < mClientRequests.size(); i++) {
int clientID = mClientRequests.keyAt(i);
sb.append("clientId ")
@@ -1974,6 +2153,26 @@
}
}
mClientRequests.clear();
+ updateMulticastLock();
+ }
+
+ /**
+ * Returns true if this client has any Java backend request that requests one of the given
+ * networks.
+ */
+ boolean hasAnyJavaBackendRequestForNetworks(@NonNull ArraySet<Network> networks) {
+ for (int i = 0; i < mClientRequests.size(); i++) {
+ final ClientRequest req = mClientRequests.valueAt(i);
+ if (!(req instanceof JavaBackendClientRequest)) {
+ continue;
+ }
+ final Network reqNetwork = ((JavaBackendClientRequest) mClientRequests.valueAt(i))
+ .getRequestedNetwork();
+ if (MdnsUtils.isAnyNetworkMatched(reqNetwork, networks)) {
+ return true;
+ }
+ }
+ return false;
}
// mClientRequests is a sparse array of listener id -> ClientRequest. For a given
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..d90f67f 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.
@@ -90,21 +97,80 @@
// the netlink monitor is never stop and the old states must be kept.
private final SparseArray<LinkProperties> mIfaceIdxToLinkProperties = new SparseArray<>();
private final byte[] mPacketReadBuffer = new byte[READ_BUFFER_SIZE];
+ @NonNull
+ private final SocketRequestMonitor mSocketRequestMonitor;
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) {
- this(context, looper, new Dependencies(), sharedLog);
+ @NonNull SharedLog sharedLog,
+ @NonNull SocketRequestMonitor socketRequestMonitor) {
+ this(context, looper, new Dependencies(), sharedLog, socketRequestMonitor);
}
MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper,
- @NonNull Dependencies deps, @NonNull SharedLog sharedLog) {
+ @NonNull Dependencies deps, @NonNull SharedLog sharedLog,
+ @NonNull SocketRequestMonitor socketRequestMonitor) {
mContext = context;
mLooper = looper;
mHandler = new Handler(looper);
mDependencies = deps;
mSharedLog = sharedLog;
+ mSocketRequestMonitor = socketRequestMonitor;
mNetworkCallback = new NetworkCallback() {
@Override
public void onLost(Network network) {
@@ -138,6 +204,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);
}
/**
@@ -239,10 +317,12 @@
private static class SocketInfo {
final MdnsInterfaceSocket mSocket;
final List<LinkAddress> mAddresses;
+ final int[] mTransports;
- SocketInfo(MdnsInterfaceSocket socket, List<LinkAddress> addresses) {
+ SocketInfo(MdnsInterfaceSocket socket, List<LinkAddress> addresses, int[] transports) {
mSocket = socket;
mAddresses = new ArrayList<>(addresses);
+ mTransports = transports;
}
}
@@ -376,17 +456,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));
}
@@ -414,9 +505,13 @@
if (networkKey == LOCAL_NET) {
transports = new int[0];
} else {
- transports = mActiveNetworksTransports.get(((NetworkAsKey) networkKey).mNetwork);
- if (transports == null) {
+ final int[] knownTransports =
+ mActiveNetworksTransports.get(((NetworkAsKey) networkKey).mNetwork);
+ if (knownTransports != null) {
+ transports = knownTransports;
+ } else {
Log.wtf(TAG, "transports is missing for key: " + networkKey);
+ transports = new int[0];
}
}
if (networkInterface == null || !isMdnsCapableInterface(networkInterface, transports)) {
@@ -428,21 +523,22 @@
networkInterface.getNetworkInterface(), MdnsConstants.MDNS_PORT, mLooper,
mPacketReadBuffer);
final List<LinkAddress> addresses = lp.getLinkAddresses();
+ // TODO: technically transport types are mutable, although generally not in ways that
+ // would meaningfully impact the logic using it here. Consider updating logic to
+ // support transports being added/removed.
+ final SocketInfo socketInfo = new SocketInfo(socket, addresses, transports);
if (networkKey == LOCAL_NET) {
- mTetherInterfaceSockets.put(interfaceName, new SocketInfo(socket, addresses));
+ mTetherInterfaceSockets.put(interfaceName, socketInfo);
} else {
- mNetworkSockets.put(((NetworkAsKey) networkKey).mNetwork,
- new SocketInfo(socket, addresses));
+ mNetworkSockets.put(((NetworkAsKey) networkKey).mNetwork, socketInfo);
}
// Try to join IPv4/IPv6 group.
socket.joinGroup(addresses);
// Notify the listeners which need this socket.
- if (networkKey == LOCAL_NET) {
- notifySocketCreated(null /* network */, socket, addresses);
- } else {
- notifySocketCreated(((NetworkAsKey) networkKey).mNetwork, socket, addresses);
- }
+ final Network network =
+ networkKey == LOCAL_NET ? null : ((NetworkAsKey) networkKey).mNetwork;
+ notifySocketCreated(network, socketInfo);
} catch (IOException e) {
mSharedLog.e("Create socket failed ifName:" + interfaceName, e);
}
@@ -484,6 +580,7 @@
socketInfo.mSocket.destroy();
notifyInterfaceDestroyed(network, socketInfo.mSocket);
+ mSocketRequestMonitor.onSocketDestroyed(network, socketInfo.mSocket);
mSharedLog.log("Remove socket on net:" + network);
}
@@ -492,15 +589,18 @@
if (socketInfo == null) return;
socketInfo.mSocket.destroy();
notifyInterfaceDestroyed(null /* network */, socketInfo.mSocket);
+ mSocketRequestMonitor.onSocketDestroyed(null /* network */, socketInfo.mSocket);
mSharedLog.log("Remove socket on ifName:" + interfaceName);
}
- private void notifySocketCreated(Network network, MdnsInterfaceSocket socket,
- List<LinkAddress> addresses) {
+ private void notifySocketCreated(Network network, SocketInfo socketInfo) {
for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i);
if (isNetworkMatched(requestedNetwork, network)) {
- mCallbacksToRequestedNetworks.keyAt(i).onSocketCreated(network, socket, addresses);
+ mCallbacksToRequestedNetworks.keyAt(i).onSocketCreated(network, socketInfo.mSocket,
+ socketInfo.mAddresses);
+ mSocketRequestMonitor.onSocketRequestFulfilled(network, socketInfo.mSocket,
+ socketInfo.mTransports);
}
}
}
@@ -538,6 +638,8 @@
} else {
// Notify the socket for requested network.
cb.onSocketCreated(network, socketInfo.mSocket, socketInfo.mAddresses);
+ mSocketRequestMonitor.onSocketRequestFulfilled(network, socketInfo.mSocket,
+ socketInfo.mTransports);
}
}
@@ -552,6 +654,8 @@
// Notify the socket for requested network.
cb.onSocketCreated(
null /* network */, socketInfo.mSocket, socketInfo.mAddresses);
+ mSocketRequestMonitor.onSocketRequestFulfilled(null /* socketNetwork */,
+ socketInfo.mSocket, socketInfo.mTransports);
}
}
@@ -580,6 +684,11 @@
for (String tetheredInterface : mTetheredInterfaces) {
retrieveAndNotifySocketFromInterface(tetheredInterface, cb);
}
+
+ if (mWifiP2pTetherInterface != null
+ && !mLocalOnlyInterfaces.contains(mWifiP2pTetherInterface)) {
+ retrieveAndNotifySocketFromInterface(mWifiP2pTetherInterface, cb);
+ }
} else {
retrieveAndNotifySocketFromNetwork(network, cb);
}
@@ -601,6 +710,7 @@
if (matchRequestedNetwork(network)) continue;
final SocketInfo info = mNetworkSockets.removeAt(i);
info.mSocket.destroy();
+ mSocketRequestMonitor.onSocketDestroyed(network, info.mSocket);
mSharedLog.log("Remove socket on net:" + network + " after unrequestSocket");
}
@@ -610,6 +720,7 @@
for (int i = mTetherInterfaceSockets.size() - 1; i >= 0; i--) {
final SocketInfo info = mTetherInterfaceSockets.valueAt(i);
info.mSocket.destroy();
+ mSocketRequestMonitor.onSocketDestroyed(null /* network */, info.mSocket);
mSharedLog.log("Remove socket on ifName:" + mTetherInterfaceSockets.keyAt(i)
+ " after unrequestSocket");
}
@@ -620,19 +731,61 @@
}
- /*** Callbacks for listening socket changes */
+ /**
+ * Callback used to register socket requests.
+ */
public interface SocketCallback {
- /*** Notify the socket is created */
+ /**
+ * Notify the socket was created for the registered request.
+ *
+ * This may be called immediately when the request is registered with an existing socket,
+ * if it had been created previously for other requests.
+ */
default void onSocketCreated(@Nullable Network network, @NonNull MdnsInterfaceSocket socket,
@NonNull List<LinkAddress> addresses) {}
- /*** Notify the interface is destroyed */
+
+ /**
+ * Notify that the interface was destroyed, so the provided socket cannot be used anymore.
+ *
+ * This indicates that although the socket was still requested, it had to be destroyed.
+ */
default void onInterfaceDestroyed(@Nullable Network network,
@NonNull MdnsInterfaceSocket socket) {}
- /*** Notify the addresses is changed on the network */
+
+ /**
+ * Notify the interface addresses have changed for the network.
+ */
default void onAddressesChanged(@Nullable Network network,
@NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses) {}
}
+ /**
+ * Global callback indicating when sockets are created or destroyed for requests.
+ */
+ public interface SocketRequestMonitor {
+ /**
+ * Indicates that the socket was used to fulfill the request of one requester.
+ *
+ * There is always at most one socket created for each interface. The interface is available
+ * in {@link MdnsInterfaceSocket#getInterface()}.
+ * @param socketNetwork The network of the socket interface, if any.
+ * @param socket The socket that was provided to a requester.
+ * @param transports Array of TRANSPORT_* from {@link NetworkCapabilities}. Empty if the
+ * interface is not part of a network with known transports.
+ */
+ default void onSocketRequestFulfilled(@Nullable Network socketNetwork,
+ @NonNull MdnsInterfaceSocket socket, @NonNull int[] transports) {}
+
+ /**
+ * Indicates that a previously created socket was destroyed.
+ *
+ * @param socketNetwork The network of the socket interface, if any.
+ * @param socket The destroyed socket.
+ */
+ default void onSocketDestroyed(@Nullable Network socketNetwork,
+ @NonNull MdnsInterfaceSocket socket) {}
+ }
+
private interface NetworkKey {
}
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 eb12b9a..3180a6f 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
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.net.Network;
import android.os.Handler;
+import android.util.ArraySet;
import com.android.server.connectivity.mdns.MdnsConstants;
import com.android.server.connectivity.mdns.MdnsRecord;
@@ -129,12 +130,21 @@
return false;
}
- /*** Check whether the target network is matched current network */
+ /*** Check whether the target network matches the current network */
public static boolean isNetworkMatched(@Nullable Network targetNetwork,
@Nullable Network currentNetwork) {
return targetNetwork == null || targetNetwork.equals(currentNetwork);
}
+ /*** Check whether the target network matches any of the current networks */
+ public static boolean isAnyNetworkMatched(@Nullable Network targetNetwork,
+ ArraySet<Network> currentNetworks) {
+ if (targetNetwork == null) {
+ return !currentNetworks.isEmpty();
+ }
+ return currentNetworks.contains(targetNetwork);
+ }
+
/**
* Truncate a service name to up to maxLength UTF-8 bytes.
*/
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index c95295c..fad6aaa 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -317,6 +317,7 @@
import java.io.InterruptedIOException;
import java.io.PrintWriter;
import java.io.Writer;
+import java.lang.IllegalArgumentException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -441,6 +442,8 @@
private final Context mContext;
private final ConnectivityResources mResources;
+ private final int mWakeUpMark;
+ private final int mWakeUpMask;
// The Context is created for UserHandle.ALL.
private final Context mUserAllContext;
private final Dependencies mDeps;
@@ -1610,6 +1613,29 @@
mCellularRadioTimesharingCapable =
mResources.get().getBoolean(R.bool.config_cellular_radio_timesharing_capable);
+ int mark = mResources.get().getInteger(R.integer.config_networkWakeupPacketMark);
+ int mask = mResources.get().getInteger(R.integer.config_networkWakeupPacketMask);
+
+ if (SdkLevel.isAtLeastU()) {
+ // U+ default value of both mark & mask, this is the top bit of the skb->mark,
+ // see //system/netd/include/FwMark.h union Fwmark, field ingress_cpu_wakeup
+ final int defaultUMarkMask = 0x80000000; // u32
+
+ if ((mark == 0) || (mask == 0)) {
+ // simply treat unset/disabled as the default U value
+ mark = defaultUMarkMask;
+ mask = defaultUMarkMask;
+ }
+ if ((mark != defaultUMarkMask) || (mask != defaultUMarkMask)) {
+ // invalid device overlay settings
+ throw new IllegalArgumentException(
+ "Bad config_networkWakeupPacketMark/Mask " + mark + "/" + mask);
+ }
+ }
+
+ mWakeUpMark = mark;
+ mWakeUpMask = mask;
+
mNetd = netd;
mBpfNetMaps = mDeps.getBpfNetMaps(mContext, netd);
mHandlerThread = mDeps.makeHandlerThread();
@@ -8088,21 +8114,18 @@
return;
}
- int mark = mResources.get().getInteger(R.integer.config_networkWakeupPacketMark);
- int mask = mResources.get().getInteger(R.integer.config_networkWakeupPacketMask);
-
// Mask/mark of zero will not detect anything interesting.
// Don't install rules unless both values are nonzero.
- if (mark == 0 || mask == 0) {
+ if (mWakeUpMark == 0 || mWakeUpMask == 0) {
return;
}
final String prefix = makeNflogPrefix(iface, nai.network.getNetworkHandle());
try {
if (add) {
- mNetd.wakeupAddInterface(iface, prefix, mark, mask);
+ mNetd.wakeupAddInterface(iface, prefix, mWakeUpMark, mWakeUpMask);
} else {
- mNetd.wakeupDelInterface(iface, prefix, mark, mask);
+ mNetd.wakeupDelInterface(iface, prefix, mWakeUpMark, mWakeUpMask);
}
} catch (Exception e) {
loge("Exception modifying wakeup packet monitoring: " + e);
@@ -8620,10 +8643,18 @@
}
private void maybeCloseSockets(NetworkAgentInfo nai, Set<UidRange> ranges,
- Set<Integer> exemptUids) {
+ UidRangeParcel[] uidRangeParcels, int[] exemptUids) {
if (nai.isVPN() && !nai.networkAgentConfig.allowBypass) {
try {
- mDeps.destroyLiveTcpSockets(UidRange.toIntRanges(ranges), exemptUids);
+ if (mDeps.isAtLeastU()) {
+ final Set<Integer> exemptUidSet = new ArraySet<>();
+ for (final int uid: exemptUids) {
+ exemptUidSet.add(uid);
+ }
+ mDeps.destroyLiveTcpSockets(UidRange.toIntRanges(ranges), exemptUidSet);
+ } else {
+ mNetd.socketDestroy(uidRangeParcels, exemptUids);
+ }
} catch (Exception e) {
loge("Exception in socket destroy: ", e);
}
@@ -8631,16 +8662,16 @@
}
private void updateVpnUidRanges(boolean add, NetworkAgentInfo nai, Set<UidRange> uidRanges) {
- final Set<Integer> exemptUids = new ArraySet<>();
+ int[] exemptUids = new int[2];
// TODO: Excluding VPN_UID is necessary in order to not to kill the TCP connection used
// by PPTP. Fix this by making Vpn set the owner UID to VPN_UID instead of system when
// starting a legacy VPN, and remove VPN_UID here. (b/176542831)
- exemptUids.add(VPN_UID);
- exemptUids.add(nai.networkCapabilities.getOwnerUid());
+ exemptUids[0] = VPN_UID;
+ exemptUids[1] = nai.networkCapabilities.getOwnerUid();
UidRangeParcel[] ranges = toUidRangeStableParcels(uidRanges);
// Close sockets before modifying uid ranges so that RST packets can reach to the server.
- maybeCloseSockets(nai, uidRanges, exemptUids);
+ maybeCloseSockets(nai, uidRanges, ranges, exemptUids);
try {
if (add) {
mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
@@ -8654,7 +8685,7 @@
" on netId " + nai.network.netId + ". " + e);
}
// Close sockets that established connection while requesting netd.
- maybeCloseSockets(nai, uidRanges, exemptUids);
+ maybeCloseSockets(nai, uidRanges, ranges, exemptUids);
}
private boolean isProxySetOnAnyDefaultNetwork() {
diff --git a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
index f8285ed..62d79a3 100644
--- a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
@@ -18,6 +18,7 @@
import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
import static android.net.SocketKeepalive.MIN_INTERVAL_SEC;
+import static android.net.SocketKeepalive.SUCCESS;
import static android.net.SocketKeepalive.SUCCESS_PAUSED;
import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
import static android.system.OsConstants.AF_INET;
@@ -52,6 +53,7 @@
import android.system.StructTimeval;
import android.util.LocalLog;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -381,7 +383,11 @@
return;
}
autoKi.mAutomaticOnOffState = STATE_ENABLED;
- handleResumeKeepalive(newKi);
+ final int error = handleResumeKeepalive(newKi);
+ if (error != SUCCESS) {
+ // Failed to start the keepalive
+ cleanupAutoOnOffKeepalive(autoKi);
+ }
}
/**
@@ -402,7 +408,20 @@
* Forward to KeepaliveTracker.
*/
public void handleEventSocketKeepalive(@NonNull NetworkAgentInfo nai, int slot, int reason) {
- mKeepaliveTracker.handleEventSocketKeepalive(nai, slot, reason);
+ if (mKeepaliveTracker.handleEventSocketKeepalive(nai, slot, reason)) return;
+
+ // The keepalive was stopped and so the autoKi should be cleaned up.
+ final AutomaticOnOffKeepalive autoKi =
+ CollectionUtils.findFirst(
+ mAutomaticOnOffKeepalives, it -> it.match(nai.network(), slot));
+ if (autoKi == null) {
+ // This may occur when the autoKi gets cleaned up elsewhere (i.e
+ // handleCheckKeepalivesStillValid) while waiting for the network agent to
+ // start the keepalive and the network agent returns an error event.
+ Log.e(TAG, "Attempt cleanup on unknown network, slot");
+ return;
+ }
+ cleanupAutoOnOffKeepalive(autoKi);
}
/**
@@ -414,6 +433,9 @@
final List<AutomaticOnOffKeepalive> matches =
CollectionUtils.filter(mAutomaticOnOffKeepalives, it -> it.mKi.getNai() == nai);
for (final AutomaticOnOffKeepalive ki : matches) {
+ if (ki.mAutomaticOnOffState == STATE_SUSPENDED) {
+ mKeepaliveTracker.finalizePausedKeepalive(ki.mKi, reason);
+ }
cleanupAutoOnOffKeepalive(ki);
}
}
@@ -425,9 +447,14 @@
*/
public void handleStartKeepalive(Message message) {
final AutomaticOnOffKeepalive autoKi = (AutomaticOnOffKeepalive) message.obj;
+ final int error = mKeepaliveTracker.handleStartKeepalive(autoKi.mKi);
+ if (error != SUCCESS) {
+ mEventLog.log("Failed to start keepalive " + autoKi.mCallback + " on "
+ + autoKi.getNetwork() + " with error " + error);
+ return;
+ }
mEventLog.log("Start keepalive " + autoKi.mCallback + " on " + autoKi.getNetwork());
mKeepaliveStatsTracker.onStartKeepalive();
- mKeepaliveTracker.handleStartKeepalive(autoKi.mKi);
// Add automatic on/off request into list to track its life cycle.
try {
@@ -443,10 +470,22 @@
}
}
- private void handleResumeKeepalive(@NonNull final KeepaliveTracker.KeepaliveInfo ki) {
+ /**
+ * Handle resume keepalive with the given KeepaliveInfo
+ *
+ * @return SUCCESS if the keepalive is successfully starting and the error reason otherwise.
+ */
+ private int handleResumeKeepalive(@NonNull final KeepaliveTracker.KeepaliveInfo ki) {
+ final int error = mKeepaliveTracker.handleStartKeepalive(ki);
+ if (error != SUCCESS) {
+ mEventLog.log("Failed to resume keepalive " + ki.mCallback + " on " + ki.mNai
+ + " with error " + error);
+ return error;
+ }
mKeepaliveStatsTracker.onResumeKeepalive();
- mKeepaliveTracker.handleStartKeepalive(ki);
mEventLog.log("Resumed successfully keepalive " + ki.mCallback + " on " + ki.mNai);
+
+ return SUCCESS;
}
private void handlePauseKeepalive(@NonNull final KeepaliveTracker.KeepaliveInfo ki) {
@@ -467,7 +506,7 @@
final KeepaliveTracker.KeepaliveInfo ki = autoKi.mKi;
mKeepaliveTracker.handleStopKeepalive(ki.getNai(), ki.getSlot(), reason);
} else {
- mKeepaliveTracker.finalizePausedKeepalive(autoKi.mKi);
+ mKeepaliveTracker.finalizePausedKeepalive(autoKi.mKi, reason);
}
cleanupAutoOnOffKeepalive(autoKi);
@@ -612,7 +651,22 @@
* Forward to KeepaliveTracker.
*/
public void handleCheckKeepalivesStillValid(NetworkAgentInfo nai) {
- mKeepaliveTracker.handleCheckKeepalivesStillValid(nai);
+ ArrayList<Pair<AutomaticOnOffKeepalive, Integer>> invalidKeepalives = null;
+
+ for (final AutomaticOnOffKeepalive autoKi : mAutomaticOnOffKeepalives) {
+ if (!nai.equals(autoKi.mKi.mNai)) continue;
+ final int error = autoKi.mKi.isValid();
+ if (error != SUCCESS) {
+ if (invalidKeepalives == null) {
+ invalidKeepalives = new ArrayList<>();
+ }
+ invalidKeepalives.add(Pair.create(autoKi, error));
+ }
+ }
+ if (invalidKeepalives == null) return;
+ for (final Pair<AutomaticOnOffKeepalive, Integer> keepaliveAndError : invalidKeepalives) {
+ handleStopKeepalive(keepaliveAndError.first, keepaliveAndError.second);
+ }
}
@VisibleForTesting
diff --git a/service/src/com/android/server/connectivity/KeepaliveTracker.java b/service/src/com/android/server/connectivity/KeepaliveTracker.java
index cc226ce..941b616 100644
--- a/service/src/com/android/server/connectivity/KeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveTracker.java
@@ -54,7 +54,6 @@
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
-import android.util.Pair;
import com.android.connectivity.resources.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -337,7 +336,12 @@
return SUCCESS;
}
- private int isValid() {
+ /**
+ * Checks if the keepalive info is valid to start.
+ *
+ * @return SUCCESS if the keepalive is valid and the error reason otherwise.
+ */
+ public int isValid() {
synchronized (mNai) {
int error = checkInterval();
if (error == SUCCESS) error = checkLimit();
@@ -348,11 +352,17 @@
}
}
- void start(int slot) {
+ /**
+ * Attempt to start the keepalive on the given slot.
+ *
+ * @param slot the slot to start the keepalive on.
+ * @return SUCCESS if the keepalive is successfully starting and the error reason otherwise.
+ */
+ int start(int slot) {
// BINDER_DIED can happen if the binder died before the KeepaliveInfo was created and
// the constructor set the state to BINDER_DIED. If that's the case, the KI is already
// cleaned up.
- if (BINDER_DIED == mStartedState) return;
+ if (BINDER_DIED == mStartedState) return BINDER_DIED;
mSlot = slot;
int error = isValid();
if (error == SUCCESS) {
@@ -368,7 +378,7 @@
mTcpController.startSocketMonitor(mFd, this, mSlot);
} catch (InvalidSocketException e) {
handleStopKeepalive(mNai, mSlot, ERROR_INVALID_SOCKET);
- return;
+ return ERROR_INVALID_SOCKET;
}
final TcpKeepalivePacketData tcpData = (TcpKeepalivePacketData) mPacket;
mNai.onAddTcpKeepalivePacketFilter(slot, tcpData);
@@ -377,13 +387,14 @@
break;
default:
Log.wtf(TAG, "Starting keepalive with unknown type: " + mType);
- handleStopKeepalive(mNai, mSlot, error);
- return;
+ handleStopKeepalive(mNai, mSlot, ERROR_UNSUPPORTED);
+ return ERROR_UNSUPPORTED;
}
mStartedState = STARTING;
+ return SUCCESS;
} else {
handleStopKeepalive(mNai, mSlot, error);
- return;
+ return error;
}
}
@@ -444,6 +455,8 @@
}
}
+ // TODO: This does not clean up the autoKi in AutomaticOnOffKeepaliveTracker and it is not
+ // possible without a big refactor.
void onFileDescriptorInitiatedStop(final int socketKeepaliveReason) {
handleStopKeepalive(mNai, mSlot, socketKeepaliveReason);
}
@@ -486,12 +499,15 @@
/**
* Handle start keepalives with the message.
+ *
+ * @param ki the keepalive to start.
+ * @return SUCCESS if the keepalive is successfully starting and the error reason otherwise.
*/
- public void handleStartKeepalive(KeepaliveInfo ki) {
+ public int handleStartKeepalive(KeepaliveInfo ki) {
NetworkAgentInfo nai = ki.getNai();
int slot = findFirstFreeSlot(nai);
mKeepalives.get(nai).put(slot, ki);
- ki.start(slot);
+ return ki.start(slot);
}
public void handleStopAllKeepalives(NetworkAgentInfo nai, int reason) {
@@ -593,40 +609,33 @@
/**
* Finalize a paused keepalive.
*
- * This will simply send the onStopped() callback after checking that this keepalive is
- * indeed paused.
+ * This will send the appropriate callback after checking that this keepalive is indeed paused.
*
* @param ki the keepalive to finalize
+ * @param reason the reason the keepalive is stopped
*/
- public void finalizePausedKeepalive(@NonNull final KeepaliveInfo ki) {
+ public void finalizePausedKeepalive(@NonNull final KeepaliveInfo ki, int reason) {
if (SUCCESS_PAUSED != ki.mStopReason) {
throw new IllegalStateException("Keepalive is not paused");
}
- try {
- ki.mCallback.onStopped();
- } catch (RemoteException e) {
- Log.w(TAG, "Discarded onStopped callback while finalizing paused keepalive");
+ if (reason == SUCCESS) {
+ try {
+ ki.mCallback.onStopped();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Discarded onStopped callback while finalizing paused keepalive");
+ }
+ } else {
+ notifyErrorCallback(ki.mCallback, reason);
}
}
- public void handleCheckKeepalivesStillValid(NetworkAgentInfo nai) {
- HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
- if (networkKeepalives != null) {
- ArrayList<Pair<Integer, Integer>> invalidKeepalives = new ArrayList<>();
- for (int slot : networkKeepalives.keySet()) {
- int error = networkKeepalives.get(slot).isValid();
- if (error != SUCCESS) {
- invalidKeepalives.add(Pair.create(slot, error));
- }
- }
- for (Pair<Integer, Integer> slotAndError: invalidKeepalives) {
- handleStopKeepalive(nai, slotAndError.first, slotAndError.second);
- }
- }
- }
-
- /** Handle keepalive events from lower layer. */
- public void handleEventSocketKeepalive(@NonNull NetworkAgentInfo nai, int slot, int reason) {
+ /**
+ * Handle keepalive events from lower layer.
+ *
+ * @return false if the event caused handleStopKeepalive to be called, i.e. the keepalive is
+ * forced to stop. Otherwise, return true.
+ */
+ public boolean handleEventSocketKeepalive(@NonNull NetworkAgentInfo nai, int slot, int reason) {
KeepaliveInfo ki = null;
try {
ki = mKeepalives.get(nai).get(slot);
@@ -634,7 +643,7 @@
if (ki == null) {
Log.e(TAG, "Event " + NetworkAgent.EVENT_SOCKET_KEEPALIVE + "," + slot + "," + reason
+ " for unknown keepalive " + slot + " on " + nai.toShortString());
- return;
+ return true;
}
// This can be called in a number of situations :
@@ -667,11 +676,13 @@
Log.w(TAG, "Discarded " + (ki.mResumed ? "onResumed" : "onStarted")
+ " callback for slot " + slot);
}
+ return true;
} else {
Log.d(TAG, "Failed to start keepalive " + slot + " on " + nai.toShortString()
+ ": " + reason);
// The message indicated some error trying to start: do call handleStopKeepalive.
handleStopKeepalive(nai, slot, reason);
+ return false;
}
} else if (KeepaliveInfo.STOPPING == ki.mStartedState) {
// The message indicated result of stopping : clean up keepalive slots.
@@ -679,9 +690,12 @@
+ " stopped: " + reason);
ki.mStartedState = KeepaliveInfo.NOT_STARTED;
cleanupStoppedKeepalive(nai, slot);
+ return true;
} else {
Log.wtf(TAG, "Event " + NetworkAgent.EVENT_SOCKET_KEEPALIVE + "," + slot + "," + reason
+ " for keepalive in wrong state: " + ki.toString());
+ // Although this is an unexpected event, the keepalive is not stopped here.
+ return true;
}
}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index c79c295..522ac8c 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -541,8 +541,7 @@
private static final int TEST_PACKAGE_UID2 = 321;
private static final int TEST_PACKAGE_UID3 = 456;
- private static final int PACKET_WAKEUP_MASK = 0xffff0000;
- private static final int PACKET_WAKEUP_MARK = 0x88880000;
+ private static final int PACKET_WAKEUP_MARK_MASK = 0x80000000;
private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn";
@@ -1924,9 +1923,9 @@
doReturn(0).when(mResources).getInteger(R.integer.config_activelyPreferBadWifi);
doReturn(true).when(mResources)
.getBoolean(R.bool.config_cellular_radio_timesharing_capable);
- doReturn(PACKET_WAKEUP_MASK).when(mResources).getInteger(
+ doReturn(PACKET_WAKEUP_MARK_MASK).when(mResources).getInteger(
R.integer.config_networkWakeupPacketMask);
- doReturn(PACKET_WAKEUP_MARK).when(mResources).getInteger(
+ doReturn(PACKET_WAKEUP_MARK_MASK).when(mResources).getInteger(
R.integer.config_networkWakeupPacketMark);
}
@@ -12931,9 +12930,16 @@
throws Exception {
InOrder inOrder = inOrder(mMockNetd, mDestroySocketsWrapper);
final Set<Integer> exemptUidSet = new ArraySet<>(List.of(exemptUid, Process.VPN_UID));
+ ArgumentCaptor<int[]> exemptUidCaptor = ArgumentCaptor.forClass(int[].class);
- inOrder.verify(mDestroySocketsWrapper).destroyLiveTcpSockets(
- UidRange.toIntRanges(vpnRanges), exemptUidSet);
+ if (mDeps.isAtLeastU()) {
+ inOrder.verify(mDestroySocketsWrapper).destroyLiveTcpSockets(
+ UidRange.toIntRanges(vpnRanges), exemptUidSet);
+ } else {
+ inOrder.verify(mMockNetd).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)),
+ exemptUidCaptor.capture());
+ assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
+ }
if (add) {
inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(
@@ -12945,8 +12951,14 @@
toUidRangeStableParcels(vpnRanges), PREFERENCE_ORDER_VPN));
}
- inOrder.verify(mDestroySocketsWrapper).destroyLiveTcpSockets(
- UidRange.toIntRanges(vpnRanges), exemptUidSet);
+ if (mDeps.isAtLeastU()) {
+ inOrder.verify(mDestroySocketsWrapper).destroyLiveTcpSockets(
+ UidRange.toIntRanges(vpnRanges), exemptUidSet);
+ } else {
+ inOrder.verify(mMockNetd).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)),
+ exemptUidCaptor.capture());
+ assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
+ }
}
@Test
@@ -18224,8 +18236,8 @@
final String expectedPrefix = makeNflogPrefix(WIFI_IFNAME,
mWiFiAgent.getNetwork().getNetworkHandle());
- verify(mMockNetd).wakeupAddInterface(WIFI_IFNAME, expectedPrefix, PACKET_WAKEUP_MARK,
- PACKET_WAKEUP_MASK);
+ verify(mMockNetd).wakeupAddInterface(WIFI_IFNAME, expectedPrefix, PACKET_WAKEUP_MARK_MASK,
+ PACKET_WAKEUP_MARK_MASK);
}
@Test
@@ -18238,8 +18250,8 @@
if (mDeps.isAtLeastU()) {
final String expectedPrefix = makeNflogPrefix(MOBILE_IFNAME,
mCellAgent.getNetwork().getNetworkHandle());
- verify(mMockNetd).wakeupAddInterface(MOBILE_IFNAME, expectedPrefix, PACKET_WAKEUP_MARK,
- PACKET_WAKEUP_MASK);
+ verify(mMockNetd).wakeupAddInterface(MOBILE_IFNAME, expectedPrefix,
+ PACKET_WAKEUP_MARK_MASK, PACKET_WAKEUP_MARK_MASK);
} else {
verify(mMockNetd, never()).wakeupAddInterface(eq(MOBILE_IFNAME), anyString(), anyInt(),
anyInt());
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 1997215..f51b28d 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -16,13 +16,21 @@
package com.android.server;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_PLATFORM_MDNS_BACKEND;
import static android.net.connectivity.ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER;
import static android.net.nsd.NsdManager.FAILURE_BAD_PARAMETERS;
import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
import static android.net.nsd.NsdManager.FAILURE_OPERATION_NOT_RUNNING;
+import static com.android.server.NsdService.DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF;
import static com.android.server.NsdService.parseTypeAndSubtype;
import static com.android.testutils.ContextUtils.mockService;
@@ -42,6 +50,7 @@
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -51,6 +60,8 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
+import android.app.ActivityManager.OnUidImportanceListener;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ContentResolver;
import android.content.Context;
@@ -68,7 +79,9 @@
import android.net.nsd.NsdManager.DiscoveryListener;
import android.net.nsd.NsdManager.RegistrationListener;
import android.net.nsd.NsdManager.ResolveListener;
+import android.net.nsd.NsdManager.ServiceInfoCallback;
import android.net.nsd.NsdServiceInfo;
+import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -85,10 +98,12 @@
import com.android.server.NsdService.Dependencies;
import com.android.server.connectivity.mdns.MdnsAdvertiser;
import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
+import com.android.server.connectivity.mdns.MdnsInterfaceSocket;
import com.android.server.connectivity.mdns.MdnsSearchOptions;
import com.android.server.connectivity.mdns.MdnsServiceBrowserListener;
import com.android.server.connectivity.mdns.MdnsServiceInfo;
import com.android.server.connectivity.mdns.MdnsSocketProvider;
+import com.android.server.connectivity.mdns.MdnsSocketProvider.SocketRequestMonitor;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
@@ -101,6 +116,7 @@
import org.junit.runner.RunWith;
import org.mockito.AdditionalAnswers;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -145,6 +161,11 @@
@Mock MdnsDiscoveryManager mDiscoveryManager;
@Mock MdnsAdvertiser mAdvertiser;
@Mock MdnsSocketProvider mSocketProvider;
+ @Mock WifiManager mWifiManager;
+ @Mock WifiManager.MulticastLock mMulticastLock;
+ @Mock ActivityManager mActivityManager;
+ SocketRequestMonitor mSocketRequestMonitor;
+ OnUidImportanceListener mUidImportanceListener;
HandlerThread mThread;
TestHandler mHandler;
NsdService mService;
@@ -167,9 +188,13 @@
mHandler = new TestHandler(mThread.getLooper());
when(mContext.getContentResolver()).thenReturn(mResolver);
mockService(mContext, MDnsManager.class, MDnsManager.MDNS_SERVICE, mMockMDnsM);
+ mockService(mContext, WifiManager.class, Context.WIFI_SERVICE, mWifiManager);
+ mockService(mContext, ActivityManager.class, Context.ACTIVITY_SERVICE, mActivityManager);
if (mContext.getSystemService(MDnsManager.class) == null) {
// Test is using mockito-extended
doCallRealMethod().when(mContext).getSystemService(MDnsManager.class);
+ doCallRealMethod().when(mContext).getSystemService(WifiManager.class);
+ doCallRealMethod().when(mContext).getSystemService(ActivityManager.class);
}
doReturn(true).when(mMockMDnsM).registerService(
anyInt(), anyString(), anyString(), anyInt(), any(), anyInt());
@@ -180,9 +205,21 @@
doReturn(false).when(mDeps).isMdnsDiscoveryManagerEnabled(any(Context.class));
doReturn(mDiscoveryManager).when(mDeps)
.makeMdnsDiscoveryManager(any(), any(), any());
- doReturn(mSocketProvider).when(mDeps).makeMdnsSocketProvider(any(), any(), any());
+ doReturn(mMulticastLock).when(mWifiManager).createMulticastLock(any());
+ doReturn(mSocketProvider).when(mDeps).makeMdnsSocketProvider(any(), any(), any(), any());
+ doReturn(DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF).when(mDeps).getDeviceConfigInt(
+ eq(NsdService.MDNS_CONFIG_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF), anyInt());
doReturn(mAdvertiser).when(mDeps).makeMdnsAdvertiser(any(), any(), any(), any());
mService = makeService();
+ final ArgumentCaptor<SocketRequestMonitor> cbMonitorCaptor =
+ ArgumentCaptor.forClass(SocketRequestMonitor.class);
+ verify(mDeps).makeMdnsSocketProvider(any(), any(), any(), cbMonitorCaptor.capture());
+ mSocketRequestMonitor = cbMonitorCaptor.getValue();
+
+ final ArgumentCaptor<OnUidImportanceListener> uidListenerCaptor =
+ ArgumentCaptor.forClass(OnUidImportanceListener.class);
+ verify(mActivityManager).addOnUidImportanceListener(uidListenerCaptor.capture(), anyInt());
+ mUidImportanceListener = uidListenerCaptor.getValue();
}
@After
@@ -738,8 +775,8 @@
public void testRegisterAndUnregisterServiceInfoCallback() {
final NsdManager client = connectClient(mService);
final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
- final NsdManager.ServiceInfoCallback serviceInfoCallback = mock(
- NsdManager.ServiceInfoCallback.class);
+ final ServiceInfoCallback serviceInfoCallback = mock(
+ ServiceInfoCallback.class);
final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
final Network network = new Network(999);
request.setNetwork(network);
@@ -813,8 +850,8 @@
final NsdManager client = connectClient(mService);
final String invalidServiceType = "a_service";
final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, invalidServiceType);
- final NsdManager.ServiceInfoCallback serviceInfoCallback = mock(
- NsdManager.ServiceInfoCallback.class);
+ final ServiceInfoCallback serviceInfoCallback = mock(
+ ServiceInfoCallback.class);
client.registerServiceInfoCallback(request, Runnable::run, serviceInfoCallback);
waitForIdle();
@@ -826,8 +863,8 @@
@Test
public void testUnregisterNotRegisteredCallback() {
final NsdManager client = connectClient(mService);
- final NsdManager.ServiceInfoCallback serviceInfoCallback = mock(
- NsdManager.ServiceInfoCallback.class);
+ final ServiceInfoCallback serviceInfoCallback = mock(
+ ServiceInfoCallback.class);
assertThrows(IllegalArgumentException.class, () ->
client.unregisterServiceInfoCallback(serviceInfoCallback));
@@ -1336,6 +1373,194 @@
verify(mDiscoveryManager, times(2)).registerListener(anyString(), any(), any());
}
+ @Test
+ @EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+ public void testTakeMulticastLockOnBehalfOfClient_ForWifiNetworksOnly() {
+ // Test on one client in the foreground
+ mUidImportanceListener.onUidImportance(123, IMPORTANCE_FOREGROUND);
+ doReturn(123).when(mDeps).getCallingUid();
+ final NsdManager client = connectClient(mService);
+
+ final NsdServiceInfo regInfo = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+ regInfo.setHostAddresses(List.of(parseNumericAddress("192.0.2.123")));
+ regInfo.setPort(12345);
+ // File a request for all networks
+ regInfo.setNetwork(null);
+
+ final RegistrationListener regListener = mock(RegistrationListener.class);
+ client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
+ waitForIdle();
+ verify(mSocketProvider).startMonitoringSockets();
+ verify(mAdvertiser).addService(anyInt(), any(), any());
+
+ final Network wifiNetwork1 = new Network(123);
+ final Network wifiNetwork2 = new Network(124);
+ final Network ethernetNetwork = new Network(125);
+
+ final MdnsInterfaceSocket wifiNetworkSocket1 = mock(MdnsInterfaceSocket.class);
+ final MdnsInterfaceSocket wifiNetworkSocket2 = mock(MdnsInterfaceSocket.class);
+ final MdnsInterfaceSocket ethernetNetworkSocket = mock(MdnsInterfaceSocket.class);
+
+ // Nothing happens for networks with no transports, no Wi-Fi transport, or VPN transport
+ mHandler.post(() -> {
+ mSocketRequestMonitor.onSocketRequestFulfilled(
+ new Network(125), mock(MdnsInterfaceSocket.class), new int[0]);
+ mSocketRequestMonitor.onSocketRequestFulfilled(
+ ethernetNetwork, ethernetNetworkSocket,
+ new int[] { TRANSPORT_ETHERNET });
+ mSocketRequestMonitor.onSocketRequestFulfilled(
+ new Network(127), mock(MdnsInterfaceSocket.class),
+ new int[] { TRANSPORT_WIFI, TRANSPORT_VPN });
+ });
+ waitForIdle();
+ verify(mWifiManager, never()).createMulticastLock(any());
+
+ // First Wi-Fi network
+ mHandler.post(() -> mSocketRequestMonitor.onSocketRequestFulfilled(
+ wifiNetwork1, wifiNetworkSocket1, new int[] { TRANSPORT_WIFI }));
+ waitForIdle();
+ verify(mWifiManager).createMulticastLock(any());
+ verify(mMulticastLock).acquire();
+
+ // Second Wi-Fi network
+ mHandler.post(() -> mSocketRequestMonitor.onSocketRequestFulfilled(
+ wifiNetwork2, wifiNetworkSocket2, new int[] { TRANSPORT_WIFI }));
+ waitForIdle();
+ verifyNoMoreInteractions(mMulticastLock);
+
+ // One Wi-Fi network becomes unused, nothing happens
+ mHandler.post(() -> mSocketRequestMonitor.onSocketDestroyed(
+ wifiNetwork1, wifiNetworkSocket1));
+ waitForIdle();
+ verifyNoMoreInteractions(mMulticastLock);
+
+ // Ethernet network becomes unused, still nothing
+ mHandler.post(() -> mSocketRequestMonitor.onSocketDestroyed(
+ ethernetNetwork, ethernetNetworkSocket));
+ waitForIdle();
+ verifyNoMoreInteractions(mMulticastLock);
+
+ // The second Wi-Fi network becomes unused, the lock is released
+ mHandler.post(() -> mSocketRequestMonitor.onSocketDestroyed(
+ wifiNetwork2, wifiNetworkSocket2));
+ waitForIdle();
+ verify(mMulticastLock).release();
+ }
+
+ @Test
+ @EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+ public void testTakeMulticastLockOnBehalfOfClient_ForForegroundAppsOnly() {
+ final int uid1 = 12;
+ final int uid2 = 34;
+ final int uid3 = 56;
+ final int uid4 = 78;
+ final InOrder lockOrder = inOrder(mMulticastLock);
+ // Connect one client without any foreground info
+ doReturn(uid1).when(mDeps).getCallingUid();
+ final NsdManager client1 = connectClient(mService);
+
+ // Connect client2 as visible, but not foreground
+ mUidImportanceListener.onUidImportance(uid2, IMPORTANCE_VISIBLE);
+ waitForIdle();
+ doReturn(uid2).when(mDeps).getCallingUid();
+ final NsdManager client2 = connectClient(mService);
+
+ // Connect client3, client4 as foreground
+ mUidImportanceListener.onUidImportance(uid3, IMPORTANCE_FOREGROUND);
+ waitForIdle();
+ doReturn(uid3).when(mDeps).getCallingUid();
+ final NsdManager client3 = connectClient(mService);
+
+ mUidImportanceListener.onUidImportance(uid4, IMPORTANCE_FOREGROUND);
+ waitForIdle();
+ doReturn(uid4).when(mDeps).getCallingUid();
+ final NsdManager client4 = connectClient(mService);
+
+ // First client advertises on any network
+ final NsdServiceInfo regInfo = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+ regInfo.setHostAddresses(List.of(parseNumericAddress("192.0.2.123")));
+ regInfo.setPort(12345);
+ regInfo.setNetwork(null);
+ final RegistrationListener regListener = mock(RegistrationListener.class);
+ client1.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
+ waitForIdle();
+
+ final MdnsInterfaceSocket wifiSocket = mock(MdnsInterfaceSocket.class);
+ final Network wifiNetwork = new Network(123);
+
+ final MdnsInterfaceSocket ethSocket = mock(MdnsInterfaceSocket.class);
+ final Network ethNetwork = new Network(234);
+
+ mHandler.post(() -> {
+ mSocketRequestMonitor.onSocketRequestFulfilled(
+ wifiNetwork, wifiSocket, new int[] { TRANSPORT_WIFI });
+ mSocketRequestMonitor.onSocketRequestFulfilled(
+ ethNetwork, ethSocket, new int[] { TRANSPORT_ETHERNET });
+ });
+ waitForIdle();
+
+ // No multicast lock since client1 has no foreground info
+ lockOrder.verifyNoMoreInteractions();
+
+ // Second client discovers specifically on the Wi-Fi network
+ final DiscoveryListener discListener = mock(DiscoveryListener.class);
+ client2.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, wifiNetwork,
+ Runnable::run, discListener);
+ waitForIdle();
+ mHandler.post(() -> mSocketRequestMonitor.onSocketRequestFulfilled(
+ wifiNetwork, wifiSocket, new int[] { TRANSPORT_WIFI }));
+ waitForIdle();
+ // No multicast lock since client2 is not visible enough
+ lockOrder.verifyNoMoreInteractions();
+
+ // Third client registers a callback on all networks
+ final NsdServiceInfo cbInfo = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+ cbInfo.setNetwork(null);
+ final ServiceInfoCallback infoCb = mock(ServiceInfoCallback.class);
+ client3.registerServiceInfoCallback(cbInfo, Runnable::run, infoCb);
+ waitForIdle();
+ mHandler.post(() -> {
+ mSocketRequestMonitor.onSocketRequestFulfilled(
+ wifiNetwork, wifiSocket, new int[] { TRANSPORT_WIFI });
+ mSocketRequestMonitor.onSocketRequestFulfilled(
+ ethNetwork, ethSocket, new int[] { TRANSPORT_ETHERNET });
+ });
+ waitForIdle();
+
+ // Multicast lock is taken for third client
+ lockOrder.verify(mMulticastLock).acquire();
+
+ // Client3 goes to the background
+ mUidImportanceListener.onUidImportance(uid3, IMPORTANCE_CACHED);
+ waitForIdle();
+ lockOrder.verify(mMulticastLock).release();
+
+ // client4 resolves on a different network
+ final ResolveListener resolveListener = mock(ResolveListener.class);
+ final NsdServiceInfo resolveInfo = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+ resolveInfo.setNetwork(ethNetwork);
+ client4.resolveService(resolveInfo, Runnable::run, resolveListener);
+ waitForIdle();
+ mHandler.post(() -> mSocketRequestMonitor.onSocketRequestFulfilled(
+ ethNetwork, ethSocket, new int[] { TRANSPORT_ETHERNET }));
+ waitForIdle();
+
+ // client4 is foreground, but not Wi-Fi
+ lockOrder.verifyNoMoreInteractions();
+
+ // Second client becomes foreground
+ mUidImportanceListener.onUidImportance(uid2, IMPORTANCE_FOREGROUND);
+ waitForIdle();
+
+ lockOrder.verify(mMulticastLock).acquire();
+
+ // Second client is lost
+ mUidImportanceListener.onUidImportance(uid2, IMPORTANCE_GONE);
+ waitForIdle();
+
+ lockOrder.verify(mMulticastLock).release();
+ }
+
private void waitForIdle() {
HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
}
diff --git a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
index 9e0435d..608e6d8 100644
--- a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
@@ -20,9 +20,12 @@
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static com.android.testutils.HandlerUtils.visibleOnHandlerThread;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -30,17 +33,20 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.longThat;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.ignoreStubs;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.AlarmManager;
import android.content.Context;
import android.content.res.Resources;
import android.net.INetd;
import android.net.ISocketKeepaliveCallback;
-import android.net.KeepalivePacketData;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MarkMaskParcel;
@@ -48,6 +54,7 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
+import android.net.SocketKeepalive;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -63,6 +70,7 @@
import androidx.annotation.Nullable;
import com.android.connectivity.resources.R;
+import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker.AutomaticOnOffKeepalive;
import com.android.server.connectivity.KeepaliveTracker.KeepaliveInfo;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -70,6 +78,7 @@
import libcore.util.HexEncoding;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -82,12 +91,15 @@
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public class AutomaticOnOffKeepaliveTrackerTest {
private static final String TAG = AutomaticOnOffKeepaliveTrackerTest.class.getSimpleName();
+ private static final int TEST_SLOT = 1;
private static final int TEST_NETID = 0xA85;
private static final int TEST_NETID_FWMARK = 0x0A85;
private static final int OTHER_NETID = 0x1A85;
@@ -95,6 +107,8 @@
private static final int TIMEOUT_MS = 30_000;
private static final int MOCK_RESOURCE_ID = 5;
private static final int TEST_KEEPALIVE_INTERVAL_SEC = 10;
+ private static final int TEST_KEEPALIVE_INVALID_INTERVAL_SEC = 9;
+
private AutomaticOnOffKeepaliveTracker mAOOKeepaliveTracker;
private HandlerThread mHandlerThread;
@@ -102,6 +116,8 @@
@Mock AutomaticOnOffKeepaliveTracker.Dependencies mDependencies;
@Mock Context mCtx;
@Mock AlarmManager mAlarmManager;
+ @Mock NetworkAgentInfo mNai;
+
TestKeepaliveTracker mKeepaliveTracker;
AOOTestHandler mTestHandler;
@@ -202,6 +218,37 @@
private static final byte[] TEST_RESPONSE_BYTES =
HexEncoding.decode(TEST_RESPONSE_HEX.toCharArray(), false);
+ private static class TestKeepaliveInfo {
+ private static List<Socket> sOpenSockets = new ArrayList<>();
+
+ public static void closeAllSockets() throws Exception {
+ for (final Socket socket : sOpenSockets) {
+ socket.close();
+ }
+ sOpenSockets.clear();
+ }
+
+ public final Socket socket;
+ public final Binder binder;
+ public final FileDescriptor fd;
+ public final ISocketKeepaliveCallback socketKeepaliveCallback;
+ public final Network underpinnedNetwork;
+ public final NattKeepalivePacketData kpd;
+
+ TestKeepaliveInfo(NattKeepalivePacketData kpd) throws Exception {
+ this.kpd = kpd;
+ socket = new Socket();
+ socket.bind(null);
+ sOpenSockets.add(socket);
+ fd = socket.getFileDescriptor$();
+
+ binder = new Binder();
+ socketKeepaliveCallback = mock(ISocketKeepaliveCallback.class);
+ doReturn(binder).when(socketKeepaliveCallback).asBinder();
+ underpinnedNetwork = mock(Network.class);
+ }
+ }
+
private class TestKeepaliveTracker extends KeepaliveTracker {
private KeepaliveInfo mKi;
@@ -231,6 +278,14 @@
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
+ mNai.networkCapabilities =
+ new NetworkCapabilities.Builder().addTransportType(TRANSPORT_CELLULAR).build();
+ mNai.networkInfo = new NetworkInfo(TYPE_MOBILE, 0 /* subtype */, "LTE", "LTE");
+ mNai.networkInfo.setDetailedState(
+ NetworkInfo.DetailedState.CONNECTED, "test reason", "test extra info");
+ doReturn(new Network(TEST_NETID)).when(mNai).network();
+ mNai.linkProperties = new LinkProperties();
+
doReturn(PERMISSION_GRANTED).when(mCtx).checkPermission(any() /* permission */,
anyInt() /* pid */, anyInt() /* uid */);
ConnectivityResources.setResourcesContextForTest(mCtx);
@@ -255,6 +310,11 @@
new AutomaticOnOffKeepaliveTracker(mCtx, mTestHandler, mDependencies);
}
+ @After
+ public void teardown() throws Exception {
+ TestKeepaliveInfo.closeAllSockets();
+ }
+
private final class AOOTestHandler extends Handler {
public AutomaticOnOffKeepaliveTracker.AutomaticOnOffKeepalive mLastAutoKi = null;
@@ -305,45 +365,70 @@
() -> assertFalse(mAOOKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID)));
}
- @Test
- public void testAlarm() throws Exception {
+ private void triggerEventKeepalive(int slot, int reason) {
+ visibleOnHandlerThread(
+ mTestHandler,
+ () -> mAOOKeepaliveTracker.handleEventSocketKeepalive(mNai, slot, reason));
+ }
+
+ private TestKeepaliveInfo doStartNattKeepalive(int intervalSeconds) throws Exception {
final InetAddress srcAddress = InetAddress.getByAddress(
new byte[] { (byte) 192, 0, 0, (byte) 129 });
final int srcPort = 12345;
- final InetAddress dstAddress = InetAddress.getByAddress(new byte[] { 8, 8, 8, 8});
+ final InetAddress dstAddress = InetAddress.getByAddress(new byte[] {8, 8, 8, 8});
final int dstPort = 12345;
- final NetworkAgentInfo nai = mock(NetworkAgentInfo.class);
- nai.networkCapabilities = new NetworkCapabilities.Builder()
- .addTransportType(TRANSPORT_CELLULAR).build();
- nai.networkInfo = new NetworkInfo(TYPE_MOBILE, 0 /* subtype */, "LTE", "LTE");
- nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "test reason",
- "test extra info");
- nai.linkProperties = new LinkProperties();
- nai.linkProperties.addLinkAddress(new LinkAddress(srcAddress, 24));
+ mNai.linkProperties.addLinkAddress(new LinkAddress(srcAddress, 24));
- final Socket socket = new Socket();
- socket.bind(null);
- final FileDescriptor fd = socket.getFileDescriptor$();
- final IBinder binder = new Binder();
- final ISocketKeepaliveCallback cb = mock(ISocketKeepaliveCallback.class);
- doReturn(binder).when(cb).asBinder();
- final Network underpinnedNetwork = mock(Network.class);
-
- final KeepalivePacketData kpd = new NattKeepalivePacketData(srcAddress, srcPort,
+ final NattKeepalivePacketData kpd = new NattKeepalivePacketData(srcAddress, srcPort,
dstAddress, dstPort, new byte[] {1});
- final KeepaliveInfo ki = mKeepaliveTracker.new KeepaliveInfo(cb, nai, kpd,
- TEST_KEEPALIVE_INTERVAL_SEC, KeepaliveInfo.TYPE_NATT, fd);
+
+ final TestKeepaliveInfo testInfo = new TestKeepaliveInfo(kpd);
+
+ final KeepaliveInfo ki = mKeepaliveTracker.new KeepaliveInfo(
+ testInfo.socketKeepaliveCallback, mNai, kpd, intervalSeconds,
+ KeepaliveInfo.TYPE_NATT, testInfo.fd);
mKeepaliveTracker.setReturnedKeepaliveInfo(ki);
+ mAOOKeepaliveTracker.startNattKeepalive(mNai, testInfo.fd, intervalSeconds,
+ testInfo.socketKeepaliveCallback, srcAddress.toString(), srcPort,
+ dstAddress.toString(), dstPort, true /* automaticOnOffKeepalives */,
+ testInfo.underpinnedNetwork);
+ HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
+
+ return testInfo;
+ }
+
+ private TestKeepaliveInfo doStartNattKeepalive() throws Exception {
+ return doStartNattKeepalive(TEST_KEEPALIVE_INTERVAL_SEC);
+ }
+
+ private void doPauseKeepalive(AutomaticOnOffKeepalive autoKi) throws Exception {
+ setupResponseWithoutSocketExisting();
+ visibleOnHandlerThread(
+ mTestHandler,
+ () -> mAOOKeepaliveTracker.handleMonitorAutomaticKeepalive(autoKi, TEST_NETID));
+ }
+
+ private void doResumeKeepalive(AutomaticOnOffKeepalive autoKi) throws Exception {
+ setupResponseWithSocketExisting();
+ visibleOnHandlerThread(
+ mTestHandler,
+ () -> mAOOKeepaliveTracker.handleMonitorAutomaticKeepalive(autoKi, TEST_NETID));
+ }
+
+ private void doStopKeepalive(AutomaticOnOffKeepalive autoKi) throws Exception {
+ visibleOnHandlerThread(
+ mTestHandler,
+ () -> mAOOKeepaliveTracker.handleStopKeepalive(autoKi, SocketKeepalive.SUCCESS));
+ }
+
+ @Test
+ public void testAlarm() throws Exception {
// Mock elapsed real time to verify the alarm timer.
final long time = SystemClock.elapsedRealtime();
doReturn(time).when(mDependencies).getElapsedRealtime();
-
- mAOOKeepaliveTracker.startNattKeepalive(nai, fd, 10 /* intervalSeconds */, cb,
- srcAddress.toString(), srcPort, dstAddress.toString(), dstPort,
- true /* automaticOnOffKeepalives */, underpinnedNetwork);
- HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
final ArgumentCaptor<AlarmManager.OnAlarmListener> listenerCaptor =
ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
@@ -362,9 +447,8 @@
HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
assertNotNull(mTestHandler.mLastAutoKi);
- assertEquals(cb, mTestHandler.mLastAutoKi.getCallback());
- assertEquals(underpinnedNetwork, mTestHandler.mLastAutoKi.getUnderpinnedNetwork());
- socket.close();
+ assertEquals(testInfo.socketKeepaliveCallback, mTestHandler.mLastAutoKi.getCallback());
+ assertEquals(testInfo.underpinnedNetwork, mTestHandler.mLastAutoKi.getUnderpinnedNetwork());
}
private void setupResponseWithSocketExisting() throws Exception {
@@ -391,4 +475,301 @@
buffer.order(ByteOrder.nativeOrder());
return buffer;
}
+
+ private AutomaticOnOffKeepalive getAutoKiForBinder(IBinder binder) {
+ return visibleOnHandlerThread(
+ mTestHandler, () -> mAOOKeepaliveTracker.getKeepaliveForBinder(binder));
+ }
+
+ private void checkAndProcessKeepaliveStart(final NattKeepalivePacketData kpd) throws Exception {
+ checkAndProcessKeepaliveStart(TEST_SLOT, kpd);
+ }
+
+ private void checkAndProcessKeepaliveStart(
+ int slot, final NattKeepalivePacketData kpd) throws Exception {
+ verify(mNai).onStartNattSocketKeepalive(slot, TEST_KEEPALIVE_INTERVAL_SEC, kpd);
+ verify(mNai).onAddNattKeepalivePacketFilter(slot, kpd);
+ triggerEventKeepalive(slot, SocketKeepalive.SUCCESS);
+ }
+
+ private void checkAndProcessKeepaliveStop() throws Exception {
+ checkAndProcessKeepaliveStop(TEST_SLOT);
+ }
+
+ private void checkAndProcessKeepaliveStop(int slot) throws Exception {
+ verify(mNai).onStopSocketKeepalive(slot);
+ verify(mNai).onRemoveKeepalivePacketFilter(slot);
+ triggerEventKeepalive(slot, SocketKeepalive.SUCCESS);
+ }
+
+ @Test
+ public void testStartNattKeepalive_valid() throws Exception {
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
+
+ checkAndProcessKeepaliveStart(testInfo.kpd);
+
+ final AutomaticOnOffKeepalive autoKi = getAutoKiForBinder(testInfo.binder);
+ assertNotNull(autoKi);
+ assertEquals(testInfo.socketKeepaliveCallback, autoKi.getCallback());
+
+ verify(testInfo.socketKeepaliveCallback).onStarted();
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testStartNattKeepalive_invalidInterval() throws Exception {
+ final TestKeepaliveInfo testInfo =
+ doStartNattKeepalive(TEST_KEEPALIVE_INVALID_INTERVAL_SEC);
+
+ assertNull(getAutoKiForBinder(testInfo.binder));
+
+ verify(testInfo.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_INVALID_INTERVAL);
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testHandleEventSocketKeepalive_startingFailureHardwareError() throws Exception {
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
+
+ verify(mNai)
+ .onStartNattSocketKeepalive(TEST_SLOT, TEST_KEEPALIVE_INTERVAL_SEC, testInfo.kpd);
+ verify(mNai).onAddNattKeepalivePacketFilter(TEST_SLOT, testInfo.kpd);
+ // Network agent returns an error, fails to start the keepalive.
+ triggerEventKeepalive(TEST_SLOT, SocketKeepalive.ERROR_HARDWARE_ERROR);
+
+ checkAndProcessKeepaliveStop();
+
+ assertNull(getAutoKiForBinder(testInfo.binder));
+
+ verify(testInfo.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_HARDWARE_ERROR);
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testHandleCheckKeepalivesStillValid_linkPropertiesChanged() throws Exception {
+ // Successful start of NATT keepalive.
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(testInfo.kpd);
+ verify(testInfo.socketKeepaliveCallback).onStarted();
+
+ // Source address is removed from link properties by clearing.
+ mNai.linkProperties.clear();
+
+ // Check for valid keepalives
+ visibleOnHandlerThread(
+ mTestHandler, () -> mAOOKeepaliveTracker.handleCheckKeepalivesStillValid(mNai));
+
+ checkAndProcessKeepaliveStop();
+
+ assertNull(getAutoKiForBinder(testInfo.binder));
+
+ verify(testInfo.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testStopKeepalive() throws Exception {
+ // Successful start of NATT keepalive.
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(testInfo.kpd);
+ verify(testInfo.socketKeepaliveCallback).onStarted();
+
+ doStopKeepalive(getAutoKiForBinder(testInfo.binder));
+ checkAndProcessKeepaliveStop();
+
+ assertNull(getAutoKiForBinder(testInfo.binder));
+ verify(testInfo.socketKeepaliveCallback).onStopped();
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testPauseKeepalive() throws Exception {
+ // Successful start of NATT keepalive.
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(testInfo.kpd);
+ verify(testInfo.socketKeepaliveCallback).onStarted();
+
+ doPauseKeepalive(getAutoKiForBinder(testInfo.binder));
+
+ checkAndProcessKeepaliveStop();
+ verify(testInfo.socketKeepaliveCallback).onPaused();
+
+ // Pausing does not cleanup the autoKi
+ assertNotNull(getAutoKiForBinder(testInfo.binder));
+
+ clearInvocations(mNai);
+ doStopKeepalive(getAutoKiForBinder(testInfo.binder));
+ // The keepalive is already stopped.
+ verify(mNai, never()).onStopSocketKeepalive(TEST_SLOT);
+ verify(mNai, never()).onRemoveKeepalivePacketFilter(TEST_SLOT);
+
+ // Stopping while paused still calls onStopped.
+ verify(testInfo.socketKeepaliveCallback).onStopped();
+ // autoKi is cleaned up.
+ assertNull(getAutoKiForBinder(testInfo.binder));
+
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testResumeKeepalive() throws Exception {
+ // Successful start of NATT keepalive.
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(testInfo.kpd);
+ verify(testInfo.socketKeepaliveCallback).onStarted();
+
+ doPauseKeepalive(getAutoKiForBinder(testInfo.binder));
+ checkAndProcessKeepaliveStop();
+ verify(testInfo.socketKeepaliveCallback).onPaused();
+
+ clearInvocations(mNai);
+ doResumeKeepalive(getAutoKiForBinder(testInfo.binder));
+ checkAndProcessKeepaliveStart(testInfo.kpd);
+ assertNotNull(getAutoKiForBinder(testInfo.binder));
+ verify(testInfo.socketKeepaliveCallback).onResumed();
+
+ doStopKeepalive(getAutoKiForBinder(testInfo.binder));
+ checkAndProcessKeepaliveStop();
+ assertNull(getAutoKiForBinder(testInfo.binder));
+
+ verify(testInfo.socketKeepaliveCallback).onStopped();
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testResumeKeepalive_invalidSourceAddress() throws Exception {
+ // Successful start of NATT keepalive.
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(testInfo.kpd);
+ verify(testInfo.socketKeepaliveCallback).onStarted();
+
+ doPauseKeepalive(getAutoKiForBinder(testInfo.binder));
+ checkAndProcessKeepaliveStop();
+ verify(testInfo.socketKeepaliveCallback).onPaused();
+
+ mNai.linkProperties.clear();
+
+ clearInvocations(mNai);
+ doResumeKeepalive(getAutoKiForBinder(testInfo.binder));
+ verify(mNai, never()).onStartNattSocketKeepalive(anyInt(), anyInt(), any());
+ verify(mNai, never()).onAddNattKeepalivePacketFilter(anyInt(), any());
+
+ assertNull(getAutoKiForBinder(testInfo.binder));
+
+ verify(testInfo.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testResumeKeepalive_startingFailureHardwareError() throws Exception {
+ // Successful start of NATT keepalive.
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(testInfo.kpd);
+ verify(testInfo.socketKeepaliveCallback).onStarted();
+
+ doPauseKeepalive(getAutoKiForBinder(testInfo.binder));
+ checkAndProcessKeepaliveStop();
+ verify(testInfo.socketKeepaliveCallback).onPaused();
+
+ clearInvocations(mNai);
+ doResumeKeepalive(getAutoKiForBinder(testInfo.binder));
+
+ verify(mNai)
+ .onStartNattSocketKeepalive(TEST_SLOT, TEST_KEEPALIVE_INTERVAL_SEC, testInfo.kpd);
+ verify(mNai).onAddNattKeepalivePacketFilter(TEST_SLOT, testInfo.kpd);
+ // Network agent returns error on starting the keepalive.
+ triggerEventKeepalive(TEST_SLOT, SocketKeepalive.ERROR_HARDWARE_ERROR);
+
+ checkAndProcessKeepaliveStop();
+
+ assertNull(getAutoKiForBinder(testInfo.binder));
+ verify(testInfo.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_HARDWARE_ERROR);
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testStopAllKeepalives() throws Exception {
+ final TestKeepaliveInfo testInfo1 = doStartNattKeepalive();
+ final TestKeepaliveInfo testInfo2 = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(TEST_SLOT, testInfo1.kpd);
+ checkAndProcessKeepaliveStart(TEST_SLOT + 1, testInfo2.kpd);
+
+ verify(testInfo1.socketKeepaliveCallback).onStarted();
+ verify(testInfo2.socketKeepaliveCallback).onStarted();
+
+ // Pause the first keepalive
+ doPauseKeepalive(getAutoKiForBinder(testInfo1.binder));
+ checkAndProcessKeepaliveStop(TEST_SLOT);
+ verify(testInfo1.socketKeepaliveCallback).onPaused();
+
+ visibleOnHandlerThread(
+ mTestHandler,
+ () -> mAOOKeepaliveTracker.handleStopAllKeepalives(
+ mNai, SocketKeepalive.ERROR_INVALID_NETWORK));
+
+ // Note that checkAndProcessKeepaliveStop is not called since the network agent is assumed
+ // to be disconnected for a handleStopAllKeepalives call.
+ assertNull(getAutoKiForBinder(testInfo1.binder));
+ assertNull(getAutoKiForBinder(testInfo2.binder));
+
+ verify(testInfo1.socketKeepaliveCallback, never()).onStopped();
+ verify(testInfo2.socketKeepaliveCallback, never()).onStopped();
+ verify(testInfo1.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_INVALID_NETWORK);
+ verify(testInfo2.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_INVALID_NETWORK);
+
+ verifyNoMoreInteractions(ignoreStubs(testInfo1.socketKeepaliveCallback));
+ verifyNoMoreInteractions(ignoreStubs(testInfo2.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testTwoKeepalives_startAfterPause() throws Exception {
+ final TestKeepaliveInfo testInfo1 = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(testInfo1.kpd);
+ verify(testInfo1.socketKeepaliveCallback).onStarted();
+ assertNotNull(getAutoKiForBinder(testInfo1.binder));
+
+ final AutomaticOnOffKeepalive autoKi1 = getAutoKiForBinder(testInfo1.binder);
+ doPauseKeepalive(autoKi1);
+ checkAndProcessKeepaliveStop(TEST_SLOT);
+ verify(testInfo1.socketKeepaliveCallback).onPaused();
+ assertNotNull(getAutoKiForBinder(testInfo1.binder));
+
+ clearInvocations(mNai);
+ // Start the second keepalive while the first is paused.
+ final TestKeepaliveInfo testInfo2 = doStartNattKeepalive();
+ // The slot used is TEST_SLOT since it is now a free slot.
+ checkAndProcessKeepaliveStart(TEST_SLOT, testInfo2.kpd);
+ verify(testInfo2.socketKeepaliveCallback).onStarted();
+ assertNotNull(getAutoKiForBinder(testInfo2.binder));
+
+ clearInvocations(mNai);
+ doResumeKeepalive(autoKi1);
+ // The next free slot is TEST_SLOT + 1.
+ checkAndProcessKeepaliveStart(TEST_SLOT + 1, testInfo1.kpd);
+ verify(testInfo1.socketKeepaliveCallback).onResumed();
+
+ clearInvocations(mNai);
+ doStopKeepalive(autoKi1);
+ // TODO: The slot should be consistent with the checkAndProcessKeepaliveStart directly above
+ checkAndProcessKeepaliveStop(TEST_SLOT);
+ // TODO: onStopped should only be called on the first keepalive callback.
+ verify(testInfo1.socketKeepaliveCallback, never()).onStopped();
+ verify(testInfo2.socketKeepaliveCallback).onStopped();
+ assertNull(getAutoKiForBinder(testInfo1.binder));
+
+ clearInvocations(mNai);
+ assertNotNull(getAutoKiForBinder(testInfo2.binder));
+ doStopKeepalive(getAutoKiForBinder(testInfo2.binder));
+ // This slot should be consistent with its corresponding checkAndProcessKeepaliveStart.
+ // TODO: checkAndProcessKeepaliveStop should be called instead but the keepalive is
+ // unexpectedly already stopped above.
+ verify(mNai, never()).onStopSocketKeepalive(TEST_SLOT);
+ verify(mNai, never()).onRemoveKeepalivePacketFilter(TEST_SLOT);
+
+ verify(testInfo2.socketKeepaliveCallback).onStopped();
+ assertNull(getAutoKiForBinder(testInfo2.binder));
+
+ verifyNoMoreInteractions(ignoreStubs(testInfo1.socketKeepaliveCallback));
+ verifyNoMoreInteractions(ignoreStubs(testInfo2.socketKeepaliveCallback));
+ }
}
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..4ef64cb 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
@@ -31,16 +31,21 @@
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.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
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 +54,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;
@@ -64,6 +72,7 @@
import com.android.net.module.util.netlink.StructIfaddrMsg;
import com.android.net.module.util.netlink.StructNlMsgHdr;
import com.android.server.connectivity.mdns.MdnsSocketProvider.Dependencies;
+import com.android.server.connectivity.mdns.MdnsSocketProvider.SocketRequestMonitor;
import com.android.server.connectivity.mdns.internal.SocketNetlinkMonitor;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -73,6 +82,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -88,6 +98,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;
@@ -107,6 +118,7 @@
@Mock private NetworkInterfaceWrapper mTestNetworkIfaceWrapper;
@Mock private NetworkInterfaceWrapper mLocalOnlyIfaceWrapper;
@Mock private NetworkInterfaceWrapper mTetheredIfaceWrapper;
+ @Mock private SocketRequestMonitor mSocketRequestMonitor;
private Handler mHandler;
private MdnsSocketProvider mSocketProvider;
private NetworkCallback mNetworkCallback;
@@ -136,11 +148,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());
@@ -154,7 +170,29 @@
return mTestSocketNetLinkMonitor;
}).when(mDeps).createSocketNetlinkMonitor(any(), any(),
any());
- mSocketProvider = new MdnsSocketProvider(mContext, thread.getLooper(), mDeps, mLog);
+ mSocketProvider = new MdnsSocketProvider(mContext, thread.getLooper(), mDeps, mLog,
+ mSocketRequestMonitor);
+ }
+
+ 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() {
@@ -163,16 +201,14 @@
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,76 +317,82 @@
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
public void testSocketRequestAndUnrequestSocket() {
startMonitoringSockets();
+ final InOrder cbMonitorOrder = inOrder(mSocketRequestMonitor);
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));
+ cbMonitorOrder.verify(mSocketRequestMonitor).onSocketRequestFulfilled(eq(TEST_NETWORK),
+ any(), eq(new int[] { TRANSPORT_WIFI }));
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));
+ cbMonitorOrder.verify(mSocketRequestMonitor).onSocketRequestFulfilled(eq(TEST_NETWORK),
+ any(), eq(new int[] { TRANSPORT_WIFI }));
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));
+ cbMonitorOrder.verify(mSocketRequestMonitor).onSocketRequestFulfilled(eq(TEST_NETWORK),
+ any(), eq(new int[] { TRANSPORT_WIFI }));
- 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());
+ cbMonitorOrder.verify(mSocketRequestMonitor).onSocketRequestFulfilled(eq(null),
+ any(), eq(new int[0]));
- 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());
+ cbMonitorOrder.verify(mSocketRequestMonitor).onSocketRequestFulfilled(eq(null),
+ any(), eq(new int[0]));
- 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);
+ cbMonitorOrder.verify(mSocketRequestMonitor).onSocketDestroyed(eq(TEST_NETWORK), any());
- mHandler.post(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(List.of()));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(List.of()));
testCallback1.expectedNoCallback();
testCallback2.expectedNoCallback();
testCallback3.expectedInterfaceDestroyedForNetwork(null /* network */);
+ cbMonitorOrder.verify(mSocketRequestMonitor).onSocketDestroyed(eq(null), any());
- 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
testCallback3.expectedNoCallback();
+
+ // However the socket is getting destroyed, so the callback monitor is notified
+ cbMonitorOrder.verify(mSocketRequestMonitor).onSocketDestroyed(eq(null), any());
}
private RtNetlinkAddressMessage createNetworkAddressUpdateNetLink(
@@ -376,8 +418,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 +426,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 +442,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 +453,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 +463,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 +475,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 +484,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 +492,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 +500,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 +536,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 +557,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 +570,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 +580,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 +592,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 +604,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 +616,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 +630,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 +642,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());
+ }
}