Merge "Fix bug for failing to init Offload AIDL HAL service" into main
diff --git a/Cronet/OWNERS b/Cronet/OWNERS
index 62c5737..c24680e 100644
--- a/Cronet/OWNERS
+++ b/Cronet/OWNERS
@@ -1,2 +1,2 @@
set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
diff --git a/Cronet/tests/OWNERS b/Cronet/tests/OWNERS
index acb6ee6..a35a789 100644
--- a/Cronet/tests/OWNERS
+++ b/Cronet/tests/OWNERS
@@ -1,7 +1,7 @@
# Bug component: 31808
set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking_xts
# TODO: Temp ownership to develop cronet CTS
colibie@google.com #{LAST_RESORT_SUGGESTION}
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 0885f4f..464862d 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
@@ -27,12 +27,14 @@
import androidx.test.core.app.ApplicationProvider
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SkipPresubmit
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlin.test.assertEquals
import org.hamcrest.MatcherAssert
import org.hamcrest.Matchers
import org.junit.After
+import org.junit.AssumptionViolatedException
import org.junit.Before
import org.junit.runner.RunWith
@@ -58,11 +60,6 @@
@After
@Throws(Exception::class)
fun tearDown() {
- // cancel active requests to enable engine shutdown.
- stream?.let {
- it.cancel()
- callback.blockForDone()
- }
httpEngine.shutdown()
}
@@ -72,10 +69,21 @@
@Test
@Throws(Exception::class)
+ @SkipPresubmit(reason = "b/293141085 Confirm non-flaky and move to presubmit after SLO")
fun testBidirectionalStream_GetStream_CompletesSuccessfully() {
stream = createBidirectionalStreamBuilder(URL).setHttpMethod("GET").build()
stream!!.start()
- callback.assumeCallback(ResponseStep.ON_SUCCEEDED)
+ // We call to a real server and hence the server may not be reachable, cancel this stream
+ // and rethrow the exception before tearDown,
+ // otherwise shutdown would fail with active request error.
+ try {
+ callback.assumeCallback(ResponseStep.ON_SUCCEEDED)
+ } catch (e: AssumptionViolatedException) {
+ stream!!.cancel()
+ callback.blockForDone()
+ throw e
+ }
+
val info = callback.mResponseInfo
assumeOKStatusCode(info)
MatcherAssert.assertThat(
@@ -190,5 +198,4 @@
stream = builder.build()
assertThat(stream!!.isDelayRequestHeadersUntilFirstFlushEnabled()).isTrue()
}
-
}
diff --git a/Cronet/tests/mts/Android.bp b/Cronet/tests/mts/Android.bp
index 4e4251c..63905c8 100644
--- a/Cronet/tests/mts/Android.bp
+++ b/Cronet/tests/mts/Android.bp
@@ -38,7 +38,11 @@
// tests need to inherit the NetHttpTests manifest.
android_library {
name: "NetHttpTestsLibPreJarJar",
- static_libs: ["cronet_java_tests"],
+ static_libs: [
+ "cronet_aml_api_java",
+ "cronet_aml_java__testing",
+ "cronet_java_tests",
+ ],
sdk_version: "module_current",
min_sdk_version: "30",
}
@@ -51,7 +55,8 @@
static_libs: ["NetHttpTestsLibPreJarJar"],
jarjar_rules: ":net-http-test-jarjar-rules",
jni_libs: [
- "cronet_aml_components_cronet_android_cronet_tests__testing"
+ "cronet_aml_components_cronet_android_cronet__testing",
+ "cronet_aml_components_cronet_android_cronet_tests__testing",
],
test_suites: [
"general-tests",
diff --git a/Cronet/tests/mts/jarjar_excludes.txt b/Cronet/tests/mts/jarjar_excludes.txt
index a3e86de..e8fd39b 100644
--- a/Cronet/tests/mts/jarjar_excludes.txt
+++ b/Cronet/tests/mts/jarjar_excludes.txt
@@ -1,5 +1,8 @@
-# It's prohibited to jarjar androidx packages
+# jarjar-gen can't handle some kotlin object expression, exclude packages that include them
androidx\..+
+kotlin\.test\..+
+kotlin\.reflect\..+
+org\.mockito\..+
# Do not jarjar the api classes
android\.net\..+
# cronet_tests.so is not jarjared and uses base classes. We can remove this when there's a
diff --git a/OWNERS b/OWNERS
index 07a775e..649efda 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,4 +1,4 @@
set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
-per-file **IpSec* = file:platform/frameworks/base:master:/services/core/java/com/android/server/vcn/OWNERS
\ No newline at end of file
+per-file **IpSec* = file:platform/frameworks/base:main:/services/core/java/com/android/server/vcn/OWNERS
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 0326bf2..76e4af8 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -5,7 +5,12 @@
},
{
// In addition to ConnectivityCoverageTests, runs non-connectivity-module tests
- "name": "FrameworksNetTests"
+ "name": "FrameworksNetTests",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ]
},
// Run in addition to mainline-presubmit as mainline-presubmit is not
// supported in every branch.
@@ -100,6 +105,9 @@
"name": "NetHttpCoverageTests",
"options": [
{
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
// These sometimes take longer than 1 min which is the presubmit timeout
"exclude-annotation": "androidx.test.filters.LargeTest"
}
@@ -120,6 +128,18 @@
},
{
"name": "FrameworksNetDeflakeTest"
+ },
+ // Postsubmit on virtual devices to monitor flakiness of @SkipPresubmit methods
+ {
+ "name": "CtsNetTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ },
+ {
+ "name": "FrameworksNetTests"
}
],
"mainline-presubmit": [
@@ -130,6 +150,9 @@
"exclude-annotation": "com.android.testutils.SkipPresubmit"
},
{
+ "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+ },
+ {
"exclude-annotation": "androidx.test.filters.RequiresDevice"
}
]
@@ -141,6 +164,9 @@
"exclude-annotation": "com.android.testutils.SkipPresubmit"
},
{
+ "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+ },
+ {
"exclude-annotation": "androidx.test.filters.RequiresDevice"
}
]
@@ -152,6 +178,9 @@
"exclude-annotation": "com.android.testutils.SkipPresubmit"
},
{
+ "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+ },
+ {
"exclude-annotation": "androidx.test.filters.RequiresDevice"
}
]
@@ -163,6 +192,9 @@
"exclude-annotation": "com.android.testutils.SkipPresubmit"
},
{
+ "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+ },
+ {
"exclude-annotation": "androidx.test.filters.RequiresDevice"
}
]
@@ -176,10 +208,16 @@
"exclude-annotation": "com.android.testutils.SkipPresubmit"
},
{
+ "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+ },
+ {
"exclude-annotation": "androidx.test.filters.RequiresDevice"
},
{
"exclude-annotation": "com.android.testutils.ConnectivityModuleTest"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.DnsResolverModuleTest"
}
]
},
@@ -194,7 +232,13 @@
"exclude-annotation": "com.android.testutils.SkipPresubmit"
},
{
+ "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+ },
+ {
"exclude-annotation": "androidx.test.filters.RequiresDevice"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.DnsResolverModuleTest"
}
]
},
@@ -220,6 +264,9 @@
"name": "NetHttpCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
"options": [
{
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
// These sometimes take longer than 1 min which is the presubmit timeout
"exclude-annotation": "androidx.test.filters.LargeTest"
}
@@ -248,6 +295,15 @@
"exclude-annotation": "com.android.testutils.NetworkStackModuleTest"
}
]
+ },
+ // Postsubmit on virtual devices to monitor flakiness of @SkipMainlinePresubmit methods
+ {
+ "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
}
],
"imports": [
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index 9dad301..83fc3e4 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -58,6 +58,8 @@
import android.net.TetheringManager.TetheringRequest;
import android.net.TetheringTester.TetheredDevice;
import android.net.cts.util.CtsNetUtils;
+import android.net.cts.util.CtsTetheringUtils;
+import android.net.cts.util.CtsTetheringUtils.TestTetheringEventCallback;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
@@ -75,6 +77,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.BeforeClass;
import java.io.FileDescriptor;
import java.net.Inet4Address;
@@ -161,6 +164,28 @@
private TapPacketReader mDownstreamReader;
private MyTetheringEventCallback mTetheringEventCallback;
+ @BeforeClass
+ public static void setUpOnce() throws Exception {
+ // The first test case may experience tethering restart with IP conflict handling.
+ // Tethering would cache the last upstreams so that the next enabled tethering avoids
+ // picking up the address that is in conflict with the upstreams. To protect subsequent
+ // tests, turn tethering on and off before running them.
+ final Context ctx = InstrumentationRegistry.getInstrumentation().getContext();
+ final CtsTetheringUtils utils = new CtsTetheringUtils(ctx);
+ final TestTetheringEventCallback callback = utils.registerTetheringEventCallback();
+ try {
+ if (!callback.isWifiTetheringSupported(ctx)) return;
+
+ callback.expectNoTetheringActive();
+
+ utils.startWifiTethering(callback);
+ callback.getCurrentValidUpstream();
+ utils.stopWifiTethering(callback);
+ } finally {
+ utils.unregisterTetheringEventCallback(callback);
+ }
+ }
+
@Before
public void setUp() throws Exception {
mHandlerThread = new HandlerThread(getClass().getSimpleName());
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index 905b8fa..a104084 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -138,10 +138,11 @@
}
switch (proto) {
- case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6
- case IPPROTO_UDP: // address means there is no need to update their checksums.
- case IPPROTO_GRE: // We do not need to bother looking at GRE/ESP headers,
- case IPPROTO_ESP: // since there is never a checksum to update.
+ case IPPROTO_TCP: // For TCP, UDP & UDPLITE the checksum neutrality of the chosen
+ case IPPROTO_UDP: // IPv6 address means there is no need to update their checksums.
+ case IPPROTO_UDPLITE: //
+ case IPPROTO_GRE: // We do not need to bother looking at GRE/ESP headers,
+ case IPPROTO_ESP: // since there is never a checksum to update.
break;
default: // do not know how to handle anything else
@@ -328,12 +329,13 @@
if (ip4->frag_off & ~htons(IP_DF)) return TC_ACT_PIPE;
switch (ip4->protocol) {
- case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6
- case IPPROTO_GRE: // address means there is no need to update their checksums.
- case IPPROTO_ESP: // We do not need to bother looking at GRE/ESP headers,
- break; // since there is never a checksum to update.
+ case IPPROTO_TCP: // For TCP, UDP & UDPLITE the checksum neutrality of the chosen
+ case IPPROTO_UDPLITE: // IPv6 address means there is no need to update their checksums.
+ case IPPROTO_GRE: // We do not need to bother looking at GRE/ESP headers,
+ case IPPROTO_ESP: // since there is never a checksum to update.
+ break;
- case IPPROTO_UDP: // See above comment, but must also have UDP header...
+ case IPPROTO_UDP: // See above comment, but must also have UDP header...
if (data + sizeof(*ip4) + sizeof(struct udphdr) > data_end) return TC_ACT_PIPE;
const struct udphdr* uh = (const struct udphdr*)(ip4 + 1);
// If IPv4/UDP checksum is 0 then fallback to clatd so it can calculate the
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index ffa2857..9520ef6 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -82,6 +82,17 @@
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
+// The filegroup lists files that are necessary for verifying building mdns as a standalone,
+// for use with service-connectivity-mdns-standalone-build-test
+filegroup {
+ name: "framework-connectivity-t-mdns-standalone-build-sources",
+ srcs: [
+ "src/android/net/nsd/OffloadEngine.java",
+ "src/android/net/nsd/OffloadServiceInfo.java",
+ ],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
java_library {
name: "framework-connectivity-t-pre-jarjar",
defaults: ["framework-connectivity-t-defaults"],
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index 87b0a64..64762b4 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -348,3 +348,43 @@
}
+package android.net.nsd {
+
+ public final class NsdManager {
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void registerOffloadEngine(@NonNull String, long, long, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.OffloadEngine);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void unregisterOffloadEngine(@NonNull android.net.nsd.OffloadEngine);
+ }
+
+ public interface OffloadEngine {
+ method public void onOffloadServiceRemoved(@NonNull android.net.nsd.OffloadServiceInfo);
+ method public void onOffloadServiceUpdated(@NonNull android.net.nsd.OffloadServiceInfo);
+ field public static final int OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK = 1; // 0x1
+ field public static final int OFFLOAD_TYPE_FILTER_QUERIES = 2; // 0x2
+ field public static final int OFFLOAD_TYPE_FILTER_REPLIES = 4; // 0x4
+ field public static final int OFFLOAD_TYPE_REPLY = 1; // 0x1
+ }
+
+ public final class OffloadServiceInfo implements android.os.Parcelable {
+ ctor public OffloadServiceInfo(@NonNull android.net.nsd.OffloadServiceInfo.Key, @NonNull java.util.List<java.lang.String>, @NonNull String, @Nullable byte[], @IntRange(from=0, to=java.lang.Integer.MAX_VALUE) int, long);
+ method public int describeContents();
+ method @NonNull public String getHostname();
+ method @NonNull public android.net.nsd.OffloadServiceInfo.Key getKey();
+ method @Nullable public byte[] getOffloadPayload();
+ method public long getOffloadType();
+ method public int getPriority();
+ method @NonNull public java.util.List<java.lang.String> getSubtypes();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.nsd.OffloadServiceInfo> CREATOR;
+ }
+
+ public static final class OffloadServiceInfo.Key implements android.os.Parcelable {
+ ctor public OffloadServiceInfo.Key(@NonNull String, @NonNull String);
+ method public int describeContents();
+ method @NonNull public String getServiceName();
+ method @NonNull public String getServiceType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.nsd.OffloadServiceInfo.Key> CREATOR;
+ }
+
+}
+
diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java
index 8719960..4f816c5 100644
--- a/framework-t/src/android/net/NetworkStats.java
+++ b/framework-t/src/android/net/NetworkStats.java
@@ -46,6 +46,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.function.Function;
import java.util.function.Predicate;
/**
@@ -455,6 +456,41 @@
return operations;
}
+ /**
+ * Set Key fields for this entry.
+ *
+ * @return this object.
+ * @hide
+ */
+ private Entry setKeys(@Nullable String iface, int uid, @State int set,
+ int tag, @Meteredness int metered, @Roaming int roaming,
+ @DefaultNetwork int defaultNetwork) {
+ this.iface = iface;
+ this.uid = uid;
+ this.set = set;
+ this.tag = tag;
+ this.metered = metered;
+ this.roaming = roaming;
+ this.defaultNetwork = defaultNetwork;
+ return this;
+ }
+
+ /**
+ * Set Value fields for this entry.
+ *
+ * @return this object.
+ * @hide
+ */
+ private Entry setValues(long rxBytes, long rxPackets, long txBytes, long txPackets,
+ long operations) {
+ this.rxBytes = rxBytes;
+ this.rxPackets = rxPackets;
+ this.txBytes = txBytes;
+ this.txPackets = txPackets;
+ this.operations = operations;
+ return this;
+ }
+
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
@@ -1111,7 +1147,8 @@
entry.txPackets = left.txPackets[i];
entry.operations = left.operations[i];
- // find remote row that matches, and subtract
+ // Find the remote row that matches and subtract.
+ // The returned row must be uniquely matched.
final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag,
entry.metered, entry.roaming, entry.defaultNetwork, i);
if (j != -1) {
@@ -1210,30 +1247,21 @@
* @hide
*/
public NetworkStats groupedByIface() {
- final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+ // Keep backward compatibility where the method filtered out tagged stats and keep the
+ // operation counts as 0. The method used to deal with uid snapshot where tagged and
+ // non-tagged stats were mixed. And this method was also in Android O API list,
+ // so it is possible OEM can access it.
+ final NetworkStats copiedStats = this.clone();
+ copiedStats.filter(e -> e.getTag() == TAG_NONE);
- final Entry entry = new Entry();
- entry.uid = UID_ALL;
- entry.set = SET_ALL;
- entry.tag = TAG_NONE;
- entry.metered = METERED_ALL;
- entry.roaming = ROAMING_ALL;
- entry.defaultNetwork = DEFAULT_NETWORK_ALL;
- entry.operations = 0L;
+ final Entry temp = new Entry();
+ final NetworkStats mappedStats = copiedStats.map(entry -> temp.setKeys(entry.getIface(),
+ UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL));
- for (int i = 0; i < size; i++) {
- // skip specific tags, since already counted in TAG_NONE
- if (tag[i] != TAG_NONE) continue;
-
- entry.iface = iface[i];
- entry.rxBytes = rxBytes[i];
- entry.rxPackets = rxPackets[i];
- entry.txBytes = txBytes[i];
- entry.txPackets = txPackets[i];
- stats.combineValues(entry);
+ for (int i = 0; i < mappedStats.size; i++) {
+ mappedStats.operations[i] = 0L;
}
-
- return stats;
+ return mappedStats;
}
/**
@@ -1242,30 +1270,15 @@
* @hide
*/
public NetworkStats groupedByUid() {
- final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+ // Keep backward compatibility where the method filtered out tagged stats. The method used
+ // to deal with uid snapshot where tagged and non-tagged stats were mixed. And
+ // this method is also in Android O API list, so it is possible OEM can access it.
+ final NetworkStats copiedStats = this.clone();
+ copiedStats.filter(e -> e.getTag() == TAG_NONE);
- final Entry entry = new Entry();
- entry.iface = IFACE_ALL;
- entry.set = SET_ALL;
- entry.tag = TAG_NONE;
- entry.metered = METERED_ALL;
- entry.roaming = ROAMING_ALL;
- entry.defaultNetwork = DEFAULT_NETWORK_ALL;
-
- for (int i = 0; i < size; i++) {
- // skip specific tags, since already counted in TAG_NONE
- if (tag[i] != TAG_NONE) continue;
-
- entry.uid = uid[i];
- entry.rxBytes = rxBytes[i];
- entry.rxPackets = rxPackets[i];
- entry.txBytes = txBytes[i];
- entry.txPackets = txPackets[i];
- entry.operations = operations[i];
- stats.combineValues(entry);
- }
-
- return stats;
+ final Entry temp = new Entry();
+ return copiedStats.map(entry -> temp.setKeys(IFACE_ALL,
+ entry.getUid(), SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL));
}
/**
@@ -1292,13 +1305,37 @@
/**
* Removes the interface name from all entries.
- * This mutates the original structure in place.
+ * This returns a newly constructed object instead of mutating the original structure.
* @hide
*/
- public void clearInterfaces() {
- for (int i = 0; i < size; i++) {
- iface[i] = null;
+ @NonNull
+ public NetworkStats clearInterfaces() {
+ final Entry temp = new Entry();
+ return map(entry -> temp.setKeys(IFACE_ALL, entry.getUid(), entry.getSet(),
+ entry.getTag(), entry.getMetered(), entry.getRoaming(), entry.getDefaultNetwork()));
+ }
+
+ /**
+ * Returns a new NetworkStats object where entries are transformed.
+ *
+ * Note that because NetworkStats is more akin to a map than to a list,
+ * the entries will be grouped after they are mapped by the key fields,
+ * e.g. uid, set, tag, defaultNetwork.
+ * Value fields with the same keys will be added together.
+ */
+ @NonNull
+ private NetworkStats map(@NonNull Function<Entry, Entry> f) {
+ final NetworkStats ret = new NetworkStats(0, 1);
+ for (Entry e : this) {
+ final NetworkStats.Entry transformed = f.apply(e);
+ if (transformed == e) {
+ throw new IllegalStateException("A new entry must be created.");
+ }
+ transformed.setValues(e.getRxBytes(), e.getRxPackets(), e.getTxBytes(),
+ e.getTxPackets(), e.getOperations());
+ ret.combineValues(transformed);
}
+ return ret;
}
/**
diff --git a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
index 5533154..e671db1 100644
--- a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
+++ b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
@@ -17,6 +17,7 @@
package android.net.nsd;
import android.net.nsd.INsdManagerCallback;
+import android.net.nsd.IOffloadEngine;
import android.net.nsd.NsdServiceInfo;
import android.os.Messenger;
@@ -35,4 +36,6 @@
void stopResolution(int listenerKey);
void registerServiceInfoCallback(int listenerKey, in NsdServiceInfo serviceInfo);
void unregisterServiceInfoCallback(int listenerKey);
+ void registerOffloadEngine(String ifaceName, in IOffloadEngine cb, long offloadCapabilities, long offloadType);
+ void unregisterOffloadEngine(in IOffloadEngine cb);
}
\ No newline at end of file
diff --git a/framework-t/src/android/net/nsd/IOffloadEngine.aidl b/framework-t/src/android/net/nsd/IOffloadEngine.aidl
new file mode 100644
index 0000000..2af733d
--- /dev/null
+++ b/framework-t/src/android/net/nsd/IOffloadEngine.aidl
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd;
+
+import android.net.nsd.OffloadServiceInfo;
+
+/**
+ * Callbacks from NsdService to inform providers of packet offload.
+ *
+ * @hide
+ */
+oneway interface IOffloadEngine {
+ void onOffloadServiceUpdated(in OffloadServiceInfo info);
+ void onOffloadServiceRemoved(in OffloadServiceInfo info);
+}
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 2930cbd..ef0e34b 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -16,6 +16,9 @@
package android.net.nsd;
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.NETWORK_STACK;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
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;
@@ -25,6 +28,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.compat.CompatChanges;
import android.content.Context;
@@ -45,9 +49,11 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.CollectionUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -246,6 +252,10 @@
public static final int UNREGISTER_SERVICE_CALLBACK = 31;
/** @hide */
public static final int UNREGISTER_SERVICE_CALLBACK_SUCCEEDED = 32;
+ /** @hide */
+ public static final int REGISTER_OFFLOAD_ENGINE = 33;
+ /** @hide */
+ public static final int UNREGISTER_OFFLOAD_ENGINE = 34;
/** Dns based service discovery protocol */
public static final int PROTOCOL_DNS_SD = 0x0001;
@@ -313,8 +323,107 @@
private final ArrayMap<Integer, PerNetworkDiscoveryTracker>
mPerNetworkDiscoveryMap = new ArrayMap<>();
+ @GuardedBy("mOffloadEngines")
+ private final ArrayList<OffloadEngineProxy> mOffloadEngines = new ArrayList<>();
private final ServiceHandler mHandler;
+ private static class OffloadEngineProxy extends IOffloadEngine.Stub {
+ private final Executor mExecutor;
+ private final OffloadEngine mEngine;
+
+ private OffloadEngineProxy(@NonNull Executor executor, @NonNull OffloadEngine appCb) {
+ mExecutor = executor;
+ mEngine = appCb;
+ }
+
+ @Override
+ public void onOffloadServiceUpdated(OffloadServiceInfo info) {
+ mExecutor.execute(() -> mEngine.onOffloadServiceUpdated(info));
+ }
+
+ @Override
+ public void onOffloadServiceRemoved(OffloadServiceInfo info) {
+ mExecutor.execute(() -> mEngine.onOffloadServiceRemoved(info));
+ }
+ }
+
+ /**
+ * Registers an OffloadEngine with NsdManager.
+ *
+ * A caller can register itself as an OffloadEngine if it supports mDns hardware offload.
+ * The caller must implement the {@link OffloadEngine} interface and update hardware offload
+ * state property when the {@link OffloadEngine#onOffloadServiceUpdated} and
+ * {@link OffloadEngine#onOffloadServiceRemoved} callback are called. Multiple engines may be
+ * registered for the same interface, and that the same engine cannot be registered twice.
+ *
+ * @param ifaceName indicates which network interface the hardware offload runs on
+ * @param offloadType the type of offload that the offload engine support
+ * @param offloadCapability the capabilities of the offload engine
+ * @param executor the executor on which to receive the offload callbacks
+ * @param engine the OffloadEngine that will receive the offload callbacks
+ * @throws IllegalStateException if the engine is already registered.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {NETWORK_SETTINGS, PERMISSION_MAINLINE_NETWORK_STACK,
+ NETWORK_STACK})
+ public void registerOffloadEngine(@NonNull String ifaceName,
+ @OffloadEngine.OffloadType long offloadType,
+ @OffloadEngine.OffloadCapability long offloadCapability, @NonNull Executor executor,
+ @NonNull OffloadEngine engine) {
+ Objects.requireNonNull(ifaceName);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(engine);
+ final OffloadEngineProxy cbImpl = new OffloadEngineProxy(executor, engine);
+ synchronized (mOffloadEngines) {
+ if (CollectionUtils.contains(mOffloadEngines, impl -> impl.mEngine == engine)) {
+ throw new IllegalStateException("This engine is already registered");
+ }
+ mOffloadEngines.add(cbImpl);
+ }
+ try {
+ mService.registerOffloadEngine(ifaceName, cbImpl, offloadCapability, offloadType);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Unregisters an OffloadEngine from NsdService.
+ *
+ * A caller can unregister itself as an OffloadEngine when it doesn't want to receive the
+ * callback anymore. The OffloadEngine must have been previously registered with the system
+ * using the {@link NsdManager#registerOffloadEngine} method.
+ *
+ * @param engine OffloadEngine object to be removed from NsdService
+ * @throws IllegalStateException if the engine is not registered.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {NETWORK_SETTINGS, PERMISSION_MAINLINE_NETWORK_STACK,
+ NETWORK_STACK})
+ public void unregisterOffloadEngine(@NonNull OffloadEngine engine) {
+ Objects.requireNonNull(engine);
+ final OffloadEngineProxy cbImpl;
+ synchronized (mOffloadEngines) {
+ final int index = CollectionUtils.indexOf(mOffloadEngines,
+ impl -> impl.mEngine == engine);
+ if (index < 0) {
+ throw new IllegalStateException("This engine is not registered");
+ }
+ cbImpl = mOffloadEngines.remove(index);
+ }
+
+ try {
+ mService.unregisterOffloadEngine(cbImpl);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
private class PerNetworkDiscoveryTracker {
final String mServiceType;
final int mProtocolType;
diff --git a/framework-t/src/android/net/nsd/OffloadEngine.java b/framework-t/src/android/net/nsd/OffloadEngine.java
new file mode 100644
index 0000000..b566b13
--- /dev/null
+++ b/framework-t/src/android/net/nsd/OffloadEngine.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd;
+
+import android.annotation.LongDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * OffloadEngine is an interface for mDns hardware offloading.
+ *
+ * An offloading engine can interact with the firmware code to instruct the hardware to
+ * offload some of mDns network traffic before it reached android OS. This can improve the
+ * power consumption performance of the host system by not always waking up the OS to handle
+ * the mDns packet when the device is in low power mode.
+ *
+ * @hide
+ */
+@SystemApi
+public interface OffloadEngine {
+ /**
+ * Indicates that the OffloadEngine can generate replies to mDns queries.
+ *
+ * @see OffloadServiceInfo#getOffloadPayload()
+ */
+ int OFFLOAD_TYPE_REPLY = 1;
+ /**
+ * Indicates that the OffloadEngine can filter and drop mDns queries.
+ */
+ int OFFLOAD_TYPE_FILTER_QUERIES = 1 << 1;
+ /**
+ * Indicates that the OffloadEngine can filter and drop mDns replies. It can allow mDns packets
+ * to be received even when no app holds a {@link android.net.wifi.WifiManager.MulticastLock}.
+ */
+ int OFFLOAD_TYPE_FILTER_REPLIES = 1 << 2;
+
+ /**
+ * Indicates that the OffloadEngine can bypass multicast lock.
+ */
+ int OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK = 1;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @LongDef(flag = true, prefix = {"OFFLOAD_TYPE"}, value = {
+ OFFLOAD_TYPE_REPLY,
+ OFFLOAD_TYPE_FILTER_QUERIES,
+ OFFLOAD_TYPE_FILTER_REPLIES,
+ })
+ @interface OffloadType {}
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @LongDef(flag = true, prefix = {"OFFLOAD_CAPABILITY"}, value = {
+ OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK
+ })
+ @interface OffloadCapability {}
+
+ /**
+ * To be called when the OffloadServiceInfo is added or updated.
+ *
+ * @param info The OffloadServiceInfo to add or update.
+ */
+ void onOffloadServiceUpdated(@NonNull OffloadServiceInfo info);
+
+ /**
+ * To be called when the OffloadServiceInfo is removed.
+ *
+ * @param info The OffloadServiceInfo to remove.
+ */
+ void onOffloadServiceRemoved(@NonNull OffloadServiceInfo info);
+}
diff --git a/framework-t/src/android/net/nsd/OffloadServiceInfo.java b/framework-t/src/android/net/nsd/OffloadServiceInfo.java
new file mode 100644
index 0000000..7bd5a7d
--- /dev/null
+++ b/framework-t/src/android/net/nsd/OffloadServiceInfo.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.net.module.util.HexDump;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The OffloadServiceInfo class contains all the necessary information the OffloadEngine needs to
+ * know about how to offload an mDns service. The OffloadServiceInfo is keyed on
+ * {@link OffloadServiceInfo.Key} which is a (serviceName, serviceType) pair.
+ *
+ * @hide
+ */
+@SystemApi
+public final class OffloadServiceInfo implements Parcelable {
+ @NonNull
+ private final Key mKey;
+ @NonNull
+ private final String mHostname;
+ @NonNull final List<String> mSubtypes;
+ @Nullable
+ private final byte[] mOffloadPayload;
+ private final int mPriority;
+ private final long mOffloadType;
+
+ /**
+ * Creates a new OffloadServiceInfo object with the specified parameters.
+ *
+ * @param key The key of the service.
+ * @param subtypes The list of subTypes of the service.
+ * @param hostname The name of the host offering the service. It is meaningful only when
+ * offloadType contains OFFLOAD_REPLY.
+ * @param offloadPayload The raw udp payload for hardware offloading.
+ * @param priority The priority of the service, @see #getPriority.
+ * @param offloadType The type of the service offload, @see #getOffloadType.
+ */
+ public OffloadServiceInfo(@NonNull Key key,
+ @NonNull List<String> subtypes, @NonNull String hostname,
+ @Nullable byte[] offloadPayload,
+ @IntRange(from = 0, to = Integer.MAX_VALUE) int priority,
+ @OffloadEngine.OffloadType long offloadType) {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(subtypes);
+ Objects.requireNonNull(hostname);
+ mKey = key;
+ mSubtypes = subtypes;
+ mHostname = hostname;
+ mOffloadPayload = offloadPayload;
+ mPriority = priority;
+ mOffloadType = offloadType;
+ }
+
+ /**
+ * Creates a new OffloadServiceInfo object from a Parcel.
+ *
+ * @param in The Parcel to read the object from.
+ *
+ * @hide
+ */
+ public OffloadServiceInfo(@NonNull Parcel in) {
+ mKey = in.readParcelable(Key.class.getClassLoader(),
+ Key.class);
+ mSubtypes = in.createStringArrayList();
+ mHostname = in.readString();
+ mOffloadPayload = in.createByteArray();
+ mPriority = in.readInt();
+ mOffloadType = in.readLong();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mKey, flags);
+ dest.writeStringList(mSubtypes);
+ dest.writeString(mHostname);
+ dest.writeByteArray(mOffloadPayload);
+ dest.writeInt(mPriority);
+ dest.writeLong(mOffloadType);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<OffloadServiceInfo> CREATOR = new Creator<OffloadServiceInfo>() {
+ @Override
+ public OffloadServiceInfo createFromParcel(Parcel in) {
+ return new OffloadServiceInfo(in);
+ }
+
+ @Override
+ public OffloadServiceInfo[] newArray(int size) {
+ return new OffloadServiceInfo[size];
+ }
+ };
+
+ /**
+ * Get the {@link Key}.
+ */
+ @NonNull
+ public Key getKey() {
+ return mKey;
+ }
+
+ /**
+ * Get the host name. (e.g. "Android.local" )
+ */
+ @NonNull
+ public String getHostname() {
+ return mHostname;
+ }
+
+ /**
+ * Get the service subtypes. (e.g. ["_ann"] )
+ */
+ @NonNull
+ public List<String> getSubtypes() {
+ return Collections.unmodifiableList(mSubtypes);
+ }
+
+ /**
+ * Get the raw udp payload that the OffloadEngine can use to directly reply the incoming query.
+ * <p>
+ * It is null if the OffloadEngine can not handle transmit. The packet must be sent as-is when
+ * replying to query.
+ */
+ @Nullable
+ public byte[] getOffloadPayload() {
+ if (mOffloadPayload == null) {
+ return null;
+ } else {
+ return mOffloadPayload.clone();
+ }
+ }
+
+ /**
+ * Get the offloadType.
+ * <p>
+ * For example, if the {@link com.android.server.NsdService} requests the OffloadEngine to both
+ * filter the mDNS queries and replies, the {@link #mOffloadType} =
+ * ({@link OffloadEngine#OFFLOAD_TYPE_FILTER_QUERIES} |
+ * {@link OffloadEngine#OFFLOAD_TYPE_FILTER_REPLIES}).
+ */
+ @OffloadEngine.OffloadType public long getOffloadType() {
+ return mOffloadType;
+ }
+
+ /**
+ * Get the priority for the OffloadServiceInfo.
+ * <p>
+ * When OffloadEngine don't have enough resource
+ * (e.g. not enough memory) to offload all the OffloadServiceInfo. The OffloadServiceInfo
+ * having lower priority values should be handled by the OffloadEngine first.
+ */
+ public int getPriority() {
+ return mPriority;
+ }
+
+ /**
+ * Only for debug purpose, the string can be long as the raw packet is dump in the string.
+ */
+ @Override
+ public String toString() {
+ return String.format(
+ "OffloadServiceInfo{ mOffloadServiceInfoKey=%s, mHostName=%s, "
+ + "mOffloadPayload=%s, mPriority=%d, mOffloadType=%d, mSubTypes=%s }",
+ mKey,
+ mHostname, HexDump.dumpHexString(mOffloadPayload), mPriority,
+ mOffloadType, mSubtypes.toString());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof OffloadServiceInfo)) return false;
+ OffloadServiceInfo that = (OffloadServiceInfo) o;
+ return mPriority == that.mPriority && mOffloadType == that.mOffloadType
+ && mKey.equals(that.mKey)
+ && mHostname.equals(
+ that.mHostname) && Arrays.equals(mOffloadPayload,
+ that.mOffloadPayload)
+ && mSubtypes.equals(that.mSubtypes);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(mKey, mHostname, mPriority,
+ mOffloadType, mSubtypes);
+ result = 31 * result + Arrays.hashCode(mOffloadPayload);
+ return result;
+ }
+
+ /**
+ * The {@link OffloadServiceInfo.Key} is the (serviceName, serviceType) pair.
+ */
+ public static final class Key implements Parcelable {
+ @NonNull
+ private final String mServiceName;
+ @NonNull
+ private final String mServiceType;
+
+ /**
+ * Creates a new OffloadServiceInfoKey object with the specified parameters.
+ *
+ * @param serviceName The name of the service.
+ * @param serviceType The type of the service.
+ */
+ public Key(@NonNull String serviceName, @NonNull String serviceType) {
+ Objects.requireNonNull(serviceName);
+ Objects.requireNonNull(serviceType);
+ mServiceName = serviceName;
+ mServiceType = serviceType;
+ }
+
+ /**
+ * Creates a new OffloadServiceInfoKey object from a Parcel.
+ *
+ * @param in The Parcel to read the object from.
+ *
+ * @hide
+ */
+ public Key(@NonNull Parcel in) {
+ mServiceName = in.readString();
+ mServiceType = in.readString();
+ }
+ /**
+ * Get the service name. (e.g. "NsdChat")
+ */
+ @NonNull
+ public String getServiceName() {
+ return mServiceName;
+ }
+
+ /**
+ * Get the service type. (e.g. "_http._tcp.local" )
+ */
+ @NonNull
+ public String getServiceType() {
+ return mServiceType;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mServiceName);
+ dest.writeString(mServiceType);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<Key> CREATOR =
+ new Creator<Key>() {
+ @Override
+ public Key createFromParcel(Parcel in) {
+ return new Key(in);
+ }
+
+ @Override
+ public Key[] newArray(int size) {
+ return new Key[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Key)) return false;
+ Key that = (Key) o;
+ return Objects.equals(mServiceName, that.mServiceName) && Objects.equals(
+ mServiceType, that.mServiceType);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mServiceName, mServiceType);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("OffloadServiceInfoKey{ mServiceName=%s, mServiceType=%s }",
+ mServiceName, mServiceType);
+ }
+ }
+}
diff --git a/framework/Android.bp b/framework/Android.bp
index 123f02a..7897492 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -62,6 +62,7 @@
":framework-connectivity-sources",
":net-utils-framework-common-srcs",
":framework-connectivity-api-shared-srcs",
+ ":framework-remoteauth-java-sources",
],
aidl: {
generate_get_transaction_name: true,
@@ -109,8 +110,8 @@
"framework-connectivity-defaults",
],
static_libs: [
- "cronet_aml_api_java",
- "cronet_aml_java",
+ "httpclient_api",
+ "httpclient_impl",
],
libs: [
// This cannot be in the defaults clause above because if it were, it would be used
@@ -125,12 +126,12 @@
java_defaults {
name: "CronetJavaDefaults",
- srcs: [":cronet_aml_api_sources"],
+ srcs: [":httpclient_api_sources"],
libs: [
"androidx.annotation_annotation",
],
impl_only_static_libs: [
- "cronet_aml_java",
+ "httpclient_impl",
],
}
@@ -150,6 +151,7 @@
"//packages/modules/Connectivity/framework-t",
"//packages/modules/Connectivity/service",
"//packages/modules/Connectivity/service-t",
+ "//packages/modules/Connectivity/remoteauth:__subpackages__",
"//frameworks/base/packages/Connectivity/service",
"//frameworks/base",
diff --git a/framework/aidl-export/android/net/nsd/OffloadServiceInfo.aidl b/framework/aidl-export/android/net/nsd/OffloadServiceInfo.aidl
new file mode 100644
index 0000000..aa7aef2
--- /dev/null
+++ b/framework/aidl-export/android/net/nsd/OffloadServiceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd;
+
+@JavaOnlyStableParcelable parcelable OffloadServiceInfo;
\ No newline at end of file
diff --git a/framework/jarjar-excludes.txt b/framework/jarjar-excludes.txt
index 9b48d57..1ac5e8e 100644
--- a/framework/jarjar-excludes.txt
+++ b/framework/jarjar-excludes.txt
@@ -27,4 +27,10 @@
# Don't touch anything that's already under android.net.http (cronet)
# This is required since android.net.http contains api classes and hidden classes.
# TODO: Remove this after hidden classes are moved to different package
-android\.net\.http\..+
\ No newline at end of file
+android\.net\.http\..+
+
+# TODO: OffloadServiceInfo is being added as an API, but wasn't an API yet in the first module
+# versions targeting U. Do not jarjar it such versions so that tests do not have to cover both
+# cases. This will be removed in an upcoming change marking it as API.
+android\.net\.nsd\.OffloadServiceInfo(\$.+)?
+android\.net\.nsd\.OffloadEngine(\$.+)?
diff --git a/framework/src/android/net/LinkAddress.java b/framework/src/android/net/LinkAddress.java
index 90f55b3..8376963 100644
--- a/framework/src/android/net/LinkAddress.java
+++ b/framework/src/android/net/LinkAddress.java
@@ -37,6 +37,8 @@
import android.os.SystemClock;
import android.util.Pair;
+import com.android.net.module.util.ConnectivityUtils;
+
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -146,11 +148,7 @@
* Per RFC 4193 section 8, fc00::/7 identifies these addresses.
*/
private boolean isIpv6ULA() {
- if (isIpv6()) {
- byte[] bytes = address.getAddress();
- return ((bytes[0] & (byte)0xfe) == (byte)0xfc);
- }
- return false;
+ return ConnectivityUtils.isIPv6ULA(address);
}
/**
diff --git a/framework/src/android/net/MacAddress.java b/framework/src/android/net/MacAddress.java
index 26a504a..049a425 100644
--- a/framework/src/android/net/MacAddress.java
+++ b/framework/src/android/net/MacAddress.java
@@ -127,7 +127,7 @@
/**
* Convert this MacAddress to a byte array.
*
- * The returned array is in network order. For example, if this MacAddress is 1:2:3:4:5:6,
+ * The returned array is in network order. For example, if this MacAddress is 01:02:03:04:05:06,
* the returned array is [1, 2, 3, 4, 5, 6].
*
* @return a byte array representation of this MacAddress.
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 92e9599..8e219a6 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -690,17 +690,10 @@
*/
public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35;
- private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
- private static final int ALL_VALID_CAPABILITIES;
- static {
- int caps = 0;
- for (int i = MIN_NET_CAPABILITY; i <= MAX_NET_CAPABILITY; ++i) {
- caps |= 1 << i;
- }
- ALL_VALID_CAPABILITIES = caps;
- }
+ // Set all bits up to the MAX_NET_CAPABILITY-th bit
+ private static final long ALL_VALID_CAPABILITIES = (2L << MAX_NET_CAPABILITY) - 1;
/**
* Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -2519,7 +2512,7 @@
}
private static boolean isValidCapability(@NetworkCapabilities.NetCapability int capability) {
- return capability >= MIN_NET_CAPABILITY && capability <= MAX_NET_CAPABILITY;
+ return capability >= 0 && capability <= MAX_NET_CAPABILITY;
}
private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) {
diff --git a/nearby/service/Android.bp b/nearby/service/Android.bp
index ef07bb9..684b133 100644
--- a/nearby/service/Android.bp
+++ b/nearby/service/Android.bp
@@ -104,7 +104,7 @@
],
sdk_version: "system_server_current",
// This is included in service-connectivity which is 30+
- // TODO: allow APEXes to have service jars with higher min_sdk than the APEX
+ // TODO (b/293613362): allow APEXes to have service jars with higher min_sdk than the APEX
// (service-connectivity is only used on 31+) and use 31 here
min_sdk_version: "30",
diff --git a/remoteauth/OWNERS b/remoteauth/OWNERS
new file mode 100644
index 0000000..25a32b9
--- /dev/null
+++ b/remoteauth/OWNERS
@@ -0,0 +1,14 @@
+# Bug component: 1145231
+# Bug template url: http://b/new?component=1145231&template=1715387
+billyhuang@google.com
+boetger@google.com
+casbor@google.com
+derekjedral@google.com
+dlm@google.com
+igorzas@google.com
+jacobhobbie@google.com
+jasonsun@google.com
+jianbing@google.com
+jinjiechen@google.com
+justinmcclain@google.com
+salilr@google.com
diff --git a/remoteauth/README.md b/remoteauth/README.md
new file mode 100644
index 0000000..384fcf7
--- /dev/null
+++ b/remoteauth/README.md
@@ -0,0 +1,47 @@
+# RemoteAuth Mainline Module
+
+This directory contains code for the RemoteAuth module.
+
+## Directory Structure
+
+`framework`
+ - Contains client side APIs and AIDL files.
+
+`jni`
+ - JNI wrapper for invoking Android APIs from native code.
+
+`native`
+ - Native code implementation for RemoteAuth module services.
+
+`service`
+ - Server side implementation for RemoteAuth module services.
+
+`tests`
+ - Unit/Multi devices tests for RemoteAuth module (both Java and native code).
+
+## IDE setup
+
+### AIDEGen
+
+AIDEGen is deprecated, prefer ASfP [go/asfp](http://go/asfp)
+```sh
+$ source build/envsetup.sh && lunch <TARGET>
+$ cd packages/modules/Connectivity
+$ aidegen .
+# This will launch Intellij project for RemoteAuth module.
+```
+
+### ASfP
+
+See full instructions for ASfP at [go/asfp-getting-started](http://go/asfp-getting-started)
+
+## Build and Install
+
+```sh
+$ source build/envsetup.sh && lunch <TARGET>
+$ m com.google.android.tethering deapexer
+$ $ANDROID_BUILD_TOP/out/host/linux-x86/bin/deapexer decompress --input \
+ ${ANDROID_PRODUCT_OUT}/system/apex/com.google.android.tethering.capex \
+ --output /tmp/tethering.apex
+$ adb install -r /tmp/tethering.apex
+```
diff --git a/remoteauth/TEST_MAPPING b/remoteauth/TEST_MAPPING
new file mode 100644
index 0000000..5ad8da6
--- /dev/null
+++ b/remoteauth/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+ "presubmit": [
+ {
+ "name": "RemoteAuthUnitTests"
+ }
+ ]
+ // TODO(b/193602229): uncomment once it's supported.
+ //"mainline-presubmit": [
+ // {
+ // "name": "RemoteAuthUnitTests[com.google.android.tethering.apex]"
+ // }
+ //]
+}
diff --git a/remoteauth/framework/Android.bp b/remoteauth/framework/Android.bp
new file mode 100644
index 0000000..48d10b6
--- /dev/null
+++ b/remoteauth/framework/Android.bp
@@ -0,0 +1,49 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Sources included in the framework-connectivity jar
+filegroup {
+ name: "framework-remoteauth-java-sources",
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.aidl",
+ ],
+ path: "java",
+ visibility: [
+ "//packages/modules/Connectivity/framework:__subpackages__",
+ ],
+}
+
+filegroup {
+ name: "framework-remoteauth-sources",
+ defaults: ["framework-sources-module-defaults"],
+ srcs: [
+ ":framework-remoteauth-java-sources",
+ ],
+}
+
+// Build of only framework-remoteauth (not as part of connectivity) for
+// unit tests
+java_library {
+ name: "framework-remoteauth-static",
+ srcs: [":framework-remoteauth-java-sources"],
+ sdk_version: "module_current",
+ libs: [],
+ static_libs: [],
+ visibility: ["//packages/modules/Connectivity/remoteauth/tests:__subpackages__"],
+}
diff --git a/remoteauth/framework/java/android/remoteauth/README.md b/remoteauth/framework/java/android/remoteauth/README.md
new file mode 100644
index 0000000..13fefee
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/README.md
@@ -0,0 +1 @@
+This is the source root for the RemoteAuth framework
\ No newline at end of file
diff --git a/remoteauth/framework/java/android/remoteauth/aidl/README.md b/remoteauth/framework/java/android/remoteauth/aidl/README.md
new file mode 100644
index 0000000..1e9422e
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/aidl/README.md
@@ -0,0 +1 @@
+This is where the RemoteAuth AIDL files will go
\ No newline at end of file
diff --git a/remoteauth/service/Android.bp b/remoteauth/service/Android.bp
new file mode 100644
index 0000000..5c5a2fb
--- /dev/null
+++ b/remoteauth/service/Android.bp
@@ -0,0 +1,59 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+ name: "remoteauth-service-srcs",
+ srcs: ["java/**/*.java"],
+}
+
+// Main lib for remoteauth services.
+java_library {
+ name: "service-remoteauth-pre-jarjar",
+ srcs: [":remoteauth-service-srcs"],
+
+ defaults: [
+ "framework-system-server-module-defaults"
+ ],
+ libs: [],
+ static_libs: [],
+ sdk_version: "system_server_current",
+ // This is included in service-connectivity which is 30+
+ // TODO (b/293613362): allow APEXes to have service jars with higher min_sdk than the APEX
+ // (service-connectivity is only used on 31+) and use 31 here
+ min_sdk_version: "30",
+
+ dex_preopt: {
+ enabled: false,
+ app_image: false,
+ },
+ visibility: [
+ "//packages/modules/RemoteAuth/apex",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ ],
+}
+
+genrule {
+ name: "statslog-remoteauth-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module remoteauth " +
+ " --javaPackage com.android.server.remoteauth.proto --javaClass RemoteAuthStatsLog" +
+ " --minApiLevel 33",
+ out: ["com/android/server/remoteauth/proto/RemoteAuthStatsLog.java"],
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/README.md b/remoteauth/service/java/com/android/server/remoteauth/README.md
new file mode 100644
index 0000000..423ab45
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/README.md
@@ -0,0 +1 @@
+This is the source root for the RemoteAuthService
\ No newline at end of file
diff --git a/remoteauth/tests/unit/Android.bp b/remoteauth/tests/unit/Android.bp
new file mode 100644
index 0000000..8c08a1b
--- /dev/null
+++ b/remoteauth/tests/unit/Android.bp
@@ -0,0 +1,48 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "RemoteAuthUnitTests",
+ defaults: ["mts-target-sdk-version-current"],
+ sdk_version: "test_current",
+ min_sdk_version: "31",
+
+ // Include all test java files.
+ srcs: ["src/**/*.java"],
+
+ libs: [
+ "android.test.base",
+ "android.test.mock",
+ "android.test.runner",
+ ],
+ compile_multilib: "both",
+
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "framework-remoteauth-static",
+ "junit",
+ "platform-test-annotations",
+ "service-remoteauth-pre-jarjar",
+ "truth-prebuilt",
+ ],
+ test_suites: [
+ "general-tests",
+ "mts-tethering",
+ ],
+}
diff --git a/remoteauth/tests/unit/AndroidManifest.xml b/remoteauth/tests/unit/AndroidManifest.xml
new file mode 100644
index 0000000..0449409
--- /dev/null
+++ b/remoteauth/tests/unit/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.remoteauth.test">
+
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.remoteauth.test"
+ android:label="RemoteAuth Mainline Module Tests" />
+</manifest>
diff --git a/remoteauth/tests/unit/AndroidTest.xml b/remoteauth/tests/unit/AndroidTest.xml
new file mode 100644
index 0000000..325fdc6
--- /dev/null
+++ b/remoteauth/tests/unit/AndroidTest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Runs RemoteAuth Mainline API Tests.">
+ <!-- Only run tests if the device under test is SDK version 33 (Android 13) or above. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk33ModuleController" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="RemoteAuthUnitTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="RemoteAuthUnitTests" />
+ <option name="config-descriptor:metadata" key="mainline-param"
+ value="com.google.android.tethering.apex" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.remoteauth.test" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+
+ <!-- Only run RemoteAuthUnitTests in MTS if the RemoteAuth Mainline module is installed. -->
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.tethering" />
+ </object>
+</configuration>
diff --git a/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java b/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java
new file mode 100644
index 0000000..5cf3e6b
--- /dev/null
+++ b/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.remoteauth;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link RemoteAuth}. */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RemoteAuthManagerTest {
+ @Test
+ public void testStub() {
+ assertTrue(true);
+ }
+}
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/RemoteAuthServiceTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/RemoteAuthServiceTest.java
new file mode 100644
index 0000000..c6199ff
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/RemoteAuthServiceTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.remoteauth;
+
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link RemoteAuthServer}. */
+@RunWith(AndroidJUnit4.class)
+public class RemoteAuthServiceTest {
+ @Test
+ public void testStub() {
+ assertTrue(true);
+ }
+}
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 7de749c..c277cf6 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -88,8 +88,9 @@
name: "service-connectivity-mdns-standalone-build-test",
sdk_version: "core_platform",
srcs: [
- ":service-mdns-droidstubs",
"src/com/android/server/connectivity/mdns/**/*.java",
+ ":framework-connectivity-t-mdns-standalone-build-sources",
+ ":service-mdns-droidstubs"
],
exclude_srcs: [
"src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java",
diff --git a/service-t/jni/com_android_server_net_NetworkStatsService.cpp b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
index dab9d07..8d8dc32 100644
--- a/service-t/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
@@ -34,6 +34,7 @@
using android::bpf::bpfGetUidStats;
using android::bpf::bpfGetIfaceStats;
+using android::bpf::bpfGetIfIndexStats;
using android::bpf::NetworkTraceHandler;
namespace android {
@@ -94,6 +95,15 @@
}
}
+static jlong nativeGetIfIndexStat(JNIEnv* env, jclass clazz, jint ifindex, jint type) {
+ Stats stats = {};
+ if (bpfGetIfIndexStats(ifindex, &stats) == 0) {
+ return getStatsType(&stats, (StatsType) type);
+ } else {
+ return UNKNOWN;
+ }
+}
+
static jlong nativeGetUidStat(JNIEnv* env, jclass clazz, jint uid, jint type) {
Stats stats = {};
@@ -111,6 +121,7 @@
static const JNINativeMethod gMethods[] = {
{"nativeGetTotalStat", "(I)J", (void*)nativeGetTotalStat},
{"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*)nativeGetIfaceStat},
+ {"nativeGetIfIndexStat", "(II)J", (void*)nativeGetIfIndexStat},
{"nativeGetUidStat", "(II)J", (void*)nativeGetUidStat},
{"nativeInitNetworkTracing", "()V", (void*)nativeInitNetworkTracing},
};
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
index 1bc8ca5..2e6e3e5 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -40,8 +40,19 @@
using base::Result;
+// This explicitly zero-initializes the relevant Stats fields.
+void InitStats(Stats* stats) {
+ stats->rxBytes = 0;
+ stats->rxPackets = 0;
+ stats->txBytes = 0;
+ stats->txPackets = 0;
+ stats->tcpRxPackets = -1;
+ stats->tcpTxPackets = -1;
+}
+
int bpfGetUidStatsInternal(uid_t uid, Stats* stats,
const BpfMap<uint32_t, StatsValue>& appUidStatsMap) {
+ InitStats(stats);
auto statsEntry = appUidStatsMap.readValue(uid);
if (statsEntry.ok()) {
stats->rxPackets = statsEntry.value().rxPackets;
@@ -61,9 +72,8 @@
int bpfGetIfaceStatsInternal(const char* iface, Stats* stats,
const BpfMap<uint32_t, StatsValue>& ifaceStatsMap,
const BpfMap<uint32_t, IfaceValue>& ifaceNameMap) {
+ InitStats(stats);
int64_t unknownIfaceBytesTotal = 0;
- stats->tcpRxPackets = -1;
- stats->tcpTxPackets = -1;
const auto processIfaceStats =
[iface, stats, &ifaceNameMap, &unknownIfaceBytesTotal](
const uint32_t& key,
@@ -95,6 +105,25 @@
return bpfGetIfaceStatsInternal(iface, stats, ifaceStatsMap, ifaceIndexNameMap);
}
+int bpfGetIfIndexStatsInternal(uint32_t ifindex, Stats* stats,
+ const BpfMap<uint32_t, StatsValue>& ifaceStatsMap) {
+ InitStats(stats);
+ auto statsEntry = ifaceStatsMap.readValue(ifindex);
+ if (statsEntry.ok()) {
+ stats->rxPackets = statsEntry.value().rxPackets;
+ stats->txPackets = statsEntry.value().txPackets;
+ stats->rxBytes = statsEntry.value().rxBytes;
+ stats->txBytes = statsEntry.value().txBytes;
+ return 0;
+ }
+ return (statsEntry.error().code() == ENOENT) ? 0 : -statsEntry.error().code();
+}
+
+int bpfGetIfIndexStats(int ifindex, Stats* stats) {
+ static BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
+ return bpfGetIfIndexStatsInternal(ifindex, stats, ifaceStatsMap);
+}
+
stats_line populateStatsEntry(const StatsKey& statsKey, const StatsValue& statsEntry,
const char* ifname) {
stats_line newLine;
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
index ccd3f5e..4f85d9b 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
@@ -275,6 +275,20 @@
expectStatsEqual(totalValue, totalResult);
}
+TEST_F(BpfNetworkStatsHelperTest, TestGetIfIndexStatsInternal) {
+ StatsValue value = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(IFACE_INDEX1, value, BPF_ANY));
+
+ Stats result = {};
+ ASSERT_EQ(0, bpfGetIfIndexStatsInternal(IFACE_INDEX1, &result, mFakeIfaceStatsMap));
+ expectStatsEqual(value, result);
+}
+
TEST_F(BpfNetworkStatsHelperTest, TestGetStatsDetail) {
updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
diff --git a/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp b/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
index c5f9631..ec63e41 100644
--- a/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
+++ b/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
@@ -119,7 +119,14 @@
// the session and delegates writing. The corresponding handler will write
// with the setting specified in the trace config.
NetworkTraceHandler::Trace([&](NetworkTraceHandler::TraceContext ctx) {
- ctx.GetDataSourceLocked()->Write(packets, ctx);
+ perfetto::LockedHandle<NetworkTraceHandler> handle =
+ ctx.GetDataSourceLocked();
+ // The underlying handle can be invalidated between when Trace starts
+ // and GetDataSourceLocked is called, but not while the LockedHandle
+ // exists and holds the lock. Check validity prior to use.
+ if (handle.valid()) {
+ handle->Write(packets, ctx);
+ }
});
});
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
index 133009f..0a9c012 100644
--- a/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
@@ -63,6 +63,9 @@
const BpfMap<uint32_t, StatsValue>& ifaceStatsMap,
const BpfMap<uint32_t, IfaceValue>& ifaceNameMap);
// For test only
+int bpfGetIfIndexStatsInternal(uint32_t ifindex, Stats* stats,
+ const BpfMap<uint32_t, StatsValue>& ifaceStatsMap);
+// For test only
int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>& lines,
const BpfMap<StatsKey, StatsValue>& statsMap,
const BpfMap<uint32_t, IfaceValue>& ifaceMap);
@@ -112,6 +115,7 @@
int bpfGetUidStats(uid_t uid, Stats* stats);
int bpfGetIfaceStats(const char* iface, Stats* stats);
+int bpfGetIfIndexStats(int ifindex, Stats* stats);
int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines);
int parseBpfNetworkStatsDev(std::vector<stats_line>* lines);
diff --git a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
new file mode 100644
index 0000000..4594f71
--- /dev/null
+++ b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.metrics;
+
+import static com.android.metrics.NetworkNsdReported.Builder;
+
+import android.stats.connectivity.MdnsQueryResult;
+import android.stats.connectivity.NsdEventType;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ConnectivityStatsLog;
+
+/**
+ * Class to record the NetworkNsdReported into statsd. Each client should create this class to
+ * report its data.
+ */
+public class NetworkNsdReportedMetrics {
+ // Whether this client is using legacy backend.
+ private final boolean mIsLegacy;
+ // The client id.
+ private final int mClientId;
+ private final Dependencies mDependencies;
+
+ public NetworkNsdReportedMetrics(boolean isLegacy, int clientId) {
+ this(isLegacy, clientId, new Dependencies());
+ }
+
+ @VisibleForTesting
+ NetworkNsdReportedMetrics(boolean isLegacy, int clientId, Dependencies dependencies) {
+ mIsLegacy = isLegacy;
+ mClientId = clientId;
+ mDependencies = dependencies;
+ }
+
+ /**
+ * Dependencies of NetworkNsdReportedMetrics, for injection in tests.
+ */
+ public static class Dependencies {
+
+ /**
+ * @see ConnectivityStatsLog
+ */
+ public void statsWrite(NetworkNsdReported event) {
+ ConnectivityStatsLog.write(ConnectivityStatsLog.NETWORK_NSD_REPORTED,
+ event.getIsLegacy(),
+ event.getClientId(),
+ event.getTransactionId(),
+ event.getIsKnownService(),
+ event.getType().getNumber(),
+ event.getEventDurationMillisec(),
+ event.getQueryResult().getNumber(),
+ event.getFoundServiceCount(),
+ event.getFoundCallbackCount(),
+ event.getLostCallbackCount(),
+ event.getRepliedRequestsCount(),
+ event.getSentQueryCount());
+ }
+ }
+
+ private Builder makeReportedBuilder() {
+ final Builder builder = NetworkNsdReported.newBuilder();
+ builder.setIsLegacy(mIsLegacy);
+ builder.setClientId(mClientId);
+ return builder;
+ }
+
+ /**
+ * Report service registration succeeded metric data.
+ *
+ * @param transactionId The transaction id of service registration.
+ * @param durationMs The duration of service registration success.
+ */
+ public void reportServiceRegistrationSucceeded(int transactionId, long durationMs) {
+ final Builder builder = makeReportedBuilder();
+ builder.setTransactionId(transactionId);
+ builder.setType(NsdEventType.NET_REGISTER);
+ builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_REGISTERED);
+ builder.setEventDurationMillisec(durationMs);
+ mDependencies.statsWrite(builder.build());
+ }
+
+ /**
+ * Report service registration failed metric data.
+ *
+ * @param transactionId The transaction id of service registration.
+ * @param durationMs The duration of service registration failed.
+ */
+ public void reportServiceRegistrationFailed(int transactionId, long durationMs) {
+ final Builder builder = makeReportedBuilder();
+ builder.setTransactionId(transactionId);
+ builder.setType(NsdEventType.NET_REGISTER);
+ builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_REGISTRATION_FAILED);
+ builder.setEventDurationMillisec(durationMs);
+ mDependencies.statsWrite(builder.build());
+ }
+
+ /**
+ * Report service unregistration success metric data.
+ *
+ * @param transactionId The transaction id of service registration.
+ * @param durationMs The duration of service stayed registered.
+ */
+ public void reportServiceUnregistration(int transactionId, long durationMs) {
+ final Builder builder = makeReportedBuilder();
+ builder.setTransactionId(transactionId);
+ builder.setType(NsdEventType.NET_REGISTER);
+ builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_UNREGISTERED);
+ builder.setEventDurationMillisec(durationMs);
+ // TODO: Report repliedRequestsCount
+ mDependencies.statsWrite(builder.build());
+ }
+}
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index b06e9cb..53782ac 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
@@ -26,6 +27,7 @@
import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
+import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -45,9 +47,12 @@
import android.net.nsd.INsdManager;
import android.net.nsd.INsdManagerCallback;
import android.net.nsd.INsdServiceConnector;
+import android.net.nsd.IOffloadEngine;
import android.net.nsd.MDnsManager;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.net.nsd.OffloadEngine;
+import android.net.nsd.OffloadServiceInfo;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Handler;
@@ -55,6 +60,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.DeviceConfig;
@@ -68,6 +74,7 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
+import com.android.metrics.NetworkNsdReportedMetrics;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.InetAddressUtils;
@@ -96,6 +103,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -160,6 +168,7 @@
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;
+ private static final int NO_TRANSACTION = -1;
private static final SharedLog LOGGER = new SharedLog("serviceDiscovery");
private final Context mContext;
@@ -176,6 +185,8 @@
private final MdnsSocketProvider mMdnsSocketProvider;
@NonNull
private final MdnsAdvertiser mAdvertiser;
+ @NonNull
+ private final Clock mClock;
private final SharedLog mServiceLogs = LOGGER.forSubComponent(TAG);
// WARNING : Accessing these values in any thread is not safe, it must only be changed in the
// state machine thread. If change this outside state machine, it will need to introduce
@@ -210,6 +221,24 @@
// The number of client that ever connected.
private int mClientNumberId = 1;
+ private final RemoteCallbackList<IOffloadEngine> mOffloadEngines =
+ new RemoteCallbackList<>();
+
+ private static class OffloadEngineInfo {
+ @NonNull final String mInterfaceName;
+ final long mOffloadCapabilities;
+ final long mOffloadType;
+ @NonNull final IOffloadEngine mOffloadEngine;
+
+ OffloadEngineInfo(@NonNull IOffloadEngine offloadEngine,
+ @NonNull String interfaceName, long capabilities, long offloadType) {
+ this.mOffloadEngine = offloadEngine;
+ this.mInterfaceName = interfaceName;
+ this.mOffloadCapabilities = capabilities;
+ this.mOffloadType = offloadType;
+ }
+ }
+
private static class MdnsListener implements MdnsServiceBrowserListener {
protected final int mClientRequestId;
protected final int mTransactionId;
@@ -528,8 +557,11 @@
try {
cb.asBinder().linkToDeath(arg.connector, 0);
final String tag = "Client" + arg.uid + "-" + mClientNumberId++;
+ final NetworkNsdReportedMetrics metrics =
+ mDeps.makeNetworkNsdReportedMetrics(
+ !arg.useJavaBackend, (int) mClock.elapsedRealtime());
cInfo = new ClientInfo(cb, arg.uid, arg.useJavaBackend,
- mServiceLogs.forSubComponent(tag));
+ mServiceLogs.forSubComponent(tag), metrics);
mClients.put(arg.connector, cInfo);
} catch (RemoteException e) {
Log.w(TAG, "Client request id " + clientRequestId
@@ -565,7 +597,7 @@
case NsdManager.REGISTER_SERVICE:
cInfo = getClientInfoForReply(msg);
if (cInfo != null) {
- cInfo.onRegisterServiceFailed(
+ cInfo.onRegisterServiceFailedImmediately(
clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
}
break;
@@ -647,8 +679,8 @@
private void storeLegacyRequestMap(int clientRequestId, int transactionId,
ClientInfo clientInfo, int what) {
- clientInfo.mClientRequests.put(
- clientRequestId, new LegacyClientRequest(transactionId, what));
+ clientInfo.mClientRequests.put(clientRequestId, new LegacyClientRequest(
+ transactionId, what, mClock, mClock.elapsedRealtime()));
mTransactionIdToClientInfoMap.put(transactionId, clientInfo);
// Remove the cleanup event because here comes a new request.
cancelStop();
@@ -656,8 +688,8 @@
private void storeAdvertiserRequestMap(int clientRequestId, int transactionId,
ClientInfo clientInfo, @Nullable Network requestedNetwork) {
- clientInfo.mClientRequests.put(clientRequestId,
- new AdvertiserClientRequest(transactionId, requestedNetwork));
+ clientInfo.mClientRequests.put(clientRequestId, new AdvertiserClientRequest(
+ transactionId, requestedNetwork, mClock, mClock.elapsedRealtime()));
mTransactionIdToClientInfoMap.put(transactionId, clientInfo);
updateMulticastLock();
}
@@ -680,8 +712,9 @@
private void storeDiscoveryManagerRequestMap(int clientRequestId, int transactionId,
MdnsListener listener, ClientInfo clientInfo,
@Nullable Network requestedNetwork) {
- clientInfo.mClientRequests.put(clientRequestId,
- new DiscoveryManagerRequest(transactionId, listener, requestedNetwork));
+ clientInfo.mClientRequests.put(clientRequestId, new DiscoveryManagerRequest(
+ transactionId, listener, requestedNetwork, mClock,
+ mClock.elapsedRealtime()));
mTransactionIdToClientInfoMap.put(transactionId, clientInfo);
updateMulticastLock();
}
@@ -710,6 +743,7 @@
final int transactionId;
final int clientRequestId = msg.arg2;
final ListenerArgs args;
+ final OffloadEngineInfo offloadEngineInfo;
switch (msg.what) {
case NsdManager.DISCOVER_SERVICES: {
if (DBG) Log.d(TAG, "Discover services");
@@ -834,7 +868,7 @@
}
if (requestLimitReached(clientInfo)) {
- clientInfo.onRegisterServiceFailed(
+ clientInfo.onRegisterServiceFailedImmediately(
clientRequestId, NsdManager.FAILURE_MAX_LIMIT);
break;
}
@@ -850,8 +884,8 @@
|| useAdvertiserForType(registerServiceType)) {
if (registerServiceType == null) {
Log.e(TAG, "Invalid service type: " + serviceType);
- clientInfo.onRegisterServiceFailed(clientRequestId,
- NsdManager.FAILURE_INTERNAL_ERROR);
+ clientInfo.onRegisterServiceFailedImmediately(
+ clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
break;
}
serviceInfo.setServiceType(registerServiceType);
@@ -878,7 +912,7 @@
// Return success after mDns reports success
} else {
unregisterService(transactionId);
- clientInfo.onRegisterServiceFailed(
+ clientInfo.onRegisterServiceFailedImmediately(
clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
}
@@ -910,10 +944,12 @@
// instead of looking at the flag value.
if (request instanceof AdvertiserClientRequest) {
mAdvertiser.removeService(transactionId);
- clientInfo.onUnregisterServiceSucceeded(clientRequestId);
+ clientInfo.onUnregisterServiceSucceeded(clientRequestId, transactionId,
+ request.calculateRequestDurationMs());
} else {
if (unregisterService(transactionId)) {
- clientInfo.onUnregisterServiceSucceeded(clientRequestId);
+ clientInfo.onUnregisterServiceSucceeded(clientRequestId,
+ transactionId, request.calculateRequestDurationMs());
} else {
clientInfo.onUnregisterServiceFailed(
clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
@@ -1103,6 +1139,16 @@
return NOT_HANDLED;
}
break;
+ case NsdManager.REGISTER_OFFLOAD_ENGINE:
+ offloadEngineInfo = (OffloadEngineInfo) msg.obj;
+ // TODO: Limits the number of registrations created by a given class.
+ mOffloadEngines.register(offloadEngineInfo.mOffloadEngine,
+ offloadEngineInfo);
+ // TODO: Sends all the existing OffloadServiceInfos back.
+ break;
+ case NsdManager.UNREGISTER_OFFLOAD_ENGINE:
+ mOffloadEngines.unregister((IOffloadEngine) msg.obj);
+ break;
default:
return NOT_HANDLED;
}
@@ -1177,12 +1223,18 @@
final RegistrationInfo info = (RegistrationInfo) obj;
final String name = info.serviceName;
servInfo = new NsdServiceInfo(name, null /* serviceType */);
- clientInfo.onRegisterServiceSucceeded(clientRequestId, servInfo);
+ final ClientRequest request =
+ clientInfo.mClientRequests.get(clientRequestId);
+ clientInfo.onRegisterServiceSucceeded(clientRequestId, servInfo,
+ transactionId, request.calculateRequestDurationMs());
break;
}
case IMDnsEventListener.SERVICE_REGISTRATION_FAILED:
- clientInfo.onRegisterServiceFailed(
- clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
+ final ClientRequest request =
+ clientInfo.mClientRequests.get(clientRequestId);
+ clientInfo.onRegisterServiceFailed(clientRequestId,
+ NsdManager.FAILURE_INTERNAL_ERROR, transactionId,
+ request.calculateRequestDurationMs());
break;
case IMDnsEventListener.SERVICE_RESOLVED: {
final ResolutionInfo info = (ResolutionInfo) obj;
@@ -1556,12 +1608,14 @@
mRunningAppActiveImportanceCutoff);
mMdnsSocketClient =
- new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider);
+ new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider,
+ LOGGER.forSubComponent("MdnsMultinetworkSocketClient"));
mMdnsDiscoveryManager = deps.makeMdnsDiscoveryManager(new ExecutorProvider(),
mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"));
handler.post(() -> mMdnsSocketClient.setCallback(mMdnsDiscoveryManager));
mAdvertiser = deps.makeMdnsAdvertiser(handler.getLooper(), mMdnsSocketProvider,
new AdvertiserCallback(), LOGGER.forSubComponent("MdnsAdvertiser"));
+ mClock = deps.makeClock();
}
/**
@@ -1651,6 +1705,21 @@
public int getCallingUid() {
return Binder.getCallingUid();
}
+
+ /**
+ * @see NetworkNsdReportedMetrics
+ */
+ public NetworkNsdReportedMetrics makeNetworkNsdReportedMetrics(
+ boolean isLegacy, int clientId) {
+ return new NetworkNsdReportedMetrics(isLegacy, clientId);
+ }
+
+ /**
+ * @see MdnsUtils.Clock
+ */
+ public Clock makeClock() {
+ return new Clock();
+ }
}
/**
@@ -1738,7 +1807,42 @@
}
}
+ private void sendOffloadServiceInfosUpdate(@NonNull String targetInterfaceName,
+ @NonNull OffloadServiceInfo offloadServiceInfo, boolean isRemove) {
+ final int count = mOffloadEngines.beginBroadcast();
+ try {
+ for (int i = 0; i < count; i++) {
+ final OffloadEngineInfo offloadEngineInfo =
+ (OffloadEngineInfo) mOffloadEngines.getBroadcastCookie(i);
+ final String interfaceName = offloadEngineInfo.mInterfaceName;
+ if (!targetInterfaceName.equals(interfaceName)
+ || ((offloadEngineInfo.mOffloadType
+ & offloadServiceInfo.getOffloadType()) == 0)) {
+ continue;
+ }
+ try {
+ if (isRemove) {
+ mOffloadEngines.getBroadcastItem(i).onOffloadServiceRemoved(
+ offloadServiceInfo);
+ } else {
+ mOffloadEngines.getBroadcastItem(i).onOffloadServiceUpdated(
+ offloadServiceInfo);
+ }
+ } catch (RemoteException e) {
+ // Can happen in regular cases, do not log a stacktrace
+ Log.i(TAG, "Failed to send offload callback, remote died", e);
+ }
+ }
+ } finally {
+ mOffloadEngines.finishBroadcast();
+ }
+ }
+
private class AdvertiserCallback implements MdnsAdvertiser.AdvertiserCallback {
+ // TODO: add a callback to notify when a service is being added on each interface (as soon
+ // as probing starts), and call mOffloadCallbacks. This callback is for
+ // OFFLOAD_CAPABILITY_FILTER_REPLIES offload type.
+
@Override
public void onRegisterServiceSucceeded(int transactionId, NsdServiceInfo registeredInfo) {
mServiceLogs.log("onRegisterServiceSucceeded: transactionId " + transactionId);
@@ -1751,7 +1855,9 @@
// onRegisterServiceSucceeded only has the service name in its info. This aligns with
// historical behavior.
final NsdServiceInfo cbInfo = new NsdServiceInfo(registeredInfo.getServiceName(), null);
- clientInfo.onRegisterServiceSucceeded(clientRequestId, cbInfo);
+ final ClientRequest request = clientInfo.mClientRequests.get(clientRequestId);
+ clientInfo.onRegisterServiceSucceeded(
+ clientRequestId, cbInfo, transactionId, request.calculateRequestDurationMs());
}
@Override
@@ -1761,8 +1867,21 @@
final int clientRequestId = getClientRequestIdOrLog(clientInfo, transactionId);
if (clientRequestId < 0) return;
+ final ClientRequest request = clientInfo.mClientRequests.get(clientRequestId);
+ clientInfo.onRegisterServiceFailed(clientRequestId, errorCode, transactionId,
+ request.calculateRequestDurationMs());
+ }
- clientInfo.onRegisterServiceFailed(clientRequestId, errorCode);
+ @Override
+ public void onOffloadStartOrUpdate(@NonNull String interfaceName,
+ @NonNull OffloadServiceInfo offloadServiceInfo) {
+ sendOffloadServiceInfosUpdate(interfaceName, offloadServiceInfo, false /* isRemove */);
+ }
+
+ @Override
+ public void onOffloadStop(@NonNull String interfaceName,
+ @NonNull OffloadServiceInfo offloadServiceInfo) {
+ sendOffloadServiceInfosUpdate(interfaceName, offloadServiceInfo, true /* isRemove */);
}
private ClientInfo getClientInfoOrLog(int transactionId) {
@@ -1884,6 +2003,32 @@
public void binderDied() {
mNsdStateMachine.sendMessage(
mNsdStateMachine.obtainMessage(NsdManager.UNREGISTER_CLIENT, this));
+
+ }
+
+ @Override
+ public void registerOffloadEngine(String ifaceName, IOffloadEngine cb,
+ @OffloadEngine.OffloadCapability long offloadCapabilities,
+ @OffloadEngine.OffloadType long offloadTypes) {
+ // TODO: Relax the permission because NETWORK_SETTINGS is a signature permission, and
+ // it may not be possible for all the callers of this API to have it.
+ PermissionUtils.enforceNetworkStackPermissionOr(mContext, NETWORK_SETTINGS);
+ Objects.requireNonNull(ifaceName);
+ Objects.requireNonNull(cb);
+ mNsdStateMachine.sendMessage(
+ mNsdStateMachine.obtainMessage(NsdManager.REGISTER_OFFLOAD_ENGINE,
+ new OffloadEngineInfo(cb, ifaceName, offloadCapabilities,
+ offloadTypes)));
+ }
+
+ @Override
+ public void unregisterOffloadEngine(IOffloadEngine cb) {
+ // TODO: Relax the permission because NETWORK_SETTINGS is a signature permission, and
+ // it may not be possible for all the callers of this API to have it.
+ PermissionUtils.enforceNetworkStackPermissionOr(mContext, NETWORK_SETTINGS);
+ Objects.requireNonNull(cb);
+ mNsdStateMachine.sendMessage(
+ mNsdStateMachine.obtainMessage(NsdManager.UNREGISTER_OFFLOAD_ENGINE, cb));
}
}
@@ -1967,25 +2112,41 @@
return IFACE_IDX_ANY;
}
- final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
- if (cm == null) {
- Log.wtf(TAG, "No ConnectivityManager for resolveService");
+ String interfaceName = getNetworkInterfaceName(network);
+ if (interfaceName == null) {
return IFACE_IDX_ANY;
}
- final LinkProperties lp = cm.getLinkProperties(network);
- if (lp == null) return IFACE_IDX_ANY;
+ return getNetworkInterfaceIndexByName(interfaceName);
+ }
+ private String getNetworkInterfaceName(@Nullable Network network) {
+ if (network == null) {
+ return null;
+ }
+ final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+ if (cm == null) {
+ Log.wtf(TAG, "No ConnectivityManager");
+ return null;
+ }
+ final LinkProperties lp = cm.getLinkProperties(network);
+ if (lp == null) {
+ return null;
+ }
// Only resolve on non-stacked interfaces
+ return lp.getInterfaceName();
+ }
+
+ private int getNetworkInterfaceIndexByName(final String ifaceName) {
final NetworkInterface iface;
try {
- iface = NetworkInterface.getByName(lp.getInterfaceName());
+ iface = NetworkInterface.getByName(ifaceName);
} catch (SocketException e) {
Log.e(TAG, "Error querying interface", e);
return IFACE_IDX_ANY;
}
if (iface == null) {
- Log.e(TAG, "Interface not found: " + lp.getInterfaceName());
+ Log.e(TAG, "Interface not found: " + ifaceName);
return IFACE_IDX_ANY;
}
@@ -2022,17 +2183,27 @@
private abstract static class ClientRequest {
private final int mTransactionId;
+ private final Clock mClock;
+ private final long mStartTimeMs;
- private ClientRequest(int transactionId) {
+ private ClientRequest(int transactionId, @NonNull Clock clock, long startTimeMs) {
mTransactionId = transactionId;
+ mClock = clock;
+ mStartTimeMs = startTimeMs;
+ }
+
+ public long calculateRequestDurationMs() {
+ final long stopTimeMs = mClock.elapsedRealtime();
+ return stopTimeMs - mStartTimeMs;
}
}
private static class LegacyClientRequest extends ClientRequest {
private final int mRequestCode;
- private LegacyClientRequest(int transactionId, int requestCode) {
- super(transactionId);
+ private LegacyClientRequest(int transactionId, int requestCode, @NonNull Clock clock,
+ long startTimeMs) {
+ super(transactionId, clock, startTimeMs);
mRequestCode = requestCode;
}
}
@@ -2041,8 +2212,9 @@
@Nullable
private final Network mRequestedNetwork;
- private JavaBackendClientRequest(int transactionId, @Nullable Network requestedNetwork) {
- super(transactionId);
+ private JavaBackendClientRequest(int transactionId, @Nullable Network requestedNetwork,
+ @NonNull Clock clock, long startTimeMs) {
+ super(transactionId, clock, startTimeMs);
mRequestedNetwork = requestedNetwork;
}
@@ -2053,8 +2225,9 @@
}
private static class AdvertiserClientRequest extends JavaBackendClientRequest {
- private AdvertiserClientRequest(int transactionId, @Nullable Network requestedNetwork) {
- super(transactionId, requestedNetwork);
+ private AdvertiserClientRequest(int transactionId, @Nullable Network requestedNetwork,
+ @NonNull Clock clock, long startTimeMs) {
+ super(transactionId, requestedNetwork, clock, startTimeMs);
}
}
@@ -2063,8 +2236,8 @@
private final MdnsListener mListener;
private DiscoveryManagerRequest(int transactionId, @NonNull MdnsListener listener,
- @Nullable Network requestedNetwork) {
- super(transactionId, requestedNetwork);
+ @Nullable Network requestedNetwork, @NonNull Clock clock, long startTimeMs) {
+ super(transactionId, requestedNetwork, clock, startTimeMs);
mListener = listener;
}
}
@@ -2087,14 +2260,17 @@
private final boolean mUseJavaBackend;
// Store client logs
private final SharedLog mClientLogs;
+ // Report the nsd metrics data
+ private final NetworkNsdReportedMetrics mMetrics;
private ClientInfo(INsdManagerCallback cb, int uid, boolean useJavaBackend,
- SharedLog sharedLog) {
+ SharedLog sharedLog, NetworkNsdReportedMetrics metrics) {
mCb = cb;
mUid = uid;
mUseJavaBackend = useJavaBackend;
mClientLogs = sharedLog;
mClientLogs.log("New client. useJavaBackend=" + useJavaBackend);
+ mMetrics = metrics;
}
@Override
@@ -2154,6 +2330,8 @@
if (request instanceof AdvertiserClientRequest) {
mAdvertiser.removeService(transactionId);
+ mMetrics.reportServiceUnregistration(
+ transactionId, request.calculateRequestDurationMs());
continue;
}
@@ -2170,6 +2348,8 @@
break;
case NsdManager.REGISTER_SERVICE:
unregisterService(transactionId);
+ mMetrics.reportServiceUnregistration(
+ transactionId, request.calculateRequestDurationMs());
break;
default:
break;
@@ -2261,7 +2441,13 @@
}
}
- void onRegisterServiceFailed(int listenerKey, int error) {
+ void onRegisterServiceFailedImmediately(int listenerKey, int error) {
+ onRegisterServiceFailed(listenerKey, error, NO_TRANSACTION, 0 /* durationMs */);
+ }
+
+ void onRegisterServiceFailed(int listenerKey, int error, int transactionId,
+ long durationMs) {
+ mMetrics.reportServiceRegistrationFailed(transactionId, durationMs);
try {
mCb.onRegisterServiceFailed(listenerKey, error);
} catch (RemoteException e) {
@@ -2269,7 +2455,9 @@
}
}
- void onRegisterServiceSucceeded(int listenerKey, NsdServiceInfo info) {
+ void onRegisterServiceSucceeded(int listenerKey, NsdServiceInfo info, int transactionId,
+ long durationMs) {
+ mMetrics.reportServiceRegistrationSucceeded(transactionId, durationMs);
try {
mCb.onRegisterServiceSucceeded(listenerKey, info);
} catch (RemoteException e) {
@@ -2285,7 +2473,8 @@
}
}
- void onUnregisterServiceSucceeded(int listenerKey) {
+ void onUnregisterServiceSucceeded(int listenerKey, int transactionId, long durationMs) {
+ mMetrics.reportServiceUnregistration(transactionId, durationMs);
try {
mCb.onUnregisterServiceSucceeded(listenerKey);
} catch (RemoteException e) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManager.java b/service-t/src/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManager.java
index 551e3db..87aa0d2 100644
--- a/service-t/src/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManager.java
@@ -25,13 +25,12 @@
import android.net.NetworkRequest;
import android.os.Build;
-import com.android.server.connectivity.mdns.util.MdnsLogger;
+import com.android.net.module.util.SharedLog;
/** Class for monitoring connectivity changes using {@link ConnectivityManager}. */
public class ConnectivityMonitorWithConnectivityManager implements ConnectivityMonitor {
private static final String TAG = "ConnMntrWConnMgr";
- private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
-
+ private final SharedLog sharedLog;
private final Listener listener;
private final ConnectivityManager.NetworkCallback networkCallback;
private final ConnectivityManager connectivityManager;
@@ -42,8 +41,10 @@
@SuppressWarnings({"nullness:assignment", "nullness:method.invocation"})
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
- public ConnectivityMonitorWithConnectivityManager(Context context, Listener listener) {
+ public ConnectivityMonitorWithConnectivityManager(Context context, Listener listener,
+ SharedLog sharedLog) {
this.listener = listener;
+ this.sharedLog = sharedLog;
connectivityManager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -51,20 +52,20 @@
new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
- LOGGER.log("network available.");
+ sharedLog.log("network available.");
lastAvailableNetwork = network;
notifyConnectivityChange();
}
@Override
public void onLost(Network network) {
- LOGGER.log("network lost.");
+ sharedLog.log("network lost.");
notifyConnectivityChange();
}
@Override
public void onUnavailable() {
- LOGGER.log("network unavailable.");
+ sharedLog.log("network unavailable.");
notifyConnectivityChange();
}
};
@@ -82,7 +83,7 @@
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void startWatchingConnectivityChanges() {
- LOGGER.log("Start watching connectivity changes");
+ sharedLog.log("Start watching connectivity changes");
if (isCallbackRegistered) {
return;
}
@@ -98,7 +99,7 @@
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void stopWatchingConnectivityChanges() {
- LOGGER.log("Stop watching connectivity changes");
+ sharedLog.log("Stop watching connectivity changes");
if (!isCallbackRegistered) {
return;
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
index bd4ec20..fa3b646 100644
--- a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
+++ b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
@@ -16,13 +16,13 @@
package com.android.server.connectivity.mdns;
+import static com.android.server.connectivity.mdns.MdnsServiceTypeClient.INVALID_TRANSACTION_ID;
+
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.text.TextUtils;
-import android.util.Log;
import android.util.Pair;
-import com.android.server.connectivity.mdns.util.MdnsLogger;
+import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
@@ -43,7 +43,6 @@
public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<String>>> {
private static final String TAG = "MdnsQueryCallable";
- private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
private static final List<Integer> castShellEmulatorMdnsPorts;
static {
@@ -75,7 +74,9 @@
@NonNull
private final List<MdnsResponse> servicesToResolve;
@NonNull
- private final MdnsResponseDecoder.Clock clock;
+ private final MdnsUtils.Clock clock;
+ @NonNull
+ private final SharedLog sharedLog;
private final boolean onlyUseIpv6OnIpv6OnlyNetworks;
EnqueueMdnsQueryCallable(
@@ -89,7 +90,8 @@
boolean onlyUseIpv6OnIpv6OnlyNetworks,
boolean sendDiscoveryQueries,
@NonNull Collection<MdnsResponse> servicesToResolve,
- @NonNull MdnsResponseDecoder.Clock clock) {
+ @NonNull MdnsUtils.Clock clock,
+ @NonNull SharedLog sharedLog) {
weakRequestSender = new WeakReference<>(requestSender);
this.packetWriter = packetWriter;
serviceTypeLabels = TextUtils.split(serviceType, "\\.");
@@ -101,17 +103,22 @@
this.sendDiscoveryQueries = sendDiscoveryQueries;
this.servicesToResolve = new ArrayList<>(servicesToResolve);
this.clock = clock;
+ this.sharedLog = sharedLog;
}
+ /**
+ * Call to execute the mdns query.
+ *
+ * @return The pair of transaction id and the subtypes for the query.
+ */
// Incompatible return type for override of Callable#call().
@SuppressWarnings("nullness:override.return.invalid")
@Override
- @Nullable
public Pair<Integer, List<String>> call() {
try {
MdnsSocketClientBase requestSender = weakRequestSender.get();
if (requestSender == null) {
- return null;
+ return Pair.create(INVALID_TRANSACTION_ID, new ArrayList<>());
}
int numQuestions = 0;
@@ -158,7 +165,7 @@
if (numQuestions == 0) {
// No query to send
- return null;
+ return Pair.create(INVALID_TRANSACTION_ID, new ArrayList<>());
}
// Header.
@@ -195,9 +202,9 @@
}
return Pair.create(transactionId, subtypes);
} catch (IOException e) {
- LOGGER.e(String.format("Failed to create mDNS packet for subtype: %s.",
+ sharedLog.e(String.format("Failed to create mDNS packet for subtype: %s.",
TextUtils.join(",", subtypes)), e);
- return null;
+ return Pair.create(INVALID_TRANSACTION_ID, new ArrayList<>());
}
}
@@ -237,13 +244,13 @@
sendPacket(requestSender,
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), port));
} catch (IOException e) {
- Log.i(TAG, "Can't send packet to IPv4", e);
+ sharedLog.e("Can't send packet to IPv4", e);
}
try {
sendPacket(requestSender,
new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), port));
} catch (IOException e) {
- Log.i(TAG, "Can't send packet to IPv6", e);
+ sharedLog.e("Can't send packet to IPv6", e);
}
}
}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/ExecutorProvider.java b/service-t/src/com/android/server/connectivity/mdns/ExecutorProvider.java
index 0eebc61..161669b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/ExecutorProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/ExecutorProvider.java
@@ -16,6 +16,7 @@
package com.android.server.connectivity.mdns;
+import android.annotation.NonNull;
import android.util.ArraySet;
import java.util.Set;
@@ -47,5 +48,17 @@
}
executor.shutdownNow();
}
+ serviceTypeClientSchedulerExecutors.clear();
+ }
+
+ /**
+ * Shutdown one executor service and remove the executor service from the set.
+ * @param executorService the executorService to be shutdown
+ */
+ public void shutdownExecutorService(@NonNull ScheduledExecutorService executorService) {
+ if (!executorService.isShutdown()) {
+ executorService.shutdownNow();
+ }
+ serviceTypeClientSchedulerExecutors.remove(executorService);
}
}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index 158d7a3..dd72d11 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -24,15 +24,19 @@
import android.net.Network;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.net.nsd.OffloadEngine;
+import android.net.nsd.OffloadServiceInfo;
import android.os.Looper;
import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.util.MdnsUtils;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -68,9 +72,10 @@
new ArrayMap<>();
private final SparseArray<Registration> mRegistrations = new SparseArray<>();
private final Dependencies mDeps;
-
private String[] mDeviceHostName;
@NonNull private final SharedLog mSharedLog;
+ private final Map<String, List<OffloadServiceInfoWrapper>> mInterfaceOffloadServices =
+ new ArrayMap<>();
/**
* Dependencies for {@link MdnsAdvertiser}, useful for testing.
@@ -115,18 +120,32 @@
private final MdnsInterfaceAdvertiser.Callback mInterfaceAdvertiserCb =
new MdnsInterfaceAdvertiser.Callback() {
@Override
- public void onRegisterServiceSucceeded(
+ public void onServiceProbingSucceeded(
@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId) {
+ final Registration registration = mRegistrations.get(serviceId);
+ if (registration == null) {
+ mSharedLog.wtf("Register succeeded for unknown registration");
+ return;
+ }
+
+ final String interfaceName = advertiser.getSocketInterfaceName();
+ final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers =
+ mInterfaceOffloadServices.computeIfAbsent(
+ interfaceName, k -> new ArrayList<>());
+ // Remove existing offload services from cache for update.
+ existingOffloadServiceInfoWrappers.removeIf(item -> item.mServiceId == serviceId);
+ final OffloadServiceInfoWrapper newOffloadServiceInfoWrapper = createOffloadService(
+ serviceId,
+ registration);
+ existingOffloadServiceInfoWrappers.add(newOffloadServiceInfoWrapper);
+ mCb.onOffloadStartOrUpdate(interfaceName,
+ newOffloadServiceInfoWrapper.mOffloadServiceInfo);
+
// Wait for all current interfaces to be done probing before notifying of success.
if (any(mAllAdvertisers, (k, a) -> a.isProbing(serviceId))) return;
// The service may still be unregistered/renamed if a conflict is found on a later added
// interface, or if a conflicting announcement/reply is detected (RFC6762 9.)
- final Registration registration = mRegistrations.get(serviceId);
- if (registration == null) {
- Log.wtf(TAG, "Register succeeded for unknown registration");
- return;
- }
if (!registration.mNotifiedRegistrationSuccess) {
mCb.onRegisterServiceSucceeded(serviceId, registration.getServiceInfo());
registration.mNotifiedRegistrationSuccess = true;
@@ -148,7 +167,12 @@
registration.mNotifiedRegistrationSuccess = false;
// The service was done probing, just reset it to probing state (RFC6762 9.)
- forAllAdvertisers(a -> a.restartProbingForConflict(serviceId));
+ forAllAdvertisers(a -> {
+ if (!a.maybeRestartProbingForConflict(serviceId)) {
+ return;
+ }
+ maybeSendOffloadStop(a.getSocketInterfaceName(), serviceId);
+ });
return;
}
@@ -196,6 +220,22 @@
registration.updateForConflict(newInfo, renameCount);
}
+ private void maybeSendOffloadStop(final String interfaceName, int serviceId) {
+ final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers =
+ mInterfaceOffloadServices.get(interfaceName);
+ if (existingOffloadServiceInfoWrappers == null) {
+ return;
+ }
+ // Stop the offloaded service by matching the service id
+ int idx = CollectionUtils.indexOf(existingOffloadServiceInfoWrappers,
+ item -> item.mServiceId == serviceId);
+ if (idx >= 0) {
+ mCb.onOffloadStop(interfaceName,
+ existingOffloadServiceInfoWrappers.get(idx).mOffloadServiceInfo);
+ existingOffloadServiceInfoWrappers.remove(idx);
+ }
+ }
+
/**
* A request for a {@link MdnsInterfaceAdvertiser}.
*
@@ -221,7 +261,22 @@
* @return true if this {@link InterfaceAdvertiserRequest} should now be deleted.
*/
boolean onAdvertiserDestroyed(@NonNull MdnsInterfaceSocket socket) {
- mAdvertisers.remove(socket);
+ final MdnsInterfaceAdvertiser removedAdvertiser = mAdvertisers.remove(socket);
+ if (removedAdvertiser != null) {
+ final String interfaceName = removedAdvertiser.getSocketInterfaceName();
+ // If the interface is destroyed, stop all hardware offloading on that interface.
+ final List<OffloadServiceInfoWrapper> offloadServiceInfoWrappers =
+ mInterfaceOffloadServices.remove(
+ interfaceName);
+ if (offloadServiceInfoWrappers != null) {
+ for (OffloadServiceInfoWrapper offloadServiceInfoWrapper :
+ offloadServiceInfoWrappers) {
+ mCb.onOffloadStop(interfaceName,
+ offloadServiceInfoWrapper.mOffloadServiceInfo);
+ }
+ }
+ }
+
if (mAdvertisers.size() == 0 && mPendingRegistrations.size() == 0) {
// No advertiser is using sockets from this request anymore (in particular for exit
// announcements), and there is no registration so newer sockets will not be
@@ -274,7 +329,8 @@
mAdvertisers.valueAt(i).addService(
id, registration.getServiceInfo(), registration.getSubtype());
} catch (NameConflictException e) {
- Log.wtf(TAG, "Name conflict adding services that should have unique names", e);
+ mSharedLog.wtf("Name conflict adding services that should have unique names",
+ e);
}
}
}
@@ -282,7 +338,10 @@
void removeService(int id) {
mPendingRegistrations.remove(id);
for (int i = 0; i < mAdvertisers.size(); i++) {
- mAdvertisers.valueAt(i).removeService(id);
+ final MdnsInterfaceAdvertiser advertiser = mAdvertisers.valueAt(i);
+ advertiser.removeService(id);
+
+ maybeSendOffloadStop(advertiser.getSocketInterfaceName(), id);
}
}
@@ -305,7 +364,8 @@
advertiser.addService(mPendingRegistrations.keyAt(i),
registration.getServiceInfo(), registration.getSubtype());
} catch (NameConflictException e) {
- Log.wtf(TAG, "Name conflict adding services that should have unique names", e);
+ mSharedLog.wtf("Name conflict adding services that should have unique names",
+ e);
}
}
}
@@ -325,6 +385,16 @@
}
}
+ private static class OffloadServiceInfoWrapper {
+ private final @NonNull OffloadServiceInfo mOffloadServiceInfo;
+ private final int mServiceId;
+
+ OffloadServiceInfoWrapper(int serviceId, OffloadServiceInfo offloadServiceInfo) {
+ mOffloadServiceInfo = offloadServiceInfo;
+ mServiceId = serviceId;
+ }
+ }
+
private static class Registration {
@NonNull
final String mOriginalName;
@@ -425,6 +495,24 @@
// Unregistration is notified immediately as success in NsdService so no callback is needed
// here.
+
+ /**
+ * Called when a service is ready to be sent for hardware offloading.
+ *
+ * @param interfaceName the interface for sending the update to.
+ * @param offloadServiceInfo the offloading content.
+ */
+ void onOffloadStartOrUpdate(@NonNull String interfaceName,
+ @NonNull OffloadServiceInfo offloadServiceInfo);
+
+ /**
+ * Called when a service is removed or the MdnsInterfaceAdvertiser is destroyed.
+ *
+ * @param interfaceName the interface for sending the update to.
+ * @param offloadServiceInfo the offloading content.
+ */
+ void onOffloadStop(@NonNull String interfaceName,
+ @NonNull OffloadServiceInfo offloadServiceInfo);
}
public MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
@@ -459,7 +547,7 @@
public void addService(int id, NsdServiceInfo service, @Nullable String subtype) {
checkThread();
if (mRegistrations.get(id) != null) {
- Log.e(TAG, "Adding duplicate registration for " + service);
+ mSharedLog.e("Adding duplicate registration for " + service);
// TODO (b/264986328): add a more specific error code
mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR);
return;
@@ -525,4 +613,28 @@
return false;
});
}
+
+ private OffloadServiceInfoWrapper createOffloadService(int serviceId,
+ @NonNull Registration registration) {
+ final NsdServiceInfo nsdServiceInfo = registration.getServiceInfo();
+ List<String> subTypes = new ArrayList<>();
+ String subType = registration.getSubtype();
+ if (subType != null) {
+ subTypes.add(subType);
+ }
+ final OffloadServiceInfo offloadServiceInfo = new OffloadServiceInfo(
+ new OffloadServiceInfo.Key(nsdServiceInfo.getServiceName(),
+ nsdServiceInfo.getServiceType()),
+ subTypes,
+ String.join(".", mDeviceHostName),
+ null /* rawOffloadPacket */,
+ // TODO: define overlayable resources in
+ // ServiceConnectivityResources that set the priority based on
+ // service type.
+ 0 /* priority */,
+ // TODO: set the offloadType based on the callback timing.
+ OffloadEngine.OFFLOAD_TYPE_REPLY);
+ return new OffloadServiceInfoWrapper(serviceId, offloadServiceInfo);
+ }
+
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java
index 27fc945..fd2c32e 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java
@@ -21,6 +21,7 @@
import android.os.Looper;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.SharedLog;
import java.util.Collections;
import java.util.List;
@@ -39,9 +40,6 @@
private static final long EXIT_DELAY_MS = 2000L;
private static final int EXIT_COUNT = 3;
- @NonNull
- private final String mLogTag;
-
/** Base class for announcement requests to send with {@link MdnsAnnouncer}. */
public abstract static class BaseAnnouncementInfo implements MdnsPacketRepeater.Request {
private final int mServiceId;
@@ -105,16 +103,11 @@
}
}
- public MdnsAnnouncer(@NonNull String interfaceTag, @NonNull Looper looper,
+ public MdnsAnnouncer(@NonNull Looper looper,
@NonNull MdnsReplySender replySender,
- @Nullable PacketRepeaterCallback<BaseAnnouncementInfo> cb) {
- super(looper, replySender, cb);
- mLogTag = MdnsAnnouncer.class.getSimpleName() + "/" + interfaceTag;
- }
-
- @Override
- protected String getTag() {
- return mLogTag;
+ @Nullable PacketRepeaterCallback<BaseAnnouncementInfo> cb,
+ @NonNull SharedLog sharedLog) {
+ super(looper, replySender, cb, sharedLog);
}
// TODO: Notify MdnsRecordRepository that the records were announced for that service ID,
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsConfigs.java b/service-t/src/com/android/server/connectivity/mdns/MdnsConfigs.java
index 8cb3e96..d4aeacf 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsConfigs.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsConfigs.java
@@ -50,10 +50,6 @@
return false;
}
- public static boolean useSessionIdToScheduleMdnsTask() {
- return true;
- }
-
public static long sleepTimeForSocketThreadMs() {
return 20_000L;
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java b/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java
index ce5f540..0c32cf1 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java
@@ -16,18 +16,13 @@
package com.android.server.connectivity.mdns;
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-
import static java.nio.charset.StandardCharsets.UTF_8;
-import com.android.internal.annotations.VisibleForTesting;
-
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
/** mDNS-related constants. */
-@VisibleForTesting(visibility = PACKAGE)
public final class MdnsConstants {
public static final int MDNS_PORT = 5353;
// Flags word format is:
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index dfaec75..d55098c 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -51,6 +51,7 @@
@NonNull private final PerSocketServiceTypeClients perSocketServiceTypeClients;
@NonNull private final Handler handler;
@Nullable private final HandlerThread handlerThread;
+ @NonNull private final MdnsServiceCache serviceCache;
private static class PerSocketServiceTypeClients {
private final ArrayMap<Pair<String, SocketKey>, MdnsServiceTypeClient> clients =
@@ -119,10 +120,12 @@
if (socketClient.getLooper() != null) {
this.handlerThread = null;
this.handler = new Handler(socketClient.getLooper());
+ this.serviceCache = new MdnsServiceCache(socketClient.getLooper());
} else {
this.handlerThread = new HandlerThread(MdnsDiscoveryManager.class.getSimpleName());
this.handlerThread.start();
this.handler = new Handler(handlerThread.getLooper());
+ this.serviceCache = new MdnsServiceCache(handlerThread.getLooper());
}
}
@@ -201,6 +204,7 @@
if (serviceTypeClient == null) return;
// Notify all listeners that all services are removed from this socket.
serviceTypeClient.notifySocketDestroyed();
+ executorProvider.shutdownExecutorService(serviceTypeClient.getExecutor());
perSocketServiceTypeClients.remove(serviceTypeClient);
}
});
@@ -235,6 +239,7 @@
if (serviceTypeClient.stopSendAndReceive(listener)) {
// No listener is registered for the service type anymore, remove it from the list
// of the service type clients.
+ executorProvider.shutdownExecutorService(serviceTypeClient.getExecutor());
perSocketServiceTypeClients.remove(serviceTypeClient);
}
}
@@ -289,6 +294,6 @@
return new MdnsServiceTypeClient(
serviceType, socketClient,
executorProvider.newServiceTypeClientSchedulerExecutor(), socketKey,
- sharedLog.forSubComponent(tag), handler.getLooper());
+ sharedLog.forSubComponent(tag), handler.getLooper(), serviceCache);
}
}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index 724a704..a83b852 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -22,7 +22,6 @@
import android.net.nsd.NsdServiceInfo;
import android.os.Handler;
import android.os.Looper;
-import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.HexDump;
@@ -42,8 +41,6 @@
@VisibleForTesting
public static final long EXIT_ANNOUNCEMENT_DELAY_MS = 100L;
@NonNull
- private final String mTag;
- @NonNull
private final ProbingCallback mProbingCallback = new ProbingCallback();
@NonNull
private final AnnouncingCallback mAnnouncingCallback = new AnnouncingCallback();
@@ -73,7 +70,7 @@
/**
* Called by the advertiser after it successfully registered a service, after probing.
*/
- void onRegisterServiceSucceeded(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId);
+ void onServiceProbingSucceeded(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId);
/**
* Called by the advertiser when a conflict was found, during or after probing.
@@ -101,7 +98,7 @@
public void onFinished(MdnsProber.ProbingInfo info) {
final MdnsAnnouncer.AnnouncementInfo announcementInfo;
mSharedLog.i("Probing finished for service " + info.getServiceId());
- mCbHandler.post(() -> mCb.onRegisterServiceSucceeded(
+ mCbHandler.post(() -> mCb.onServiceProbingSucceeded(
MdnsInterfaceAdvertiser.this, info.getServiceId()));
try {
announcementInfo = mRecordRepository.onProbingSucceeded(info);
@@ -151,22 +148,30 @@
/** @see MdnsReplySender */
@NonNull
public MdnsReplySender makeReplySender(@NonNull String interfaceTag, @NonNull Looper looper,
- @NonNull MdnsInterfaceSocket socket, @NonNull byte[] packetCreationBuffer) {
- return new MdnsReplySender(interfaceTag, looper, socket, packetCreationBuffer);
+ @NonNull MdnsInterfaceSocket socket, @NonNull byte[] packetCreationBuffer,
+ @NonNull SharedLog sharedLog) {
+ return new MdnsReplySender(looper, socket, packetCreationBuffer,
+ sharedLog.forSubComponent(
+ MdnsReplySender.class.getSimpleName() + "/" + interfaceTag));
}
/** @see MdnsAnnouncer */
public MdnsAnnouncer makeMdnsAnnouncer(@NonNull String interfaceTag, @NonNull Looper looper,
@NonNull MdnsReplySender replySender,
- @Nullable PacketRepeaterCallback<MdnsAnnouncer.BaseAnnouncementInfo> cb) {
- return new MdnsAnnouncer(interfaceTag, looper, replySender, cb);
+ @Nullable PacketRepeaterCallback<MdnsAnnouncer.BaseAnnouncementInfo> cb,
+ @NonNull SharedLog sharedLog) {
+ return new MdnsAnnouncer(looper, replySender, cb,
+ sharedLog.forSubComponent(
+ MdnsAnnouncer.class.getSimpleName() + "/" + interfaceTag));
}
/** @see MdnsProber */
public MdnsProber makeMdnsProber(@NonNull String interfaceTag, @NonNull Looper looper,
@NonNull MdnsReplySender replySender,
- @NonNull PacketRepeaterCallback<MdnsProber.ProbingInfo> cb) {
- return new MdnsProber(interfaceTag, looper, replySender, cb);
+ @NonNull PacketRepeaterCallback<MdnsProber.ProbingInfo> cb,
+ @NonNull SharedLog sharedLog) {
+ return new MdnsProber(looper, replySender, cb, sharedLog.forSubComponent(
+ MdnsProber.class.getSimpleName() + "/" + interfaceTag));
}
}
@@ -182,17 +187,17 @@
@NonNull List<LinkAddress> initialAddresses, @NonNull Looper looper,
@NonNull byte[] packetCreationBuffer, @NonNull Callback cb, @NonNull Dependencies deps,
@NonNull String[] deviceHostName, @NonNull SharedLog sharedLog) {
- mTag = MdnsInterfaceAdvertiser.class.getSimpleName() + "/" + sharedLog.getTag();
mRecordRepository = deps.makeRecordRepository(looper, deviceHostName);
mRecordRepository.updateAddresses(initialAddresses);
mSocket = socket;
mCb = cb;
mCbHandler = new Handler(looper);
mReplySender = deps.makeReplySender(sharedLog.getTag(), looper, socket,
- packetCreationBuffer);
+ packetCreationBuffer, sharedLog);
mAnnouncer = deps.makeMdnsAnnouncer(sharedLog.getTag(), looper, mReplySender,
- mAnnouncingCallback);
- mProber = deps.makeMdnsProber(sharedLog.getTag(), looper, mReplySender, mProbingCallback);
+ mAnnouncingCallback, sharedLog);
+ mProber = deps.makeMdnsProber(sharedLog.getTag(), looper, mReplySender, mProbingCallback,
+ sharedLog);
mSharedLog = sharedLog;
}
@@ -282,11 +287,12 @@
/**
* Reset a service to the probing state due to a conflict found on the network.
*/
- public void restartProbingForConflict(int serviceId) {
+ public boolean maybeRestartProbingForConflict(int serviceId) {
final MdnsProber.ProbingInfo probingInfo = mRecordRepository.setServiceProbing(serviceId);
- if (probingInfo == null) return;
+ if (probingInfo == null) return false;
mProber.restartForConflict(probingInfo);
+ return true;
}
/**
@@ -317,20 +323,18 @@
try {
packet = MdnsPacket.parse(new MdnsPacketReader(recvbuf, length));
} catch (MdnsPacket.ParseException e) {
- Log.e(mTag, "Error parsing mDNS packet", e);
+ mSharedLog.e("Error parsing mDNS packet", e);
if (DBG) {
- Log.v(
- mTag, "Packet: " + HexDump.toHexString(recvbuf, 0, length));
+ mSharedLog.v("Packet: " + HexDump.toHexString(recvbuf, 0, length));
}
return;
}
if (DBG) {
- Log.v(mTag,
- "Parsed packet with " + packet.questions.size() + " questions, "
- + packet.answers.size() + " answers, "
- + packet.authorityRecords.size() + " authority, "
- + packet.additionalRecords.size() + " additional from " + src);
+ mSharedLog.v("Parsed packet with " + packet.questions.size() + " questions, "
+ + packet.answers.size() + " answers, "
+ + packet.authorityRecords.size() + " authority, "
+ + packet.additionalRecords.size() + " additional from " + src);
}
for (int conflictServiceId : mRecordRepository.getConflictingServices(packet)) {
@@ -346,4 +350,8 @@
if (answers == null) return;
mReplySender.queueReply(answers);
}
+
+ public String getSocketInterfaceName() {
+ return mSocket.getInterface().getName();
+ }
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
index 119c7a8..534f8d0 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
@@ -28,7 +28,8 @@
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
-import android.util.Log;
+
+import com.android.net.module.util.SharedLog;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -54,11 +55,12 @@
@NonNull private final NetworkInterface mNetworkInterface;
@NonNull private final MulticastPacketReader mPacketReader;
@NonNull private final ParcelFileDescriptor mFileDescriptor;
+ @NonNull private final SharedLog mSharedLog;
private boolean mJoinedIpv4 = false;
private boolean mJoinedIpv6 = false;
public MdnsInterfaceSocket(@NonNull NetworkInterface networkInterface, int port,
- @NonNull Looper looper, @NonNull byte[] packetReadBuffer)
+ @NonNull Looper looper, @NonNull byte[] packetReadBuffer, @NonNull SharedLog sharedLog)
throws IOException {
mNetworkInterface = networkInterface;
mMulticastSocket = new MulticastSocket(port);
@@ -80,6 +82,8 @@
mPacketReader = new MulticastPacketReader(networkInterface.getName(), mFileDescriptor,
new Handler(looper), packetReadBuffer);
mPacketReader.start();
+
+ mSharedLog = sharedLog;
}
/**
@@ -117,7 +121,7 @@
return true;
} catch (IOException e) {
// The address may have just been removed
- Log.e(TAG, "Error joining multicast group for " + mNetworkInterface, e);
+ mSharedLog.e("Error joining multicast group for " + mNetworkInterface, e);
return false;
}
}
@@ -148,7 +152,7 @@
try {
mFileDescriptor.close();
} catch (IOException e) {
- Log.e(TAG, "Close file descriptor failed.");
+ mSharedLog.e("Close file descriptor failed.");
}
mMulticastSocket.close();
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index d1fa57c..097dbe0 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -25,7 +25,8 @@
import android.os.Handler;
import android.os.Looper;
import android.util.ArrayMap;
-import android.util.Log;
+
+import com.android.net.module.util.SharedLog;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -46,6 +47,7 @@
@NonNull private final Handler mHandler;
@NonNull private final MdnsSocketProvider mSocketProvider;
+ @NonNull private final SharedLog mSharedLog;
private final ArrayMap<MdnsServiceBrowserListener, InterfaceSocketCallback> mRequestedNetworks =
new ArrayMap<>();
@@ -55,9 +57,11 @@
private int mReceivedPacketNumber = 0;
public MdnsMultinetworkSocketClient(@NonNull Looper looper,
- @NonNull MdnsSocketProvider provider) {
+ @NonNull MdnsSocketProvider provider,
+ @NonNull SharedLog sharedLog) {
mHandler = new Handler(looper);
mSocketProvider = provider;
+ mSharedLog = sharedLog;
}
private class InterfaceSocketCallback implements MdnsSocketProvider.SocketCallback {
@@ -172,7 +176,7 @@
throw new IllegalArgumentException("Can not register duplicated listener");
}
- if (DBG) Log.d(TAG, "notifyNetworkRequested: network=" + network);
+ if (DBG) mSharedLog.v("notifyNetworkRequested: network=" + network);
callback = new InterfaceSocketCallback(socketCreationCallback);
mRequestedNetworks.put(listener, callback);
mSocketProvider.requestSocket(network, callback);
@@ -184,7 +188,7 @@
ensureRunningOnHandlerThread(mHandler);
final InterfaceSocketCallback callback = mRequestedNetworks.get(listener);
if (callback == null) {
- Log.e(TAG, "Can not be unrequested with unknown listener=" + listener);
+ mSharedLog.e("Can not be unrequested with unknown listener=" + listener);
return;
}
callback.onNetworkUnrequested();
@@ -222,7 +226,7 @@
try {
socket.send(packet);
} catch (IOException e) {
- Log.e(TAG, "Failed to send a mDNS packet.", e);
+ mSharedLog.e("Failed to send a mDNS packet.", e);
}
}
}
@@ -249,7 +253,7 @@
response = MdnsResponseDecoder.parseResponse(recvbuf, length);
} catch (MdnsPacket.ParseException e) {
if (e.code != MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE) {
- Log.e(TAG, e.getMessage(), e);
+ mSharedLog.e(e.getMessage(), e);
if (mCallback != null) {
mCallback.onFailedToParseMdnsResponse(packetNumber, e.code, socketKey);
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
index 4c385da..644560c 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
@@ -24,7 +24,8 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.util.Log;
+
+import com.android.net.module.util.SharedLog;
import java.io.IOException;
import java.net.InetSocketAddress;
@@ -45,6 +46,8 @@
protected final Handler mHandler;
@Nullable
private final PacketRepeaterCallback<T> mCb;
+ @NonNull
+ private final SharedLog mSharedLog;
/**
* Status callback from {@link MdnsPacketRepeater}.
@@ -87,12 +90,6 @@
int getNumSends();
}
- /**
- * Get the logging tag to use.
- */
- @NonNull
- protected abstract String getTag();
-
private final class ProbeHandler extends Handler {
ProbeHandler(@NonNull Looper looper) {
super(looper);
@@ -112,7 +109,7 @@
final MdnsPacket packet = request.getPacket(index);
if (DBG) {
- Log.v(getTag(), "Sending packets for iteration " + index + " out of "
+ mSharedLog.v("Sending packets for iteration " + index + " out of "
+ request.getNumSends() + " for ID " + msg.what);
}
// Send to both v4 and v6 addresses; the reply sender will take care of ignoring the
@@ -121,7 +118,7 @@
try {
mReplySender.sendNow(packet, destination);
} catch (IOException e) {
- Log.e(getTag(), "Error sending packet to " + destination, e);
+ mSharedLog.e("Error sending packet to " + destination, e);
}
}
@@ -133,7 +130,7 @@
// likely not to be available since the device is in deep sleep anyway.
final long delay = request.getDelayMs(nextIndex);
sendMessageDelayed(obtainMessage(msg.what, nextIndex, 0, request), delay);
- if (DBG) Log.v(getTag(), "Scheduled next packet in " + delay + "ms");
+ if (DBG) mSharedLog.v("Scheduled next packet in " + delay + "ms");
}
// Call onSent after scheduling the next run, to allow the callback to cancel it
@@ -144,15 +141,16 @@
}
protected MdnsPacketRepeater(@NonNull Looper looper, @NonNull MdnsReplySender replySender,
- @Nullable PacketRepeaterCallback<T> cb) {
+ @Nullable PacketRepeaterCallback<T> cb, @NonNull SharedLog sharedLog) {
mHandler = new ProbeHandler(looper);
mReplySender = replySender;
mCb = cb;
+ mSharedLog = sharedLog;
}
protected void startSending(int id, @NonNull T request, long initialDelayMs) {
if (DBG) {
- Log.v(getTag(), "Starting send with id " + id + ", request "
+ mSharedLog.v("Starting send with id " + id + ", request "
+ request.getClass().getSimpleName() + ", delay " + initialDelayMs);
}
mHandler.sendMessageDelayed(mHandler.obtainMessage(id, 0, 0, request), initialDelayMs);
@@ -171,7 +169,7 @@
// message cannot be cancelled.
if (mHandler.hasMessages(id)) {
if (DBG) {
- Log.v(getTag(), "Stopping send on id " + id);
+ mSharedLog.v("Stopping send on id " + id);
}
mHandler.removeMessages(id);
return true;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java b/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
index ecf846e..ba37f32 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
@@ -21,6 +21,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.util.ArrayList;
@@ -34,14 +35,11 @@
*/
public class MdnsProber extends MdnsPacketRepeater<MdnsProber.ProbingInfo> {
private static final long CONFLICT_RETRY_DELAY_MS = 5_000L;
- @NonNull
- private final String mLogTag;
- public MdnsProber(@NonNull String interfaceTag, @NonNull Looper looper,
- @NonNull MdnsReplySender replySender,
- @NonNull PacketRepeaterCallback<ProbingInfo> cb) {
- super(looper, replySender, cb);
- mLogTag = MdnsProber.class.getSimpleName() + "/" + interfaceTag;
+ public MdnsProber(@NonNull Looper looper, @NonNull MdnsReplySender replySender,
+ @NonNull PacketRepeaterCallback<ProbingInfo> cb,
+ @NonNull SharedLog sharedLog) {
+ super(looper, replySender, cb, sharedLog);
}
/** Probing request to send with {@link MdnsProber}. */
@@ -118,11 +116,6 @@
}
}
- @NonNull
- @Override
- protected String getTag() {
- return mLogTag;
- }
@VisibleForTesting
protected long getInitialDelay() {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsQueryScheduler.java b/service-t/src/com/android/server/connectivity/mdns/MdnsQueryScheduler.java
new file mode 100644
index 0000000..3fcf0d4
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsQueryScheduler.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * The query scheduler class for calculating next query tasks parameters.
+ * <p>
+ * The class is not thread-safe and needs to be used on a consistent thread.
+ */
+public class MdnsQueryScheduler {
+
+ /**
+ * The argument for tracking the query tasks status.
+ */
+ public static class ScheduledQueryTaskArgs {
+ public final QueryTaskConfig config;
+ public final long timeToRun;
+ public final long minTtlExpirationTimeWhenScheduled;
+ public final long sessionId;
+
+ ScheduledQueryTaskArgs(@NonNull QueryTaskConfig config, long timeToRun,
+ long minTtlExpirationTimeWhenScheduled, long sessionId) {
+ this.config = config;
+ this.timeToRun = timeToRun;
+ this.minTtlExpirationTimeWhenScheduled = minTtlExpirationTimeWhenScheduled;
+ this.sessionId = sessionId;
+ }
+ }
+
+ @Nullable
+ private ScheduledQueryTaskArgs mLastScheduledQueryTaskArgs;
+
+ public MdnsQueryScheduler() {
+ }
+
+ /**
+ * Cancel the scheduled run. The method needed to be called when the scheduled task need to
+ * be canceled and rescheduling is not need.
+ */
+ public void cancelScheduledRun() {
+ mLastScheduledQueryTaskArgs = null;
+ }
+
+ /**
+ * Calculates ScheduledQueryTaskArgs for rescheduling the current task. Returns null if the
+ * rescheduling is not necessary.
+ */
+ @Nullable
+ public ScheduledQueryTaskArgs maybeRescheduleCurrentRun(long now,
+ long minRemainingTtl, long lastSentTime, long sessionId) {
+ if (mLastScheduledQueryTaskArgs == null) {
+ return null;
+ }
+ if (!mLastScheduledQueryTaskArgs.config.shouldUseQueryBackoff()) {
+ return null;
+ }
+
+ final long timeToRun = calculateTimeToRun(mLastScheduledQueryTaskArgs,
+ mLastScheduledQueryTaskArgs.config, now, minRemainingTtl, lastSentTime);
+
+ if (timeToRun <= mLastScheduledQueryTaskArgs.timeToRun) {
+ return null;
+ }
+
+ mLastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(mLastScheduledQueryTaskArgs.config,
+ timeToRun,
+ minRemainingTtl + now,
+ sessionId);
+ return mLastScheduledQueryTaskArgs;
+ }
+
+ /**
+ * Calculates the ScheduledQueryTaskArgs for the next run.
+ */
+ @NonNull
+ public ScheduledQueryTaskArgs scheduleNextRun(
+ @NonNull QueryTaskConfig currentConfig,
+ long minRemainingTtl,
+ long now,
+ long lastSentTime,
+ long sessionId) {
+ final QueryTaskConfig nextRunConfig = currentConfig.getConfigForNextRun();
+ final long timeToRun;
+ if (mLastScheduledQueryTaskArgs == null) {
+ timeToRun = now + nextRunConfig.delayUntilNextTaskWithoutBackoffMs;
+ } else {
+ timeToRun = calculateTimeToRun(mLastScheduledQueryTaskArgs,
+ nextRunConfig, now, minRemainingTtl, lastSentTime);
+ }
+ mLastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(nextRunConfig, timeToRun,
+ minRemainingTtl + now,
+ sessionId);
+ return mLastScheduledQueryTaskArgs;
+ }
+
+ /**
+ * Calculates the ScheduledQueryTaskArgs for the initial run.
+ */
+ public ScheduledQueryTaskArgs scheduleFirstRun(@NonNull QueryTaskConfig taskConfig,
+ long now, long minRemainingTtl, long currentSessionId) {
+ mLastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(taskConfig, now /* timeToRun */,
+ now + minRemainingTtl/* minTtlExpirationTimeWhenScheduled */,
+ currentSessionId);
+ return mLastScheduledQueryTaskArgs;
+ }
+
+ private static long calculateTimeToRun(@NonNull ScheduledQueryTaskArgs taskArgs,
+ QueryTaskConfig queryTaskConfig, long now, long minRemainingTtl, long lastSentTime) {
+ final long baseDelayInMs = queryTaskConfig.delayUntilNextTaskWithoutBackoffMs;
+ if (!queryTaskConfig.shouldUseQueryBackoff()) {
+ return lastSentTime + baseDelayInMs;
+ }
+ if (minRemainingTtl <= 0) {
+ // There's no service, or there is an expired service. In any case, schedule for the
+ // minimum time, which is the base delay.
+ return lastSentTime + baseDelayInMs;
+ }
+ // If the next TTL expiration time hasn't changed, then use previous calculated timeToRun.
+ if (lastSentTime < now
+ && taskArgs.minTtlExpirationTimeWhenScheduled == now + minRemainingTtl) {
+ // Use the original scheduling time if the TTL has not changed, to avoid continuously
+ // rescheduling to 80% of the remaining TTL as time passes
+ return taskArgs.timeToRun;
+ }
+ return Math.max(now + (long) (0.8 * minRemainingTtl), lastSentTime + baseDelayInMs);
+ }
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
index 8bc598d..16c7d27 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
@@ -22,8 +22,8 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.util.Log;
+import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.MdnsRecordRepository.ReplyInfo;
import java.io.IOException;
@@ -43,21 +43,21 @@
public class MdnsReplySender {
private static final boolean DBG = MdnsAdvertiser.DBG;
private static final int MSG_SEND = 1;
-
- private final String mLogTag;
@NonNull
private final MdnsInterfaceSocket mSocket;
@NonNull
private final Handler mHandler;
@NonNull
private final byte[] mPacketCreationBuffer;
+ @NonNull
+ private final SharedLog mSharedLog;
- public MdnsReplySender(@NonNull String interfaceTag, @NonNull Looper looper,
- @NonNull MdnsInterfaceSocket socket, @NonNull byte[] packetCreationBuffer) {
+ public MdnsReplySender(@NonNull Looper looper, @NonNull MdnsInterfaceSocket socket,
+ @NonNull byte[] packetCreationBuffer, @NonNull SharedLog sharedLog) {
mHandler = new SendHandler(looper);
- mLogTag = MdnsReplySender.class.getSimpleName() + "/" + interfaceTag;
mSocket = socket;
mPacketCreationBuffer = packetCreationBuffer;
+ mSharedLog = sharedLog;
}
/**
@@ -69,7 +69,7 @@
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SEND, reply), reply.sendDelayMs);
if (DBG) {
- Log.v(mLogTag, "Scheduling " + reply);
+ mSharedLog.v("Scheduling " + reply);
}
}
@@ -134,7 +134,7 @@
@Override
public void handleMessage(@NonNull Message msg) {
final ReplyInfo replyInfo = (ReplyInfo) msg.obj;
- if (DBG) Log.v(mLogTag, "Sending " + replyInfo);
+ if (DBG) mSharedLog.v("Sending " + replyInfo);
final int flags = 0x8400; // Response, authoritative (rfc6762 18.4)
final MdnsPacket packet = new MdnsPacket(flags,
@@ -146,7 +146,7 @@
try {
sendNow(packet, replyInfo.destination);
} catch (IOException e) {
- Log.e(mLogTag, "Error sending MDNS response", e);
+ mSharedLog.e("Error sending MDNS response", e);
}
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
index eff1880..2f10bde 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
@@ -19,12 +19,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.Network;
-import android.os.SystemClock;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
-import com.android.server.connectivity.mdns.util.MdnsLogger;
import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.EOFException;
@@ -36,14 +34,13 @@
public class MdnsResponseDecoder {
public static final int SUCCESS = 0;
private static final String TAG = "MdnsResponseDecoder";
- private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
private final boolean allowMultipleSrvRecordsPerHost =
MdnsConfigs.allowMultipleSrvRecordsPerHost();
@Nullable private final String[] serviceType;
- private final Clock clock;
+ private final MdnsUtils.Clock clock;
/** Constructs a new decoder that will extract responses for the given service type. */
- public MdnsResponseDecoder(@NonNull Clock clock, @Nullable String[] serviceType) {
+ public MdnsResponseDecoder(@NonNull MdnsUtils.Clock clock, @Nullable String[] serviceType) {
this.clock = clock;
this.serviceType = serviceType;
}
@@ -330,10 +327,4 @@
}
return result == null ? List.of() : result;
}
-
- public static class Clock {
- public long elapsedRealtime() {
- return SystemClock.elapsedRealtime();
- }
- }
}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
index dc99e49..ec6af9b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
@@ -96,7 +96,14 @@
: Collections.emptyList();
}
- private MdnsResponse findMatchedResponse(@NonNull List<MdnsResponse> responses,
+ /**
+ * Find a matched response for given service name
+ *
+ * @param responses the responses to be searched.
+ * @param serviceName the target service name
+ * @return the response which matches the given service name or null if not found.
+ */
+ public static MdnsResponse findMatchedResponse(@NonNull List<MdnsResponse> responses,
@NonNull String serviceName) {
for (MdnsResponse response : responses) {
if (equalsIgnoreDnsCase(serviceName, response.getServiceInstanceName())) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index e1e3170..53a7ab9 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -16,6 +16,8 @@
package com.android.server.connectivity.mdns;
+import static com.android.server.connectivity.mdns.MdnsServiceCache.findMatchedResponse;
+import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock;
import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
import android.annotation.NonNull;
@@ -28,7 +30,6 @@
import android.util.ArraySet;
import android.util.Pair;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
@@ -38,12 +39,9 @@
import java.net.Inet6Address;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
/**
@@ -56,6 +54,8 @@
private static final int DEFAULT_MTU = 1500;
@VisibleForTesting
static final int EVENT_START_QUERYTASK = 1;
+ static final int EVENT_QUERY_RESULT = 2;
+ static final int INVALID_TRANSACTION_ID = -1;
private final String serviceType;
private final String[] serviceTypeLabels;
@@ -65,16 +65,17 @@
@NonNull private final SocketKey socketKey;
@NonNull private final SharedLog sharedLog;
@NonNull private final Handler handler;
+ @NonNull private final MdnsQueryScheduler mdnsQueryScheduler;
@NonNull private final Dependencies dependencies;
- private final Object lock = new Object();
+ /**
+ * The service caches for each socket. It should be accessed from looper thread only.
+ */
+ @NonNull private final MdnsServiceCache serviceCache;
private final ArrayMap<MdnsServiceBrowserListener, MdnsSearchOptions> listeners =
new ArrayMap<>();
- // TODO: change instanceNameToResponse to TreeMap with case insensitive comparator.
- @GuardedBy("lock")
- private final Map<String, MdnsResponse> instanceNameToResponse = new HashMap<>();
private final boolean removeServiceAfterTtlExpires =
MdnsConfigs.removeServiceAfterTtlExpires();
- private final MdnsResponseDecoder.Clock clock;
+ private final Clock clock;
@Nullable private MdnsSearchOptions searchOptions;
@@ -82,12 +83,6 @@
// QueryTask for
// new subtypes. It stays the same between packets for same subtypes.
private long currentSessionId = 0;
-
- @GuardedBy("lock")
- @Nullable
- private QueryTask lastScheduledTask;
-
- @GuardedBy("lock")
private long lastSentTime;
private class QueryTaskHandler extends Handler {
@@ -96,11 +91,57 @@
}
@Override
+ @SuppressWarnings("FutureReturnValueIgnored")
public void handleMessage(Message msg) {
switch (msg.what) {
- case EVENT_START_QUERYTASK:
- handleStartQueryTask((QueryTask) msg.obj);
+ case EVENT_START_QUERYTASK: {
+ final MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs =
+ (MdnsQueryScheduler.ScheduledQueryTaskArgs) msg.obj;
+ // QueryTask should be run immediately after being created (not be scheduled in
+ // advance). Because the result of "makeResponsesForResolve" depends on answers
+ // that were received before it is called, so to take into account all answers
+ // before sending the query, it needs to be called just before sending it.
+ final List<MdnsResponse> servicesToResolve = makeResponsesForResolve(socketKey);
+ final QueryTask queryTask = new QueryTask(taskArgs, servicesToResolve,
+ servicesToResolve.size() < listeners.size() /* sendDiscoveryQueries */);
+ executor.submit(queryTask);
break;
+ }
+ case EVENT_QUERY_RESULT: {
+ final QuerySentArguments sentResult = (QuerySentArguments) msg.obj;
+ // If a task is cancelled while the Executor is running it, EVENT_QUERY_RESULT
+ // will still be sent when it ends. So use session ID to check if this task
+ // should continue to schedule more.
+ if (sentResult.taskArgs.sessionId != currentSessionId) {
+ break;
+ }
+
+ if ((sentResult.transactionId != INVALID_TRANSACTION_ID)) {
+ for (int i = 0; i < listeners.size(); i++) {
+ listeners.keyAt(i).onDiscoveryQuerySent(
+ sentResult.subTypes, sentResult.transactionId);
+ }
+ }
+
+ tryRemoveServiceAfterTtlExpires();
+
+ final long now = clock.elapsedRealtime();
+ lastSentTime = now;
+ final long minRemainingTtl = getMinRemainingTtl(now);
+ MdnsQueryScheduler.ScheduledQueryTaskArgs args =
+ mdnsQueryScheduler.scheduleNextRun(
+ sentResult.taskArgs.config,
+ minRemainingTtl,
+ now,
+ lastSentTime,
+ sentResult.taskArgs.sessionId
+ );
+ dependencies.sendMessageDelayed(
+ handler,
+ handler.obtainMessage(EVENT_START_QUERYTASK, args),
+ calculateTimeToNextTask(args, now, sharedLog));
+ break;
+ }
default:
sharedLog.e("Unrecognized event " + msg.what);
break;
@@ -134,6 +175,13 @@
public boolean hasMessages(@NonNull Handler handler, int what) {
return handler.hasMessages(what);
}
+
+ /**
+ * @see Handler#post(Runnable)
+ */
+ public void sendMessage(@NonNull Handler handler, @NonNull Message message) {
+ handler.sendMessage(message);
+ }
}
/**
@@ -148,9 +196,10 @@
@NonNull ScheduledExecutorService executor,
@NonNull SocketKey socketKey,
@NonNull SharedLog sharedLog,
- @NonNull Looper looper) {
- this(serviceType, socketClient, executor, new MdnsResponseDecoder.Clock(), socketKey,
- sharedLog, looper, new Dependencies());
+ @NonNull Looper looper,
+ @NonNull MdnsServiceCache serviceCache) {
+ this(serviceType, socketClient, executor, new Clock(), socketKey, sharedLog, looper,
+ new Dependencies(), serviceCache);
}
@VisibleForTesting
@@ -158,11 +207,12 @@
@NonNull String serviceType,
@NonNull MdnsSocketClientBase socketClient,
@NonNull ScheduledExecutorService executor,
- @NonNull MdnsResponseDecoder.Clock clock,
+ @NonNull Clock clock,
@NonNull SocketKey socketKey,
@NonNull SharedLog sharedLog,
@NonNull Looper looper,
- @NonNull Dependencies dependencies) {
+ @NonNull Dependencies dependencies,
+ @NonNull MdnsServiceCache serviceCache) {
this.serviceType = serviceType;
this.socketClient = socketClient;
this.executor = executor;
@@ -173,6 +223,8 @@
this.sharedLog = sharedLog;
this.handler = new QueryTaskHandler(looper);
this.dependencies = dependencies;
+ this.serviceCache = serviceCache;
+ this.mdnsQueryScheduler = new MdnsQueryScheduler();
}
private static MdnsServiceInfo buildMdnsServiceInfoFromResponse(
@@ -232,66 +284,77 @@
* @param listener The {@link MdnsServiceBrowserListener} to register.
* @param searchOptions {@link MdnsSearchOptions} contains the list of subtypes to discover.
*/
+ @SuppressWarnings("FutureReturnValueIgnored")
public void startSendAndReceive(
@NonNull MdnsServiceBrowserListener listener,
@NonNull MdnsSearchOptions searchOptions) {
ensureRunningOnHandlerThread(handler);
- synchronized (lock) {
- this.searchOptions = searchOptions;
- boolean hadReply = false;
- if (listeners.put(listener, searchOptions) == null) {
- for (MdnsResponse existingResponse : instanceNameToResponse.values()) {
- if (!responseMatchesOptions(existingResponse, searchOptions)) continue;
- final MdnsServiceInfo info =
- buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
- listener.onServiceNameDiscovered(info);
- if (existingResponse.isComplete()) {
- listener.onServiceFound(info);
- hadReply = true;
- }
+ this.searchOptions = searchOptions;
+ boolean hadReply = false;
+ if (listeners.put(listener, searchOptions) == null) {
+ for (MdnsResponse existingResponse :
+ serviceCache.getCachedServices(serviceType, socketKey)) {
+ if (!responseMatchesOptions(existingResponse, searchOptions)) continue;
+ final MdnsServiceInfo info =
+ buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
+ listener.onServiceNameDiscovered(info);
+ if (existingResponse.isComplete()) {
+ listener.onServiceFound(info);
+ hadReply = true;
}
}
- // Remove the next scheduled periodical task.
- removeScheduledTaskLock();
- // Keep tracking the ScheduledFuture for the task so we can cancel it if caller is not
- // interested anymore.
- final QueryTaskConfig taskConfig = new QueryTaskConfig(
- searchOptions.getSubtypes(),
- searchOptions.isPassiveMode(),
- searchOptions.onlyUseIpv6OnIpv6OnlyNetworks(),
- searchOptions.numOfQueriesBeforeBackoff(),
- socketKey);
- final long now = clock.elapsedRealtime();
- if (lastSentTime == 0) {
- lastSentTime = now;
- }
- if (hadReply) {
- final QueryTaskConfig queryTaskConfig = taskConfig.getConfigForNextRun();
- final long minRemainingTtl = getMinRemainingTtlLocked(now);
- final long timeToRun = now + queryTaskConfig.delayUntilNextTaskWithoutBackoffMs;
- scheduleNextRunLocked(
- queryTaskConfig, minRemainingTtl, now, timeToRun, currentSessionId);
- } else {
- lastScheduledTask = new QueryTask(taskConfig,
- now /* timeToRun */,
- now + getMinRemainingTtlLocked(now)/* minTtlExpirationTimeWhenScheduled */,
- currentSessionId);
- handleStartQueryTask(lastScheduledTask);
- }
+ }
+ // Remove the next scheduled periodical task.
+ removeScheduledTask();
+ mdnsQueryScheduler.cancelScheduledRun();
+ // Keep tracking the ScheduledFuture for the task so we can cancel it if caller is not
+ // interested anymore.
+ final QueryTaskConfig taskConfig = new QueryTaskConfig(
+ searchOptions.getSubtypes(),
+ searchOptions.isPassiveMode(),
+ searchOptions.onlyUseIpv6OnIpv6OnlyNetworks(),
+ searchOptions.numOfQueriesBeforeBackoff(),
+ socketKey);
+ final long now = clock.elapsedRealtime();
+ if (lastSentTime == 0) {
+ lastSentTime = now;
+ }
+ final long minRemainingTtl = getMinRemainingTtl(now);
+ if (hadReply) {
+ MdnsQueryScheduler.ScheduledQueryTaskArgs args =
+ mdnsQueryScheduler.scheduleNextRun(
+ taskConfig,
+ minRemainingTtl,
+ now,
+ lastSentTime,
+ currentSessionId
+ );
+ dependencies.sendMessageDelayed(
+ handler,
+ handler.obtainMessage(EVENT_START_QUERYTASK, args),
+ calculateTimeToNextTask(args, now, sharedLog));
+ } else {
+ final List<MdnsResponse> servicesToResolve = makeResponsesForResolve(socketKey);
+ final QueryTask queryTask = new QueryTask(
+ mdnsQueryScheduler.scheduleFirstRun(taskConfig, now,
+ minRemainingTtl, currentSessionId), servicesToResolve,
+ servicesToResolve.size() < listeners.size() /* sendDiscoveryQueries */);
+ executor.submit(queryTask);
}
}
- @GuardedBy("lock")
- private void removeScheduledTaskLock() {
+ /**
+ * Get the executor service.
+ */
+ public ScheduledExecutorService getExecutor() {
+ return executor;
+ }
+
+ private void removeScheduledTask() {
dependencies.removeMessages(handler, EVENT_START_QUERYTASK);
sharedLog.log("Remove EVENT_START_QUERYTASK"
+ ", current session: " + currentSessionId);
++currentSessionId;
- lastScheduledTask = null;
- }
-
- private void handleStartQueryTask(@NonNull QueryTask task) {
- executor.submit(task);
}
private boolean responseMatchesOptions(@NonNull MdnsResponse response,
@@ -323,15 +386,14 @@
*/
public boolean stopSendAndReceive(@NonNull MdnsServiceBrowserListener listener) {
ensureRunningOnHandlerThread(handler);
- synchronized (lock) {
- if (listeners.remove(listener) == null) {
- return listeners.isEmpty();
- }
- if (listeners.isEmpty()) {
- removeScheduledTaskLock();
- }
+ if (listeners.remove(listener) == null) {
return listeners.isEmpty();
}
+ if (listeners.isEmpty()) {
+ removeScheduledTask();
+ mdnsQueryScheduler.cancelScheduledRun();
+ }
+ return listeners.isEmpty();
}
/**
@@ -340,51 +402,51 @@
public synchronized void processResponse(@NonNull MdnsPacket packet,
@NonNull SocketKey socketKey) {
ensureRunningOnHandlerThread(handler);
- synchronized (lock) {
- // Augment the list of current known responses, and generated responses for resolve
- // requests if there is no known response
- final List<MdnsResponse> currentList = new ArrayList<>(instanceNameToResponse.values());
- List<MdnsResponse> additionalResponses = makeResponsesForResolve(socketKey);
- for (MdnsResponse additionalResponse : additionalResponses) {
- if (!instanceNameToResponse.containsKey(
- additionalResponse.getServiceInstanceName())) {
- currentList.add(additionalResponse);
- }
+ // Augment the list of current known responses, and generated responses for resolve
+ // requests if there is no known response
+ final List<MdnsResponse> cachedList =
+ serviceCache.getCachedServices(serviceType, socketKey);
+ final List<MdnsResponse> currentList = new ArrayList<>(cachedList);
+ List<MdnsResponse> additionalResponses = makeResponsesForResolve(socketKey);
+ for (MdnsResponse additionalResponse : additionalResponses) {
+ if (findMatchedResponse(
+ cachedList, additionalResponse.getServiceInstanceName()) == null) {
+ currentList.add(additionalResponse);
}
- final Pair<ArraySet<MdnsResponse>, ArrayList<MdnsResponse>> augmentedResult =
- responseDecoder.augmentResponses(packet, currentList,
- socketKey.getInterfaceIndex(), socketKey.getNetwork());
+ }
+ final Pair<ArraySet<MdnsResponse>, ArrayList<MdnsResponse>> augmentedResult =
+ responseDecoder.augmentResponses(packet, currentList,
+ socketKey.getInterfaceIndex(), socketKey.getNetwork());
- final ArraySet<MdnsResponse> modifiedResponse = augmentedResult.first;
- final ArrayList<MdnsResponse> allResponses = augmentedResult.second;
+ final ArraySet<MdnsResponse> modifiedResponse = augmentedResult.first;
+ final ArrayList<MdnsResponse> allResponses = augmentedResult.second;
- for (MdnsResponse response : allResponses) {
- if (modifiedResponse.contains(response)) {
- if (response.isGoodbye()) {
- onGoodbyeReceivedLocked(response.getServiceInstanceName());
- } else {
- onResponseModifiedLocked(response);
- }
- } else if (instanceNameToResponse.containsKey(response.getServiceInstanceName())) {
- // If the response is not modified and already in the cache. The cache will
- // need to be updated to refresh the last receipt time.
- instanceNameToResponse.put(response.getServiceInstanceName(), response);
+ for (MdnsResponse response : allResponses) {
+ final String serviceInstanceName = response.getServiceInstanceName();
+ if (modifiedResponse.contains(response)) {
+ if (response.isGoodbye()) {
+ onGoodbyeReceived(serviceInstanceName);
+ } else {
+ onResponseModified(response);
}
+ } else if (findMatchedResponse(cachedList, serviceInstanceName) != null) {
+ // If the response is not modified and already in the cache. The cache will
+ // need to be updated to refresh the last receipt time.
+ serviceCache.addOrUpdateService(serviceType, socketKey, response);
}
- if (dependencies.hasMessages(handler, EVENT_START_QUERYTASK)
- && lastScheduledTask != null
- && lastScheduledTask.config.shouldUseQueryBackoff()) {
- final long now = clock.elapsedRealtime();
- final long minRemainingTtl = getMinRemainingTtlLocked(now);
- final long timeToRun = calculateTimeToRun(lastScheduledTask,
- lastScheduledTask.config, now,
- minRemainingTtl, lastSentTime);
- if (timeToRun > lastScheduledTask.timeToRun) {
- QueryTaskConfig lastTaskConfig = lastScheduledTask.config;
- removeScheduledTaskLock();
- scheduleNextRunLocked(
- lastTaskConfig, minRemainingTtl, now, timeToRun, currentSessionId);
- }
+ }
+ if (dependencies.hasMessages(handler, EVENT_START_QUERYTASK)) {
+ final long now = clock.elapsedRealtime();
+ final long minRemainingTtl = getMinRemainingTtl(now);
+ MdnsQueryScheduler.ScheduledQueryTaskArgs args =
+ mdnsQueryScheduler.maybeRescheduleCurrentRun(now, minRemainingTtl,
+ lastSentTime, currentSessionId + 1);
+ if (args != null) {
+ removeScheduledTask();
+ dependencies.sendMessageDelayed(
+ handler,
+ handler.obtainMessage(EVENT_START_QUERYTASK, args),
+ calculateTimeToNextTask(args, now, sharedLog));
}
}
}
@@ -399,43 +461,41 @@
/** Notify all services are removed because the socket is destroyed. */
public void notifySocketDestroyed() {
ensureRunningOnHandlerThread(handler);
- synchronized (lock) {
- for (MdnsResponse response : instanceNameToResponse.values()) {
- final String name = response.getServiceInstanceName();
- if (name == null) continue;
- for (int i = 0; i < listeners.size(); i++) {
- if (!responseMatchesOptions(response, listeners.valueAt(i))) continue;
- final MdnsServiceBrowserListener listener = listeners.keyAt(i);
- final MdnsServiceInfo serviceInfo =
- buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
- if (response.isComplete()) {
- sharedLog.log("Socket destroyed. onServiceRemoved: " + name);
- listener.onServiceRemoved(serviceInfo);
- }
- sharedLog.log("Socket destroyed. onServiceNameRemoved: " + name);
- listener.onServiceNameRemoved(serviceInfo);
+ for (MdnsResponse response : serviceCache.getCachedServices(serviceType, socketKey)) {
+ final String name = response.getServiceInstanceName();
+ if (name == null) continue;
+ for (int i = 0; i < listeners.size(); i++) {
+ if (!responseMatchesOptions(response, listeners.valueAt(i))) continue;
+ final MdnsServiceBrowserListener listener = listeners.keyAt(i);
+ final MdnsServiceInfo serviceInfo =
+ buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
+ if (response.isComplete()) {
+ sharedLog.log("Socket destroyed. onServiceRemoved: " + name);
+ listener.onServiceRemoved(serviceInfo);
}
+ sharedLog.log("Socket destroyed. onServiceNameRemoved: " + name);
+ listener.onServiceNameRemoved(serviceInfo);
}
- removeScheduledTaskLock();
}
+ removeScheduledTask();
+ mdnsQueryScheduler.cancelScheduledRun();
}
- @GuardedBy("lock")
- private void onResponseModifiedLocked(@NonNull MdnsResponse response) {
+ private void onResponseModified(@NonNull MdnsResponse response) {
final String serviceInstanceName = response.getServiceInstanceName();
final MdnsResponse currentResponse =
- instanceNameToResponse.get(serviceInstanceName);
+ serviceCache.getCachedService(serviceInstanceName, serviceType, socketKey);
boolean newServiceFound = false;
boolean serviceBecomesComplete = false;
if (currentResponse == null) {
newServiceFound = true;
if (serviceInstanceName != null) {
- instanceNameToResponse.put(serviceInstanceName, response);
+ serviceCache.addOrUpdateService(serviceType, socketKey, response);
}
} else {
boolean before = currentResponse.isComplete();
- instanceNameToResponse.put(serviceInstanceName, response);
+ serviceCache.addOrUpdateService(serviceType, socketKey, response);
boolean after = response.isComplete();
serviceBecomesComplete = !before && after;
}
@@ -467,9 +527,9 @@
}
}
- @GuardedBy("lock")
- private void onGoodbyeReceivedLocked(@Nullable String serviceInstanceName) {
- final MdnsResponse response = instanceNameToResponse.remove(serviceInstanceName);
+ private void onGoodbyeReceived(@Nullable String serviceInstanceName) {
+ final MdnsResponse response =
+ serviceCache.removeService(serviceInstanceName, serviceType, socketKey);
if (response == null) {
return;
}
@@ -499,145 +559,6 @@
return new MdnsPacketWriter(DEFAULT_MTU);
}
- // A configuration for the PeriodicalQueryTask that contains parameters to build a query packet.
- // Call to getConfigForNextRun returns a config that can be used to build the next query task.
- @VisibleForTesting
- static class QueryTaskConfig {
-
- private static final int INITIAL_TIME_BETWEEN_BURSTS_MS =
- (int) MdnsConfigs.initialTimeBetweenBurstsMs();
- private static final int TIME_BETWEEN_BURSTS_MS = (int) MdnsConfigs.timeBetweenBurstsMs();
- private static final int QUERIES_PER_BURST = (int) MdnsConfigs.queriesPerBurst();
- private static final int TIME_BETWEEN_QUERIES_IN_BURST_MS =
- (int) MdnsConfigs.timeBetweenQueriesInBurstMs();
- private static final int QUERIES_PER_BURST_PASSIVE_MODE =
- (int) MdnsConfigs.queriesPerBurstPassive();
- private static final int UNSIGNED_SHORT_MAX_VALUE = 65536;
- // The following fields are used by QueryTask so we need to test them.
- @VisibleForTesting
- final List<String> subtypes;
- private final boolean alwaysAskForUnicastResponse =
- MdnsConfigs.alwaysAskForUnicastResponseInEachBurst();
- private final boolean usePassiveMode;
- private final boolean onlyUseIpv6OnIpv6OnlyNetworks;
- private final int numOfQueriesBeforeBackoff;
- @VisibleForTesting
- final int transactionId;
- @VisibleForTesting
- final boolean expectUnicastResponse;
- private final int queriesPerBurst;
- private final int timeBetweenBurstsInMs;
- private final int burstCounter;
- private final long delayUntilNextTaskWithoutBackoffMs;
- private final boolean isFirstBurst;
- private final long queryCount;
- @NonNull private final SocketKey socketKey;
-
-
- QueryTaskConfig(@NonNull QueryTaskConfig other, long queryCount, int transactionId,
- boolean expectUnicastResponse, boolean isFirstBurst, int burstCounter,
- int queriesPerBurst, int timeBetweenBurstsInMs,
- long delayUntilNextTaskWithoutBackoffMs) {
- this.subtypes = new ArrayList<>(other.subtypes);
- this.usePassiveMode = other.usePassiveMode;
- this.onlyUseIpv6OnIpv6OnlyNetworks = other.onlyUseIpv6OnIpv6OnlyNetworks;
- this.numOfQueriesBeforeBackoff = other.numOfQueriesBeforeBackoff;
- this.transactionId = transactionId;
- this.expectUnicastResponse = expectUnicastResponse;
- this.queriesPerBurst = queriesPerBurst;
- this.timeBetweenBurstsInMs = timeBetweenBurstsInMs;
- this.burstCounter = burstCounter;
- this.delayUntilNextTaskWithoutBackoffMs = delayUntilNextTaskWithoutBackoffMs;
- this.isFirstBurst = isFirstBurst;
- this.queryCount = queryCount;
- this.socketKey = other.socketKey;
- }
- QueryTaskConfig(@NonNull Collection<String> subtypes,
- boolean usePassiveMode,
- boolean onlyUseIpv6OnIpv6OnlyNetworks,
- int numOfQueriesBeforeBackoff,
- @Nullable SocketKey socketKey) {
- this.usePassiveMode = usePassiveMode;
- this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
- this.numOfQueriesBeforeBackoff = numOfQueriesBeforeBackoff;
- this.subtypes = new ArrayList<>(subtypes);
- this.queriesPerBurst = QUERIES_PER_BURST;
- this.burstCounter = 0;
- this.transactionId = 1;
- this.expectUnicastResponse = true;
- this.isFirstBurst = true;
- // Config the scan frequency based on the scan mode.
- if (this.usePassiveMode) {
- // In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and then
- // in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
- // queries.
- this.timeBetweenBurstsInMs = TIME_BETWEEN_BURSTS_MS;
- } else {
- // In active scan mode, sends a burst of QUERIES_PER_BURST queries,
- // TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
- // then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
- // doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
- this.timeBetweenBurstsInMs = INITIAL_TIME_BETWEEN_BURSTS_MS;
- }
- this.socketKey = socketKey;
- this.queryCount = 0;
- this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
- }
-
- QueryTaskConfig getConfigForNextRun() {
- long newQueryCount = queryCount + 1;
- int newTransactionId = transactionId + 1;
- if (newTransactionId > UNSIGNED_SHORT_MAX_VALUE) {
- newTransactionId = 1;
- }
- boolean newExpectUnicastResponse = false;
- boolean newIsFirstBurst = isFirstBurst;
- int newQueriesPerBurst = queriesPerBurst;
- int newBurstCounter = burstCounter + 1;
- long newDelayUntilNextTaskWithoutBackoffMs = delayUntilNextTaskWithoutBackoffMs;
- int newTimeBetweenBurstsInMs = timeBetweenBurstsInMs;
- // Only the first query expects uni-cast response.
- if (newBurstCounter == queriesPerBurst) {
- newBurstCounter = 0;
-
- if (alwaysAskForUnicastResponse) {
- newExpectUnicastResponse = true;
- }
- // In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and
- // then in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
- // queries.
- if (isFirstBurst) {
- newIsFirstBurst = false;
- if (usePassiveMode) {
- newQueriesPerBurst = QUERIES_PER_BURST_PASSIVE_MODE;
- }
- }
- // In active scan mode, sends a burst of QUERIES_PER_BURST queries,
- // TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
- // then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
- // doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
- newDelayUntilNextTaskWithoutBackoffMs = timeBetweenBurstsInMs;
- if (timeBetweenBurstsInMs < TIME_BETWEEN_BURSTS_MS) {
- newTimeBetweenBurstsInMs = Math.min(timeBetweenBurstsInMs * 2,
- TIME_BETWEEN_BURSTS_MS);
- }
- } else {
- newDelayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
- }
- return new QueryTaskConfig(this, newQueryCount, newTransactionId,
- newExpectUnicastResponse, newIsFirstBurst, newBurstCounter, newQueriesPerBurst,
- newTimeBetweenBurstsInMs, newDelayUntilNextTaskWithoutBackoffMs);
- }
-
- private boolean shouldUseQueryBackoff() {
- // Don't enable backoff mode during the burst or in the first burst
- if (burstCounter != 0 || isFirstBurst) {
- return false;
- }
- return queryCount > numOfQueriesBeforeBackoff;
- }
- }
-
private List<MdnsResponse> makeResponsesForResolve(@NonNull SocketKey socketKey) {
final List<MdnsResponse> resolveResponses = new ArrayList<>();
for (int i = 0; i < listeners.size(); i++) {
@@ -645,7 +566,8 @@
if (resolveName == null) {
continue;
}
- MdnsResponse knownResponse = instanceNameToResponse.get(resolveName);
+ MdnsResponse knownResponse =
+ serviceCache.getCachedService(resolveName, serviceType, socketKey);
if (knownResponse == null) {
final ArrayList<String> instanceFullName = new ArrayList<>(
serviceTypeLabels.length + 1);
@@ -660,34 +582,66 @@
return resolveResponses;
}
+ private void tryRemoveServiceAfterTtlExpires() {
+ if (!shouldRemoveServiceAfterTtlExpires()) return;
+
+ Iterator<MdnsResponse> iter =
+ serviceCache.getCachedServices(serviceType, socketKey).iterator();
+ while (iter.hasNext()) {
+ MdnsResponse existingResponse = iter.next();
+ final String serviceInstanceName = existingResponse.getServiceInstanceName();
+ if (existingResponse.hasServiceRecord()
+ && existingResponse.getServiceRecord()
+ .getRemainingTTL(clock.elapsedRealtime()) == 0) {
+ serviceCache.removeService(serviceInstanceName, serviceType, socketKey);
+ for (int i = 0; i < listeners.size(); i++) {
+ if (!responseMatchesOptions(existingResponse, listeners.valueAt(i))) {
+ continue;
+ }
+ final MdnsServiceBrowserListener listener = listeners.keyAt(i);
+ if (serviceInstanceName != null) {
+ final MdnsServiceInfo serviceInfo = buildMdnsServiceInfoFromResponse(
+ existingResponse, serviceTypeLabels);
+ if (existingResponse.isComplete()) {
+ sharedLog.log("TTL expired. onServiceRemoved: " + serviceInfo);
+ listener.onServiceRemoved(serviceInfo);
+ }
+ sharedLog.log("TTL expired. onServiceNameRemoved: " + serviceInfo);
+ listener.onServiceNameRemoved(serviceInfo);
+ }
+ }
+ }
+ }
+ }
+
+
+ private static class QuerySentArguments {
+ private final int transactionId;
+ private final List<String> subTypes = new ArrayList<>();
+ private final MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs;
+
+ QuerySentArguments(int transactionId, @NonNull List<String> subTypes,
+ @NonNull MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs) {
+ this.transactionId = transactionId;
+ this.subTypes.addAll(subTypes);
+ this.taskArgs = taskArgs;
+ }
+ }
+
// A FutureTask that enqueues a single query, and schedule a new FutureTask for the next task.
private class QueryTask implements Runnable {
-
- private final QueryTaskConfig config;
- private final long timeToRun;
- private final long minTtlExpirationTimeWhenScheduled;
- private final long sessionId;
-
- QueryTask(@NonNull QueryTaskConfig config, long timeToRun,
- long minTtlExpirationTimeWhenScheduled,
- long sessionId) {
- this.config = config;
- this.timeToRun = timeToRun;
- this.minTtlExpirationTimeWhenScheduled = minTtlExpirationTimeWhenScheduled;
- this.sessionId = sessionId;
+ private final MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs;
+ private final List<MdnsResponse> servicesToResolve = new ArrayList<>();
+ private final boolean sendDiscoveryQueries;
+ QueryTask(@NonNull MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs,
+ @NonNull List<MdnsResponse> servicesToResolve, boolean sendDiscoveryQueries) {
+ this.taskArgs = taskArgs;
+ this.servicesToResolve.addAll(servicesToResolve);
+ this.sendDiscoveryQueries = sendDiscoveryQueries;
}
@Override
public void run() {
- final List<MdnsResponse> servicesToResolve;
- final boolean sendDiscoveryQueries;
- synchronized (lock) {
- // The listener is requesting to resolve a service that has no info in
- // cache. Use the provided name to generate a minimal response, so other records are
- // queried to complete it.
- servicesToResolve = makeResponsesForResolve(config.socketKey);
- sendDiscoveryQueries = servicesToResolve.size() < listeners.size();
- }
Pair<Integer, List<String>> result;
try {
result =
@@ -695,104 +649,30 @@
socketClient,
createMdnsPacketWriter(),
serviceType,
- config.subtypes,
- config.expectUnicastResponse,
- config.transactionId,
- config.socketKey,
- config.onlyUseIpv6OnIpv6OnlyNetworks,
+ taskArgs.config.subtypes,
+ taskArgs.config.expectUnicastResponse,
+ taskArgs.config.transactionId,
+ taskArgs.config.socketKey,
+ taskArgs.config.onlyUseIpv6OnIpv6OnlyNetworks,
sendDiscoveryQueries,
servicesToResolve,
- clock)
+ clock,
+ sharedLog)
.call();
} catch (RuntimeException e) {
sharedLog.e(String.format("Failed to run EnqueueMdnsQueryCallable for subtype: %s",
- TextUtils.join(",", config.subtypes)), e);
- result = null;
+ TextUtils.join(",", taskArgs.config.subtypes)), e);
+ result = Pair.create(INVALID_TRANSACTION_ID, new ArrayList<>());
}
- synchronized (lock) {
- if (MdnsConfigs.useSessionIdToScheduleMdnsTask()) {
- // In case that the task is not canceled successfully, use session ID to check
- // if this task should continue to schedule more.
- if (sessionId != currentSessionId) {
- return;
- }
- }
-
- if ((result != null)) {
- for (int i = 0; i < listeners.size(); i++) {
- listeners.keyAt(i).onDiscoveryQuerySent(result.second, result.first);
- }
- }
- if (shouldRemoveServiceAfterTtlExpires()) {
- Iterator<MdnsResponse> iter = instanceNameToResponse.values().iterator();
- while (iter.hasNext()) {
- MdnsResponse existingResponse = iter.next();
- if (existingResponse.hasServiceRecord()
- && existingResponse
- .getServiceRecord()
- .getRemainingTTL(clock.elapsedRealtime())
- == 0) {
- iter.remove();
- for (int i = 0; i < listeners.size(); i++) {
- if (!responseMatchesOptions(existingResponse,
- listeners.valueAt(i))) {
- continue;
- }
- final MdnsServiceBrowserListener listener = listeners.keyAt(i);
- if (existingResponse.getServiceInstanceName() != null) {
- final MdnsServiceInfo serviceInfo =
- buildMdnsServiceInfoFromResponse(
- existingResponse, serviceTypeLabels);
- if (existingResponse.isComplete()) {
- sharedLog.log("TTL expired. onServiceRemoved: "
- + serviceInfo);
- listener.onServiceRemoved(serviceInfo);
- }
- sharedLog.log("TTL expired. onServiceNameRemoved: "
- + serviceInfo);
- listener.onServiceNameRemoved(serviceInfo);
- }
- }
- }
- }
- }
- QueryTaskConfig nextRunConfig = this.config.getConfigForNextRun();
- final long now = clock.elapsedRealtime();
- lastSentTime = now;
- final long minRemainingTtl = getMinRemainingTtlLocked(now);
- final long timeToRun = calculateTimeToRun(this, nextRunConfig, now,
- minRemainingTtl, lastSentTime);
- scheduleNextRunLocked(nextRunConfig, minRemainingTtl, now, timeToRun,
- lastScheduledTask.sessionId);
- }
+ dependencies.sendMessage(
+ handler, handler.obtainMessage(EVENT_QUERY_RESULT,
+ new QuerySentArguments(result.first, result.second, taskArgs)));
}
}
- private static long calculateTimeToRun(@NonNull QueryTask lastScheduledTask,
- QueryTaskConfig queryTaskConfig, long now, long minRemainingTtl, long lastSentTime) {
- final long baseDelayInMs = queryTaskConfig.delayUntilNextTaskWithoutBackoffMs;
- if (!queryTaskConfig.shouldUseQueryBackoff()) {
- return lastSentTime + baseDelayInMs;
- }
- if (minRemainingTtl <= 0) {
- // There's no service, or there is an expired service. In any case, schedule for the
- // minimum time, which is the base delay.
- return lastSentTime + baseDelayInMs;
- }
- // If the next TTL expiration time hasn't changed, then use previous calculated timeToRun.
- if (lastSentTime < now
- && lastScheduledTask.minTtlExpirationTimeWhenScheduled == now + minRemainingTtl) {
- // Use the original scheduling time if the TTL has not changed, to avoid continuously
- // rescheduling to 80% of the remaining TTL as time passes
- return lastScheduledTask.timeToRun;
- }
- return Math.max(now + (long) (0.8 * minRemainingTtl), lastSentTime + baseDelayInMs);
- }
-
- @GuardedBy("lock")
- private long getMinRemainingTtlLocked(long now) {
+ private long getMinRemainingTtl(long now) {
long minRemainingTtl = Long.MAX_VALUE;
- for (MdnsResponse response : instanceNameToResponse.values()) {
+ for (MdnsResponse response : serviceCache.getCachedServices(serviceType, socketKey)) {
if (!response.isComplete()) {
continue;
}
@@ -809,21 +689,11 @@
return minRemainingTtl == Long.MAX_VALUE ? 0 : minRemainingTtl;
}
- @GuardedBy("lock")
- @NonNull
- private void scheduleNextRunLocked(@NonNull QueryTaskConfig nextRunConfig,
- long minRemainingTtl,
- long timeWhenScheduled, long timeToRun, long sessionId) {
- lastScheduledTask = new QueryTask(nextRunConfig, timeToRun,
- minRemainingTtl + timeWhenScheduled, sessionId);
- // The timeWhenScheduled could be greater than the timeToRun if the Runnable is delayed.
- long timeToNextTasksWithBackoffInMs = Math.max(timeToRun - timeWhenScheduled, 0);
- sharedLog.log(
- String.format("Next run: sessionId: %d, in %d ms", lastScheduledTask.sessionId,
- timeToNextTasksWithBackoffInMs));
- dependencies.sendMessageDelayed(
- handler,
- handler.obtainMessage(EVENT_START_QUERYTASK, lastScheduledTask),
- timeToNextTasksWithBackoffInMs);
+ private static long calculateTimeToNextTask(MdnsQueryScheduler.ScheduledQueryTaskArgs args,
+ long now, SharedLog sharedLog) {
+ long timeToNextTasksWithBackoffInMs = Math.max(args.timeToRun - now, 0);
+ sharedLog.log(String.format("Next run: sessionId: %d, in %d ms",
+ args.sessionId, timeToNextTasksWithBackoffInMs));
+ return timeToNextTasksWithBackoffInMs;
}
}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java
index cdd9f76..d690032 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java
@@ -21,7 +21,7 @@
import android.net.Network;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.connectivity.mdns.util.MdnsLogger;
+import com.android.net.module.util.SharedLog;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -37,8 +37,6 @@
* @see MulticastSocket for javadoc of each public method.
*/
public class MdnsSocket {
- private static final MdnsLogger LOGGER = new MdnsLogger("MdnsSocket");
-
static final int INTERFACE_INDEX_UNSPECIFIED = -1;
public static final InetSocketAddress MULTICAST_IPV4_ADDRESS =
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT);
@@ -47,19 +45,22 @@
private final MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider;
private final MulticastSocket multicastSocket;
private boolean isOnIPv6OnlyNetwork;
+ private final SharedLog sharedLog;
public MdnsSocket(
- @NonNull MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider, int port)
+ @NonNull MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider, int port,
+ SharedLog sharedLog)
throws IOException {
- this(multicastNetworkInterfaceProvider, new MulticastSocket(port));
+ this(multicastNetworkInterfaceProvider, new MulticastSocket(port), sharedLog);
}
@VisibleForTesting
MdnsSocket(@NonNull MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider,
- MulticastSocket multicastSocket) throws IOException {
+ MulticastSocket multicastSocket, SharedLog sharedLog) throws IOException {
this.multicastNetworkInterfaceProvider = multicastNetworkInterfaceProvider;
this.multicastNetworkInterfaceProvider.startWatchingConnectivityChanges();
this.multicastSocket = multicastSocket;
+ this.sharedLog = sharedLog;
// RFC Spec: https://tools.ietf.org/html/rfc6762
// Time to live is set 255, which is similar to the jMDNS implementation.
multicastSocket.setTimeToLive(255);
@@ -130,7 +131,7 @@
try {
return multicastSocket.getNetworkInterface().getIndex();
} catch (SocketException e) {
- LOGGER.e("Failed to retrieve interface index for socket.", e);
+ sharedLog.e("Failed to retrieve interface index for socket.", e);
return -1;
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
index 9c9812d..d18a19b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -27,7 +27,7 @@
import android.text.format.DateUtils;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.connectivity.mdns.util.MdnsLogger;
+import com.android.net.module.util.SharedLog;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -57,7 +57,6 @@
private static final String CAST_SENDER_LOG_SOURCE = "CAST_SENDER_SDK";
private static final String CAST_PREFS_NAME = "google_cast";
private static final String PREF_CAST_SENDER_ID = "PREF_CAST_SENDER_ID";
- private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
private static final String MULTICAST_TYPE = "multicast";
private static final String UNICAST_TYPE = "unicast";
@@ -105,8 +104,11 @@
@Nullable private Timer logMdnsPacketTimer;
private AtomicInteger packetsCount;
@Nullable private Timer checkMulticastResponseTimer;
+ private final SharedLog sharedLog;
- public MdnsSocketClient(@NonNull Context context, @NonNull MulticastLock multicastLock) {
+ public MdnsSocketClient(@NonNull Context context, @NonNull MulticastLock multicastLock,
+ SharedLog sharedLog) {
+ this.sharedLog = sharedLog;
this.context = context;
this.multicastLock = multicastLock;
if (useSeparateSocketForUnicast) {
@@ -125,7 +127,7 @@
@Override
public synchronized void startDiscovery() throws IOException {
if (multicastSocket != null) {
- LOGGER.w("Discovery is already in progress.");
+ sharedLog.w("Discovery is already in progress.");
return;
}
@@ -136,11 +138,11 @@
shouldStopSocketLoop = false;
try {
// TODO (changed when importing code): consider setting thread stats tag
- multicastSocket = createMdnsSocket(MdnsConstants.MDNS_PORT);
+ multicastSocket = createMdnsSocket(MdnsConstants.MDNS_PORT, sharedLog);
multicastSocket.joinGroup();
if (useSeparateSocketForUnicast) {
// For unicast, use port 0 and the system will assign it with any available port.
- unicastSocket = createMdnsSocket(0);
+ unicastSocket = createMdnsSocket(0, sharedLog);
}
multicastLock.acquire();
} catch (IOException e) {
@@ -164,7 +166,7 @@
@RequiresPermission(permission.CHANGE_WIFI_MULTICAST_STATE)
@Override
public void stopDiscovery() {
- LOGGER.log("Stop discovery.");
+ sharedLog.log("Stop discovery.");
if (multicastSocket == null && unicastSocket == null) {
return;
}
@@ -233,7 +235,7 @@
private void sendMdnsPacket(DatagramPacket packet, Queue<DatagramPacket> packetQueueToUse,
boolean onlyUseIpv6OnIpv6OnlyNetworks) {
if (shouldStopSocketLoop && !MdnsConfigs.allowAddMdnsPacketAfterDiscoveryStops()) {
- LOGGER.w("sendMdnsPacket() is called after discovery already stopped");
+ sharedLog.w("sendMdnsPacket() is called after discovery already stopped");
return;
}
@@ -260,7 +262,7 @@
private void createAndStartSendThread() {
if (sendThread != null) {
- LOGGER.w("A socket thread already exists.");
+ sharedLog.w("A socket thread already exists.");
return;
}
sendThread = new Thread(this::sendThreadMain);
@@ -270,7 +272,7 @@
private void createAndStartReceiverThreads() {
if (multicastReceiveThread != null) {
- LOGGER.w("A multicast receiver thread already exists.");
+ sharedLog.w("A multicast receiver thread already exists.");
return;
}
multicastReceiveThread =
@@ -292,12 +294,12 @@
}
private void triggerSendThread() {
- LOGGER.log("Trigger send thread.");
+ sharedLog.log("Trigger send thread.");
Thread sendThread = this.sendThread;
if (sendThread != null) {
sendThread.interrupt();
} else {
- LOGGER.w("Socket thread is null");
+ sharedLog.w("Socket thread is null");
}
}
@@ -314,9 +316,9 @@
}
private void waitForSendThreadToStop() {
- LOGGER.log("wait For Send Thread To Stop");
+ sharedLog.log("wait For Send Thread To Stop");
if (sendThread == null) {
- LOGGER.w("socket thread is already dead.");
+ sharedLog.w("socket thread is already dead.");
return;
}
waitForThread(sendThread);
@@ -331,7 +333,7 @@
thread.interrupt();
thread.join(waitMs);
if (thread.isAlive()) {
- LOGGER.w("Failed to join thread: " + thread);
+ sharedLog.w("Failed to join thread: " + thread);
}
break;
} catch (InterruptedException e) {
@@ -390,13 +392,13 @@
}
}
} finally {
- LOGGER.log("Send thread stopped.");
+ sharedLog.log("Send thread stopped.");
try {
if (multicastSocket != null) {
multicastSocket.leaveGroup();
}
} catch (Exception t) {
- LOGGER.e("Failed to leave the group.", t);
+ sharedLog.e("Failed to leave the group.", t);
}
// Close the socket first. This is the only way to interrupt a blocking receive.
@@ -409,7 +411,7 @@
unicastSocket.close();
}
} catch (RuntimeException t) {
- LOGGER.e("Failed to close the mdns socket.", t);
+ sharedLog.e("Failed to close the mdns socket.", t);
}
}
}
@@ -439,11 +441,11 @@
}
} catch (IOException e) {
if (!shouldStopSocketLoop) {
- LOGGER.e("Failed to receive mDNS packets.", e);
+ sharedLog.e("Failed to receive mDNS packets.", e);
}
}
}
- LOGGER.log("Receive thread stopped.");
+ sharedLog.log("Receive thread stopped.");
}
private int processResponsePacket(@NonNull DatagramPacket packet, String responseType,
@@ -454,7 +456,7 @@
try {
response = MdnsResponseDecoder.parseResponse(packet.getData(), packet.getLength());
} catch (MdnsPacket.ParseException e) {
- LOGGER.w(String.format("Error while decoding %s packet (%d): %d",
+ sharedLog.w(String.format("Error while decoding %s packet (%d): %d",
responseType, packetNumber, e.code));
if (callback != null) {
callback.onFailedToParseMdnsResponse(packetNumber, e.code,
@@ -476,8 +478,9 @@
}
@VisibleForTesting
- MdnsSocket createMdnsSocket(int port) throws IOException {
- return new MdnsSocket(new MulticastNetworkInterfaceProvider(context), port);
+ MdnsSocket createMdnsSocket(int port, SharedLog sharedLog) throws IOException {
+ return new MdnsSocket(new MulticastNetworkInterfaceProvider(context, sharedLog), port,
+ sharedLog);
}
private void sendPackets(List<DatagramPacket> packets, MdnsSocket socket) {
@@ -487,7 +490,7 @@
break;
}
try {
- LOGGER.log("Sending a %s mDNS packet...", requestType);
+ sharedLog.log(String.format("Sending a %s mDNS packet...", requestType));
socket.send(packet);
// Start the timer task to monitor the response.
@@ -516,7 +519,7 @@
}
if ((!receivedMulticastResponse)
&& receivedUnicastResponse) {
- LOGGER.e(String.format(
+ sharedLog.e(String.format(
"Haven't received multicast response"
+ " in the last %d ms.",
checkMulticastResponseIntervalMs));
@@ -531,7 +534,7 @@
}
}
} catch (IOException e) {
- LOGGER.e(String.format("Failed to send a %s mDNS packet.", requestType), e);
+ sharedLog.e(String.format("Failed to send a %s mDNS packet.", requestType), e);
}
}
packets.clear();
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 6925b49..23c5a4d 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
@@ -44,7 +44,6 @@
import android.os.Handler;
import android.os.Looper;
import android.util.ArrayMap;
-import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -118,7 +117,7 @@
if (mWifiP2pTetherInterface != null) {
if (newP2pIface != null) {
- Log.wtf(TAG, "Wifi p2p interface is changed from " + mWifiP2pTetherInterface
+ mSharedLog.wtf("Wifi p2p interface is changed from " + mWifiP2pTetherInterface
+ " to " + newP2pIface + " without null broadcast");
}
// Remove the socket.
@@ -133,7 +132,7 @@
if (newP2pIface != null && !socketAlreadyExists) {
// Create a socket for wifi p2p interface.
final int ifaceIndex =
- mDependencies.getNetworkInterfaceIndexByName(newP2pIface);
+ mDependencies.getNetworkInterfaceIndexByName(newP2pIface, mSharedLog);
createSocket(LOCAL_NET, createLPForTetheredInterface(newP2pIface, ifaceIndex));
}
}
@@ -233,21 +232,23 @@
/*** Create a MdnsInterfaceSocket */
public MdnsInterfaceSocket createMdnsInterfaceSocket(
@NonNull NetworkInterface networkInterface, int port, @NonNull Looper looper,
- @NonNull byte[] packetReadBuffer) throws IOException {
- return new MdnsInterfaceSocket(networkInterface, port, looper, packetReadBuffer);
+ @NonNull byte[] packetReadBuffer, @NonNull SharedLog sharedLog) throws IOException {
+ return new MdnsInterfaceSocket(networkInterface, port, looper, packetReadBuffer,
+ sharedLog);
}
/*** Get network interface by given interface name */
- public int getNetworkInterfaceIndexByName(@NonNull final String ifaceName) {
+ public int getNetworkInterfaceIndexByName(@NonNull final String ifaceName,
+ @NonNull SharedLog sharedLog) {
final NetworkInterface iface;
try {
iface = NetworkInterface.getByName(ifaceName);
} catch (SocketException e) {
- Log.e(TAG, "Error querying interface", e);
+ sharedLog.e("Error querying interface", e);
return IFACE_IDX_NOT_EXIST;
}
if (iface == null) {
- Log.e(TAG, "Interface not found: " + ifaceName);
+ sharedLog.e("Interface not found: " + ifaceName);
return IFACE_IDX_NOT_EXIST;
}
return iface.getIndex();
@@ -335,7 +336,7 @@
ensureRunningOnHandlerThread(mHandler);
mRequestStop = false; // Reset stop request flag.
if (mMonitoringSockets) {
- Log.d(TAG, "Already monitoring sockets.");
+ mSharedLog.v("Already monitoring sockets.");
return;
}
mSharedLog.i("Start monitoring sockets.");
@@ -390,7 +391,7 @@
public void requestStopWhenInactive() {
ensureRunningOnHandlerThread(mHandler);
if (!mMonitoringSockets) {
- Log.d(TAG, "Monitoring sockets hasn't been started.");
+ mSharedLog.v("Monitoring sockets hasn't been started.");
return;
}
mRequestStop = true;
@@ -410,7 +411,7 @@
mActiveNetworksLinkProperties.put(network, lp);
if (!matchRequestedNetwork(network)) {
if (DBG) {
- Log.d(TAG, "Ignore LinkProperties change. There is no request for the"
+ mSharedLog.v("Ignore LinkProperties change. There is no request for the"
+ " Network:" + network);
}
return;
@@ -428,7 +429,7 @@
@NonNull final List<LinkAddress> updatedAddresses) {
for (int i = 0; i < mTetherInterfaceSockets.size(); ++i) {
String tetheringInterfaceName = mTetherInterfaceSockets.keyAt(i);
- if (mDependencies.getNetworkInterfaceIndexByName(tetheringInterfaceName)
+ if (mDependencies.getNetworkInterfaceIndexByName(tetheringInterfaceName, mSharedLog)
== ifaceIndex) {
updateSocketInfoAddress(null /* network */,
mTetherInterfaceSockets.valueAt(i), updatedAddresses);
@@ -462,7 +463,7 @@
// tethering are only created if there is a request for all networks (interfaces).
// 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"
+ mSharedLog.v("Ignore tether interfaces change. There is no request for all"
+ " networks.");
}
current.clear();
@@ -482,7 +483,7 @@
continue;
}
- int ifaceIndex = mDependencies.getNetworkInterfaceIndexByName(name);
+ int ifaceIndex = mDependencies.getNetworkInterfaceIndexByName(name, mSharedLog);
createSocket(LOCAL_NET, createLPForTetheredInterface(name, ifaceIndex));
}
for (String name : interfaceDiff.removed) {
@@ -495,7 +496,7 @@
private void createSocket(NetworkKey networkKey, LinkProperties lp) {
final String interfaceName = lp.getInterfaceName();
if (interfaceName == null) {
- Log.e(TAG, "Can not create socket with null interface name.");
+ mSharedLog.e("Can not create socket with null interface name.");
return;
}
@@ -514,7 +515,7 @@
if (knownTransports != null) {
transports = knownTransports;
} else {
- Log.wtf(TAG, "transports is missing for key: " + networkKey);
+ mSharedLog.wtf("transports is missing for key: " + networkKey);
transports = new int[0];
}
}
@@ -525,7 +526,8 @@
mSharedLog.log("Create socket on net:" + networkKey + ", ifName:" + interfaceName);
final MdnsInterfaceSocket socket = mDependencies.createMdnsInterfaceSocket(
networkInterface.getNetworkInterface(), MdnsConstants.MDNS_PORT, mLooper,
- mPacketReadBuffer);
+ mPacketReadBuffer, mSharedLog.forSubComponent(
+ MdnsInterfaceSocket.class.getSimpleName() + "/" + interfaceName));
final List<LinkAddress> addresses = lp.getLinkAddresses();
final Network network =
networkKey == LOCAL_NET ? null : ((NetworkAsKey) networkKey).mNetwork;
@@ -637,7 +639,7 @@
final LinkProperties lp = mActiveNetworksLinkProperties.get(network);
if (lp == null) {
// The requested network is not existed. Maybe wait for LinkProperties change later.
- if (DBG) Log.d(TAG, "There is no LinkProperties for this network:" + network);
+ if (DBG) mSharedLog.v("There is no LinkProperties for this network:" + network);
return;
}
createSocket(new NetworkAsKey(network), lp);
@@ -652,7 +654,8 @@
private void retrieveAndNotifySocketFromInterface(String interfaceName, SocketCallback cb) {
final SocketInfo socketInfo = mTetherInterfaceSockets.get(interfaceName);
if (socketInfo == null) {
- int ifaceIndex = mDependencies.getNetworkInterfaceIndexByName(interfaceName);
+ int ifaceIndex = mDependencies.getNetworkInterfaceIndexByName(interfaceName,
+ mSharedLog);
createSocket(
LOCAL_NET,
createLPForTetheredInterface(interfaceName, ifaceIndex));
diff --git a/service-t/src/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java b/service-t/src/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
index f248c98..da82e96 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
@@ -22,7 +22,7 @@
import android.net.Network;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.connectivity.mdns.util.MdnsLogger;
+import com.android.net.module.util.SharedLog;
import java.io.IOException;
import java.net.Inet4Address;
@@ -41,7 +41,7 @@
public class MulticastNetworkInterfaceProvider {
private static final String TAG = "MdnsNIProvider";
- private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
+ private final SharedLog sharedLog;
private static final boolean PREFER_IPV6 = MdnsConfigs.preferIpv6();
private final List<NetworkInterfaceWrapper> multicastNetworkInterfaces = new ArrayList<>();
@@ -51,10 +51,12 @@
private volatile boolean connectivityChanged = true;
@SuppressWarnings("nullness:methodref.receiver.bound")
- public MulticastNetworkInterfaceProvider(@NonNull Context context) {
+ public MulticastNetworkInterfaceProvider(@NonNull Context context,
+ @NonNull SharedLog sharedLog) {
+ this.sharedLog = sharedLog;
// IMPORT CHANGED
this.connectivityMonitor = new ConnectivityMonitorWithConnectivityManager(
- context, this::onConnectivityChanged);
+ context, this::onConnectivityChanged, sharedLog);
}
private synchronized void onConnectivityChanged() {
@@ -83,7 +85,7 @@
connectivityChanged = false;
updateMulticastNetworkInterfaces();
if (multicastNetworkInterfaces.isEmpty()) {
- LOGGER.log("No network interface available for mDNS scanning.");
+ sharedLog.log("No network interface available for mDNS scanning.");
}
}
return new ArrayList<>(multicastNetworkInterfaces);
@@ -93,7 +95,7 @@
multicastNetworkInterfaces.clear();
List<NetworkInterfaceWrapper> networkInterfaceWrappers = getNetworkInterfaces();
for (NetworkInterfaceWrapper interfaceWrapper : networkInterfaceWrappers) {
- if (canScanOnInterface(interfaceWrapper)) {
+ if (canScanOnInterface(interfaceWrapper, sharedLog)) {
multicastNetworkInterfaces.add(interfaceWrapper);
}
}
@@ -133,10 +135,10 @@
}
}
} catch (SocketException e) {
- LOGGER.e("Failed to get network interfaces.", e);
+ sharedLog.e("Failed to get network interfaces.", e);
} catch (NullPointerException e) {
// Android R has a bug that could lead to a NPE. See b/159277702.
- LOGGER.e("Failed to call getNetworkInterfaces API", e);
+ sharedLog.e("Failed to call getNetworkInterfaces API", e);
}
return networkInterfaceWrappers;
@@ -148,7 +150,8 @@
}
/*** Check whether given network interface can support mdns */
- private static boolean canScanOnInterface(@Nullable NetworkInterfaceWrapper networkInterface) {
+ private static boolean canScanOnInterface(@Nullable NetworkInterfaceWrapper networkInterface,
+ @NonNull SharedLog sharedLog) {
try {
if ((networkInterface == null)
|| networkInterface.isLoopback()
@@ -160,7 +163,7 @@
}
return hasInet4Address(networkInterface) || hasInet6Address(networkInterface);
} catch (IOException e) {
- LOGGER.e(String.format("Failed to check interface %s.",
+ sharedLog.e(String.format("Failed to check interface %s.",
networkInterface.getNetworkInterface().getDisplayName()), e);
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java b/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java
new file mode 100644
index 0000000..19282b0
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A configuration for the PeriodicalQueryTask that contains parameters to build a query packet.
+ * Call to getConfigForNextRun returns a config that can be used to build the next query task.
+ */
+public class QueryTaskConfig {
+
+ private static final int INITIAL_TIME_BETWEEN_BURSTS_MS =
+ (int) MdnsConfigs.initialTimeBetweenBurstsMs();
+ private static final int TIME_BETWEEN_BURSTS_MS = (int) MdnsConfigs.timeBetweenBurstsMs();
+ private static final int QUERIES_PER_BURST = (int) MdnsConfigs.queriesPerBurst();
+ private static final int TIME_BETWEEN_QUERIES_IN_BURST_MS =
+ (int) MdnsConfigs.timeBetweenQueriesInBurstMs();
+ private static final int QUERIES_PER_BURST_PASSIVE_MODE =
+ (int) MdnsConfigs.queriesPerBurstPassive();
+ private static final int UNSIGNED_SHORT_MAX_VALUE = 65536;
+ // The following fields are used by QueryTask so we need to test them.
+ @VisibleForTesting
+ final List<String> subtypes;
+ private final boolean alwaysAskForUnicastResponse =
+ MdnsConfigs.alwaysAskForUnicastResponseInEachBurst();
+ private final boolean usePassiveMode;
+ final boolean onlyUseIpv6OnIpv6OnlyNetworks;
+ private final int numOfQueriesBeforeBackoff;
+ @VisibleForTesting
+ final int transactionId;
+ @VisibleForTesting
+ final boolean expectUnicastResponse;
+ private final int queriesPerBurst;
+ private final int timeBetweenBurstsInMs;
+ private final int burstCounter;
+ final long delayUntilNextTaskWithoutBackoffMs;
+ private final boolean isFirstBurst;
+ private final long queryCount;
+ @NonNull
+ final SocketKey socketKey;
+
+ QueryTaskConfig(@NonNull QueryTaskConfig other, long queryCount, int transactionId,
+ boolean expectUnicastResponse, boolean isFirstBurst, int burstCounter,
+ int queriesPerBurst, int timeBetweenBurstsInMs,
+ long delayUntilNextTaskWithoutBackoffMs) {
+ this.subtypes = new ArrayList<>(other.subtypes);
+ this.usePassiveMode = other.usePassiveMode;
+ this.onlyUseIpv6OnIpv6OnlyNetworks = other.onlyUseIpv6OnIpv6OnlyNetworks;
+ this.numOfQueriesBeforeBackoff = other.numOfQueriesBeforeBackoff;
+ this.transactionId = transactionId;
+ this.expectUnicastResponse = expectUnicastResponse;
+ this.queriesPerBurst = queriesPerBurst;
+ this.timeBetweenBurstsInMs = timeBetweenBurstsInMs;
+ this.burstCounter = burstCounter;
+ this.delayUntilNextTaskWithoutBackoffMs = delayUntilNextTaskWithoutBackoffMs;
+ this.isFirstBurst = isFirstBurst;
+ this.queryCount = queryCount;
+ this.socketKey = other.socketKey;
+ }
+ QueryTaskConfig(@NonNull Collection<String> subtypes,
+ boolean usePassiveMode,
+ boolean onlyUseIpv6OnIpv6OnlyNetworks,
+ int numOfQueriesBeforeBackoff,
+ @Nullable SocketKey socketKey) {
+ this.usePassiveMode = usePassiveMode;
+ this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
+ this.numOfQueriesBeforeBackoff = numOfQueriesBeforeBackoff;
+ this.subtypes = new ArrayList<>(subtypes);
+ this.queriesPerBurst = QUERIES_PER_BURST;
+ this.burstCounter = 0;
+ this.transactionId = 1;
+ this.expectUnicastResponse = true;
+ this.isFirstBurst = true;
+ // Config the scan frequency based on the scan mode.
+ if (this.usePassiveMode) {
+ // In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and then
+ // in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
+ // queries.
+ this.timeBetweenBurstsInMs = TIME_BETWEEN_BURSTS_MS;
+ } else {
+ // In active scan mode, sends a burst of QUERIES_PER_BURST queries,
+ // TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
+ // then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
+ // doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
+ this.timeBetweenBurstsInMs = INITIAL_TIME_BETWEEN_BURSTS_MS;
+ }
+ this.socketKey = socketKey;
+ this.queryCount = 0;
+ this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
+ }
+
+ /**
+ * Get new QueryTaskConfig for next run.
+ */
+ public QueryTaskConfig getConfigForNextRun() {
+ long newQueryCount = queryCount + 1;
+ int newTransactionId = transactionId + 1;
+ if (newTransactionId > UNSIGNED_SHORT_MAX_VALUE) {
+ newTransactionId = 1;
+ }
+ boolean newExpectUnicastResponse = false;
+ boolean newIsFirstBurst = isFirstBurst;
+ int newQueriesPerBurst = queriesPerBurst;
+ int newBurstCounter = burstCounter + 1;
+ long newDelayUntilNextTaskWithoutBackoffMs = delayUntilNextTaskWithoutBackoffMs;
+ int newTimeBetweenBurstsInMs = timeBetweenBurstsInMs;
+ // Only the first query expects uni-cast response.
+ if (newBurstCounter == queriesPerBurst) {
+ newBurstCounter = 0;
+
+ if (alwaysAskForUnicastResponse) {
+ newExpectUnicastResponse = true;
+ }
+ // In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and
+ // then in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
+ // queries.
+ if (isFirstBurst) {
+ newIsFirstBurst = false;
+ if (usePassiveMode) {
+ newQueriesPerBurst = QUERIES_PER_BURST_PASSIVE_MODE;
+ }
+ }
+ // In active scan mode, sends a burst of QUERIES_PER_BURST queries,
+ // TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
+ // then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
+ // doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
+ newDelayUntilNextTaskWithoutBackoffMs = timeBetweenBurstsInMs;
+ if (timeBetweenBurstsInMs < TIME_BETWEEN_BURSTS_MS) {
+ newTimeBetweenBurstsInMs = Math.min(timeBetweenBurstsInMs * 2,
+ TIME_BETWEEN_BURSTS_MS);
+ }
+ } else {
+ newDelayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
+ }
+ return new QueryTaskConfig(this, newQueryCount, newTransactionId,
+ newExpectUnicastResponse, newIsFirstBurst, newBurstCounter, newQueriesPerBurst,
+ newTimeBetweenBurstsInMs, newDelayUntilNextTaskWithoutBackoffMs);
+ }
+
+ /**
+ * Determine if the query backoff should be used.
+ */
+ public boolean shouldUseQueryBackoff() {
+ // Don't enable backoff mode during the burst or in the first burst
+ if (burstCounter != 0 || isFirstBurst) {
+ return false;
+ }
+ return queryCount > numOfQueriesBeforeBackoff;
+ }
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java b/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java
index c21c903..6f16436 100644
--- a/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java
+++ b/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java
@@ -20,7 +20,6 @@
import android.net.LinkAddress;
import android.os.Handler;
import android.system.OsConstants;
-import android.util.Log;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.ip.NetlinkMonitor;
@@ -37,6 +36,8 @@
public class SocketNetlinkMonitor extends NetlinkMonitor implements AbstractSocketNetlinkMonitor {
public static final String TAG = SocketNetlinkMonitor.class.getSimpleName();
+ @NonNull
+ private final SharedLog mSharedLog;
@NonNull
private final MdnsSocketProvider.NetLinkMonitorCallBack mCb;
@@ -46,6 +47,7 @@
super(handler, log, TAG, OsConstants.NETLINK_ROUTE,
NetlinkConstants.RTMGRP_IPV4_IFADDR | NetlinkConstants.RTMGRP_IPV6_IFADDR);
mCb = cb;
+ mSharedLog = log;
}
@Override
public void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) {
@@ -71,7 +73,7 @@
mCb.deleteInterfaceAddress(ifaddrMsg.index, la);
break;
default:
- Log.e(TAG, "Unknown rtnetlink address msg type " + msg.getHeader().nlmsg_type);
+ mSharedLog.e("Unknown rtnetlink address msg type " + msg.getHeader().nlmsg_type);
}
}
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 3180a6f..df3bde8 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.os.SystemClock;
import android.util.ArraySet;
import com.android.server.connectivity.mdns.MdnsConstants;
@@ -173,4 +174,14 @@
return mdnsRecord.getTtl() > 0
&& mdnsRecord.getRemainingTTL(now) <= mdnsRecord.getTtl() / 2;
}
+
+ /** A wrapper class of {@link SystemClock} to be mocked in unit tests. */
+ public static class Clock {
+ /**
+ * @see SystemClock#elapsedRealtime
+ */
+ public long elapsedRealtime() {
+ return SystemClock.elapsedRealtime();
+ }
+ }
}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 1f22b02..8141350 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -431,7 +431,7 @@
for (String iface : getClientModeInterfaces(canUseRestrictedNetworks)) {
unicastInterfaceStateChange(listener, iface);
}
- if (mTetheringInterfaceMode == INTERFACE_MODE_SERVER) {
+ if (mTetheringInterface != null && mTetheringInterfaceMode == INTERFACE_MODE_SERVER) {
unicastInterfaceStateChange(listener, mTetheringInterface);
}
diff --git a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
index ceae9ba..27c0f9f 100644
--- a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
+++ b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
@@ -41,7 +41,7 @@
// This is current path but may be changed soon.
private static final String IFACE_INDEX_NAME_MAP_PATH =
"/sys/fs/bpf/netd_shared/map_netd_iface_index_name_map";
- private final IBpfMap<S32, InterfaceMapValue> mBpfMap;
+ private final IBpfMap<S32, InterfaceMapValue> mIndexToIfaceBpfMap;
private final INetd mNetd;
private final Handler mHandler;
private final Dependencies mDeps;
@@ -53,7 +53,7 @@
@VisibleForTesting
public BpfInterfaceMapUpdater(Context ctx, Handler handler, Dependencies deps) {
mDeps = deps;
- mBpfMap = deps.getInterfaceMap();
+ mIndexToIfaceBpfMap = deps.getInterfaceMap();
mNetd = deps.getINetd(ctx);
mHandler = handler;
}
@@ -91,7 +91,7 @@
*/
public void start() {
mHandler.post(() -> {
- if (mBpfMap == null) {
+ if (mIndexToIfaceBpfMap == null) {
Log.wtf(TAG, "Fail to start: Null bpf map");
return;
}
@@ -126,7 +126,7 @@
}
try {
- mBpfMap.updateEntry(new S32(iface.index), new InterfaceMapValue(ifaceName));
+ mIndexToIfaceBpfMap.updateEntry(new S32(iface.index), new InterfaceMapValue(ifaceName));
} catch (ErrnoException e) {
Log.e(TAG, "Unable to update entry for " + ifaceName + ", " + e);
}
@@ -142,7 +142,7 @@
/** get interface name by interface index from bpf map */
public String getIfNameByIndex(final int index) {
try {
- final InterfaceMapValue value = mBpfMap.getValue(new S32(index));
+ final InterfaceMapValue value = mIndexToIfaceBpfMap.getValue(new S32(index));
if (value == null) {
Log.e(TAG, "No if name entry for index " + index);
return null;
@@ -162,11 +162,12 @@
public void dump(final IndentingPrintWriter pw) {
pw.println("BPF map status:");
pw.increaseIndent();
- BpfDump.dumpMapStatus(mBpfMap, pw, "IfaceIndexNameMap", IFACE_INDEX_NAME_MAP_PATH);
+ BpfDump.dumpMapStatus(mIndexToIfaceBpfMap, pw, "IfaceIndexNameMap",
+ IFACE_INDEX_NAME_MAP_PATH);
pw.decreaseIndent();
pw.println("BPF map content:");
pw.increaseIndent();
- BpfDump.dumpMap(mBpfMap, pw, "IfaceIndexNameMap",
+ BpfDump.dumpMap(mIndexToIfaceBpfMap, pw, "IfaceIndexNameMap",
(key, value) -> "ifaceIndex=" + key.val
+ " ifaceName=" + value.getInterfaceNameString());
pw.decreaseIndent();
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index e7ef510..c46eada 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -63,6 +63,7 @@
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import static com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport;
import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
@@ -1744,8 +1745,7 @@
// information. This is because no caller needs this information for now, and it
// makes it easier to change the implementation later by using the histories in the
// recorder.
- stats.clearInterfaces();
- return stats;
+ return stats.clearInterfaces();
} catch (RemoteException e) {
Log.wtf(TAG, "Error compiling UID stats", e);
return new NetworkStats(0L, 0);
@@ -3249,7 +3249,8 @@
* Default external settings that read from
* {@link android.provider.Settings.Global}.
*/
- private static class DefaultNetworkStatsSettings implements NetworkStatsSettings {
+ @VisibleForTesting(visibility = PRIVATE)
+ static class DefaultNetworkStatsSettings implements NetworkStatsSettings {
DefaultNetworkStatsSettings() {}
@Override
@@ -3304,6 +3305,7 @@
private static native long nativeGetTotalStat(int type);
private static native long nativeGetIfaceStat(String iface, int type);
+ private static native long nativeGetIfIndexStat(int ifindex, int type);
private static native long nativeGetUidStat(int uid, int type);
/** Initializes and registers the Perfetto Network Trace data source */
diff --git a/service/Android.bp b/service/Android.bp
index e1376a1..a65d664 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -236,6 +236,7 @@
"service-connectivity-pre-jarjar",
"service-connectivity-tiramisu-pre-jarjar",
"service-nearby-pre-jarjar",
+ "service-remoteauth-pre-jarjar",
],
// The below libraries are not actually needed to build since no source is compiled
// (only combining prebuilt static_libs), but they are necessary so that R8 has the right
@@ -303,6 +304,7 @@
":framework-connectivity-jarjar-rules",
":service-connectivity-jarjar-gen",
":service-nearby-jarjar-gen",
+ ":service-remoteauth-jarjar-gen",
],
out: ["connectivity-jarjar-rules.txt"],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
@@ -354,6 +356,24 @@
visibility: ["//visibility:private"],
}
+java_genrule {
+ name: "service-remoteauth-jarjar-gen",
+ tool_files: [
+ ":service-remoteauth-pre-jarjar{.jar}",
+ "jarjar-excludes.txt",
+ ],
+ tools: [
+ "jarjar-rules-generator",
+ ],
+ out: ["service_remoteauth_jarjar_rules.txt"],
+ cmd: "$(location jarjar-rules-generator) " +
+ "$(location :service-remoteauth-pre-jarjar{.jar}) " +
+ "--prefix com.android.server.remoteauth " +
+ "--excludes $(location jarjar-excludes.txt) " +
+ "--output $(out)",
+ visibility: ["//visibility:private"],
+}
+
genrule {
name: "statslog-connectivity-java-gen",
tools: ["stats-log-api-gen"],
diff --git a/service/src/com/android/metrics/stats.proto b/service/src/com/android/metrics/stats.proto
index 006d20a..99afb90 100644
--- a/service/src/com/android/metrics/stats.proto
+++ b/service/src/com/android/metrics/stats.proto
@@ -61,6 +61,9 @@
// Record query service count before unregistered service
optional int32 replied_requests_count = 11;
+
+ // Record sent query count before stopped discovery
+ optional int32 sent_query_count = 12;
}
/**
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index ec168dd..7aff6a4 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -40,6 +40,7 @@
import android.app.StatsManager;
import android.content.Context;
import android.net.INetd;
+import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.provider.DeviceConfig;
@@ -51,6 +52,8 @@
import android.util.Pair;
import android.util.StatsEvent;
+import androidx.annotation.RequiresApi;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.BackgroundThread;
import com.android.modules.utils.build.SdkLevel;
@@ -1140,19 +1143,48 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
private static native void native_init(boolean startSkDestroyListener);
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private native int native_addNaughtyApp(int uid);
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private native int native_removeNaughtyApp(int uid);
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private native int native_addNiceApp(int uid);
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private native int native_removeNiceApp(int uid);
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private native int native_setChildChain(int childChain, boolean enable);
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private native int native_replaceUidChain(String name, boolean isAllowlist, int[] uids);
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private native int native_setUidRule(int childChain, int uid, int firewallRule);
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private native int native_addUidInterfaceRules(String ifName, int[] uids);
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private native int native_removeUidInterfaceRules(int[] uids);
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private native int native_updateUidLockdownRule(int uid, boolean add);
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private native int native_swapActiveStatsMap();
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private native void native_setPermissionForUids(int permissions, int[] uids);
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static native void native_dump(FileDescriptor fd, boolean verbose);
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static native int native_synchronizeKernelRCU();
}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 83afa83..a29f47f 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -1704,8 +1704,7 @@
mUserAllContext.registerReceiver(mPackageIntentReceiver, packageIntentFilter,
null /* broadcastPermission */, mHandler);
- mNetworkActivityTracker =
- new LegacyNetworkActivityTracker(mContext, mNetd, mHandler, mDeps.isAtLeastU());
+ mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mNetd, mHandler);
final NetdCallback netdCallback = new NetdCallback();
try {
@@ -2986,19 +2985,17 @@
}
private void handleFrozenUids(int[] uids, int[] frozenStates) {
- final ArraySet<Range<Integer>> ranges = new ArraySet<>();
+ final ArraySet<Integer> ownerUids = new ArraySet<>();
for (int i = 0; i < uids.length; i++) {
if (frozenStates[i] == UID_FROZEN_STATE_FROZEN) {
- Integer uidAsInteger = Integer.valueOf(uids[i]);
- ranges.add(new Range(uidAsInteger, uidAsInteger));
+ ownerUids.add(uids[i]);
}
}
- if (!ranges.isEmpty()) {
- final Set<Integer> exemptUids = new ArraySet<>();
+ if (!ownerUids.isEmpty()) {
try {
- mDeps.destroyLiveTcpSockets(ranges, exemptUids);
+ mDeps.destroyLiveTcpSocketsByOwnerUids(ownerUids);
} catch (Exception e) {
loge("Exception in socket destroy: " + e);
}
@@ -11139,9 +11136,10 @@
new RemoteCallbackList<>();
// Indicate the current system default network activity is active or not.
// This needs to be volatile to allow non handler threads to read this value without lock.
- private volatile boolean mIsDefaultNetworkActive;
+ // If there is no default network, default network is considered active to keep the existing
+ // behavior. Initial value is used until first connect to the default network.
+ private volatile boolean mIsDefaultNetworkActive = true;
private final ArrayMap<String, IdleTimerParams> mActiveIdleTimers = new ArrayMap<>();
- private final boolean mIsAtLeastU;
private static class IdleTimerParams {
public final int timeout;
@@ -11154,11 +11152,10 @@
}
LegacyNetworkActivityTracker(@NonNull Context context, @NonNull INetd netd,
- @NonNull Handler handler, boolean isAtLeastU) {
+ @NonNull Handler handler) {
mContext = context;
mNetd = netd;
mHandler = handler;
- mIsAtLeastU = isAtLeastU;
}
private void ensureRunningOnConnectivityServiceThread() {
@@ -11319,13 +11316,14 @@
boolean hasIdleTimer) {
if (defaultNetwork != null) {
mIsDefaultNetworkActive = true;
- // On T-, callbacks are called only when the network has the idle timer.
- if (mIsAtLeastU || hasIdleTimer) {
+ // Callbacks are called only when the network has the idle timer.
+ if (hasIdleTimer) {
reportNetworkActive();
}
} else {
- // If there is no default network, default network is considered inactive.
- mIsDefaultNetworkActive = false;
+ // If there is no default network, default network is considered active to keep the
+ // existing behavior.
+ mIsDefaultNetworkActive = true;
}
}
diff --git a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
index d03cac6..4f2909c 100644
--- a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
@@ -227,23 +227,28 @@
throw new IllegalArgumentException("fd can't be null with automatic "
+ "on/off keepalives");
}
- try {
- mFd = Os.dup(ki.mFd);
- } catch (ErrnoException e) {
- Log.e(TAG, "Cannot dup fd: ", e);
- throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
- }
mAlarmListener = () -> mConnectivityServiceHandler.obtainMessage(
CMD_MONITOR_AUTOMATIC_KEEPALIVE, mCallback.asBinder())
.sendToTarget();
} else {
mAutomaticOnOffState = STATE_ALWAYS_ON;
- // A null fd is acceptable in KeepaliveInfo for backward compatibility of
- // PacketKeepalive API, but it must never happen with automatic keepalives.
- // TODO : remove mFd from KeepaliveInfo or from this class.
- mFd = ki.mFd;
mAlarmListener = null;
}
+
+ // A null fd is acceptable in KeepaliveInfo for backward compatibility of
+ // PacketKeepalive API, but it must never happen with automatic keepalives.
+ // TODO : remove mFd from KeepaliveInfo.
+ mFd = dupFd(ki.mFd);
+ }
+
+ private FileDescriptor dupFd(FileDescriptor fd) throws InvalidSocketException {
+ try {
+ if (fd == null) return null;
+ return Os.dup(fd);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot dup fd: ", e);
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
+ }
}
@VisibleForTesting
@@ -332,12 +337,18 @@
final long time = mDependencies.getElapsedRealtime();
mMetricsWriteTimeBase = time % METRICS_COLLECTION_DURATION_MS;
- final long triggerAtMillis = mMetricsWriteTimeBase + METRICS_COLLECTION_DURATION_MS;
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtMillis, TAG,
- this::writeMetricsAndRescheduleAlarm, handler);
+ if (mKeepaliveStatsTracker.isEnabled()) {
+ final long triggerAtMillis = mMetricsWriteTimeBase + METRICS_COLLECTION_DURATION_MS;
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtMillis, TAG,
+ this::writeMetricsAndRescheduleAlarm, handler);
+ }
}
private void writeMetricsAndRescheduleAlarm() {
+ // If the metrics is disabled, skip writing and scheduling the next alarm.
+ if (!mKeepaliveStatsTracker.isEnabled()) {
+ return;
+ }
mKeepaliveStatsTracker.writeAndResetMetrics();
final long time = mDependencies.getElapsedRealtime();
@@ -495,11 +506,7 @@
final AutomaticOnOffKeepalive autoKi;
try {
autoKi = target.withKeepaliveInfo(res.second);
- // Only automatic keepalives duplicate the fd.
- if (target.mAutomaticOnOffState != STATE_ALWAYS_ON) {
- // Close the duplicated fd.
- target.close();
- }
+ target.close();
} catch (InvalidSocketException e) {
Log.wtf(TAG, "Fail to create AutomaticOnOffKeepalive", e);
return;
diff --git a/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
index 414aca3..0c2ed18 100644
--- a/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
@@ -56,6 +56,8 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Tracks carrier and duration metrics of automatic on/off keepalives.
@@ -63,9 +65,14 @@
* <p>This class follows AutomaticOnOffKeepaliveTracker closely and its on*Keepalive methods needs
* to be called in a timely manner to keep the metrics accurate. It is also not thread-safe and all
* public methods must be called by the same thread, namely the ConnectivityService handler thread.
+ *
+ * <p>In the case that the keepalive state becomes out of sync with the hardware, the tracker will
+ * be disabled. e.g. Calling onStartKeepalive on a given network, slot pair twice without calling
+ * onStopKeepalive is unexpected and will disable the tracker.
*/
public class KeepaliveStatsTracker {
private static final String TAG = KeepaliveStatsTracker.class.getSimpleName();
+ private static final int INVALID_KEEPALIVE_ID = -1;
@NonNull private final Handler mConnectivityServiceHandler;
@NonNull private final Dependencies mDependencies;
@@ -76,6 +83,11 @@
// Updates are received from the ACTION_DEFAULT_SUBSCRIPTION_CHANGED broadcast.
private int mCachedDefaultSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ // Boolean to track whether the KeepaliveStatsTracker is enabled.
+ // Use a final AtomicBoolean to ensure initialization is seen on the handler thread.
+ // Repeated fields in metrics are only supported on T+ so this is enabled only on T+.
+ private final AtomicBoolean mEnabled = new AtomicBoolean(SdkLevel.isAtLeastT());
+
// Class to store network information, lifetime durations and active state of a keepalive.
private static final class KeepaliveStats {
// The carrier ID for a keepalive, or TelephonyManager.UNKNOWN_CARRIER_ID(-1) if not set.
@@ -185,17 +197,21 @@
// Map of keepalives identified by the id from getKeepaliveId to their stats information.
private final SparseArray<KeepaliveStats> mKeepaliveStatsPerId = new SparseArray<>();
- // Generate a unique integer using a given network's netId and the slot number.
+ // Generate and return a unique integer using a given network's netId and the slot number.
// This is possible because netId is a 16 bit integer, so an integer with the first 16 bits as
// the netId and the last 16 bits as the slot number can be created. This allows slot numbers to
// be up to 2^16.
+ // Returns INVALID_KEEPALIVE_ID if the netId or slot is not as expected above.
private int getKeepaliveId(@NonNull Network network, int slot) {
final int netId = network.getNetId();
+ // Since there is no enforcement that a Network's netId is valid check for it here.
if (netId < 0 || netId >= (1 << 16)) {
- throw new IllegalArgumentException("Unexpected netId value: " + netId);
+ disableTracker("Unexpected netId value: " + netId);
+ return INVALID_KEEPALIVE_ID;
}
if (slot < 0 || slot >= (1 << 16)) {
- throw new IllegalArgumentException("Unexpected slot value: " + slot);
+ disableTracker("Unexpected slot value: " + slot);
+ return INVALID_KEEPALIVE_ID;
}
return (netId << 16) + slot;
@@ -274,29 +290,42 @@
this(context, handler, new Dependencies());
}
+ private final Context mContext;
+ private final SubscriptionManager mSubscriptionManager;
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mCachedDefaultSubscriptionId =
+ intent.getIntExtra(
+ SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+ };
+
+ private final CompletableFuture<OnSubscriptionsChangedListener> mListenerFuture =
+ new CompletableFuture<>();
+
@VisibleForTesting
public KeepaliveStatsTracker(
@NonNull Context context,
@NonNull Handler handler,
@NonNull Dependencies dependencies) {
- Objects.requireNonNull(context);
+ mContext = Objects.requireNonNull(context);
mDependencies = Objects.requireNonNull(dependencies);
mConnectivityServiceHandler = Objects.requireNonNull(handler);
- final SubscriptionManager subscriptionManager =
+ mSubscriptionManager =
Objects.requireNonNull(context.getSystemService(SubscriptionManager.class));
mLastUpdateDurationsTimestamp = mDependencies.getElapsedRealtime();
+
+ if (!isEnabled()) {
+ return;
+ }
+
context.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mCachedDefaultSubscriptionId =
- intent.getIntExtra(
- SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
- SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- }
- },
+ mBroadcastReceiver,
new IntentFilter(SubscriptionManager.ACTION_DEFAULT_SUBSCRIPTION_CHANGED),
/* broadcastPermission= */ null,
mConnectivityServiceHandler);
@@ -306,38 +335,41 @@
// this will throw. Therefore, post a runnable that creates it there.
// When the callback is called on the BackgroundThread, post a message on the CS handler
// thread to update the caches, which can only be touched there.
- BackgroundThread.getHandler().post(() ->
- subscriptionManager.addOnSubscriptionsChangedListener(
- r -> r.run(), new OnSubscriptionsChangedListener() {
- @Override
- public void onSubscriptionsChanged() {
- final List<SubscriptionInfo> activeSubInfoList =
- subscriptionManager.getActiveSubscriptionInfoList();
- // A null subInfo list here indicates the current state is unknown
- // but not necessarily empty, simply ignore it. Another call to the
- // listener will be invoked in the future.
- if (activeSubInfoList == null) return;
- mConnectivityServiceHandler.post(() -> {
- mCachedCarrierIdPerSubId.clear();
+ BackgroundThread.getHandler().post(() -> {
+ final OnSubscriptionsChangedListener listener =
+ new OnSubscriptionsChangedListener() {
+ @Override
+ public void onSubscriptionsChanged() {
+ final List<SubscriptionInfo> activeSubInfoList =
+ mSubscriptionManager.getActiveSubscriptionInfoList();
+ // A null subInfo list here indicates the current state is unknown
+ // but not necessarily empty, simply ignore it. Another call to the
+ // listener will be invoked in the future.
+ if (activeSubInfoList == null) return;
+ mConnectivityServiceHandler.post(() -> {
+ mCachedCarrierIdPerSubId.clear();
- for (final SubscriptionInfo subInfo : activeSubInfoList) {
- mCachedCarrierIdPerSubId.put(subInfo.getSubscriptionId(),
- subInfo.getCarrierId());
- }
- });
- }
- }));
+ for (final SubscriptionInfo subInfo : activeSubInfoList) {
+ mCachedCarrierIdPerSubId.put(subInfo.getSubscriptionId(),
+ subInfo.getCarrierId());
+ }
+ });
+ }
+ };
+ mListenerFuture.complete(listener);
+ mSubscriptionManager.addOnSubscriptionsChangedListener(r -> r.run(), listener);
+ });
}
/** Ensures the list of duration metrics is large enough for number of registered keepalives. */
private void ensureDurationPerNumOfKeepaliveSize() {
if (mNumActiveKeepalive < 0 || mNumRegisteredKeepalive < 0) {
- throw new IllegalStateException(
- "Number of active or registered keepalives is negative");
+ disableTracker("Number of active or registered keepalives is negative");
+ return;
}
if (mNumActiveKeepalive > mNumRegisteredKeepalive) {
- throw new IllegalStateException(
- "Number of active keepalives greater than registered keepalives");
+ disableTracker("Number of active keepalives greater than registered keepalives");
+ return;
}
while (mDurationPerNumOfKeepalive.size() <= mNumRegisteredKeepalive) {
@@ -426,10 +458,12 @@
int appUid,
boolean isAutoKeepalive) {
ensureRunningOnHandlerThread();
+ if (!isEnabled()) return;
final int keepaliveId = getKeepaliveId(network, slot);
+ if (keepaliveId == INVALID_KEEPALIVE_ID) return;
if (mKeepaliveStatsPerId.contains(keepaliveId)) {
- throw new IllegalArgumentException(
- "Attempt to start keepalive stats on a known network, slot pair");
+ disableTracker("Attempt to start keepalive stats on a known network, slot pair");
+ return;
}
mNumKeepaliveRequests++;
@@ -457,13 +491,11 @@
/**
* Inform the KeepaliveStatsTracker that the keepalive with the given network, slot pair has
* updated its active state to keepaliveActive.
- *
- * @return the KeepaliveStats associated with the network, slot pair or null if it is unknown.
*/
- private @NonNull KeepaliveStats onKeepaliveActive(
+ private void onKeepaliveActive(
@NonNull Network network, int slot, boolean keepaliveActive) {
final long timeNow = mDependencies.getElapsedRealtime();
- return onKeepaliveActive(network, slot, keepaliveActive, timeNow);
+ onKeepaliveActive(network, slot, keepaliveActive, timeNow);
}
/**
@@ -474,45 +506,53 @@
* @param slot the slot number of the keepalive
* @param keepaliveActive the new active state of the keepalive
* @param timeNow a timestamp obtained using Dependencies.getElapsedRealtime
- * @return the KeepaliveStats associated with the network, slot pair or null if it is unknown.
*/
- private @NonNull KeepaliveStats onKeepaliveActive(
+ private void onKeepaliveActive(
@NonNull Network network, int slot, boolean keepaliveActive, long timeNow) {
- ensureRunningOnHandlerThread();
-
final int keepaliveId = getKeepaliveId(network, slot);
- if (!mKeepaliveStatsPerId.contains(keepaliveId)) {
- throw new IllegalArgumentException(
- "Attempt to set active keepalive on an unknown network, slot pair");
+ if (keepaliveId == INVALID_KEEPALIVE_ID) return;
+
+ final KeepaliveStats keepaliveStats = mKeepaliveStatsPerId.get(keepaliveId, null);
+
+ if (keepaliveStats == null) {
+ disableTracker("Attempt to set active keepalive on an unknown network, slot pair");
+ return;
}
updateDurationsPerNumOfKeepalive(timeNow);
- final KeepaliveStats keepaliveStats = mKeepaliveStatsPerId.get(keepaliveId);
if (keepaliveActive != keepaliveStats.isKeepaliveActive()) {
mNumActiveKeepalive += keepaliveActive ? 1 : -1;
}
keepaliveStats.updateLifetimeStatsAndSetActive(timeNow, keepaliveActive);
- return keepaliveStats;
}
/** Inform the KeepaliveStatsTracker a keepalive has just been paused. */
public void onPauseKeepalive(@NonNull Network network, int slot) {
+ ensureRunningOnHandlerThread();
+ if (!isEnabled()) return;
onKeepaliveActive(network, slot, /* keepaliveActive= */ false);
}
/** Inform the KeepaliveStatsTracker a keepalive has just been resumed. */
public void onResumeKeepalive(@NonNull Network network, int slot) {
+ ensureRunningOnHandlerThread();
+ if (!isEnabled()) return;
onKeepaliveActive(network, slot, /* keepaliveActive= */ true);
}
/** Inform the KeepaliveStatsTracker a keepalive has just been stopped. */
public void onStopKeepalive(@NonNull Network network, int slot) {
+ ensureRunningOnHandlerThread();
+ if (!isEnabled()) return;
+
final int keepaliveId = getKeepaliveId(network, slot);
+ if (keepaliveId == INVALID_KEEPALIVE_ID) return;
final long timeNow = mDependencies.getElapsedRealtime();
- final KeepaliveStats keepaliveStats =
- onKeepaliveActive(network, slot, /* keepaliveActive= */ false, timeNow);
+ onKeepaliveActive(network, slot, /* keepaliveActive= */ false, timeNow);
+ final KeepaliveStats keepaliveStats = mKeepaliveStatsPerId.get(keepaliveId, null);
+ if (keepaliveStats == null) return;
mNumRegisteredKeepalive--;
@@ -651,7 +691,25 @@
return metrics;
}
- /** Writes the stored metrics to ConnectivityStatsLog and resets. */
+ private void disableTracker(String msg) {
+ if (!mEnabled.compareAndSet(/* expectedValue= */ true, /* newValue= */ false)) {
+ // already disabled
+ return;
+ }
+ Log.wtf(TAG, msg + ". Disabling KeepaliveStatsTracker");
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ // The returned future is ignored since it is void and the is never completed exceptionally.
+ final CompletableFuture<Void> unused = mListenerFuture.thenAcceptAsync(
+ listener -> mSubscriptionManager.removeOnSubscriptionsChangedListener(listener),
+ BackgroundThread.getExecutor());
+ }
+
+ /** Whether this tracker is enabled. This method is thread safe. */
+ public boolean isEnabled() {
+ return mEnabled.get();
+ }
+
+ /** Writes the stored metrics to ConnectivityStatsLog and resets. */
public void writeAndResetMetrics() {
ensureRunningOnHandlerThread();
// Keepalive stats use repeated atoms, which are only supported on T+. If written to statsd
@@ -660,6 +718,10 @@
Log.d(TAG, "KeepaliveStatsTracker is disabled before T, skipping write");
return;
}
+ if (!isEnabled()) {
+ Log.d(TAG, "KeepaliveStatsTracker is disabled, skipping write");
+ return;
+ }
final DailykeepaliveInfoReported dailyKeepaliveInfoReported = buildAndResetMetrics();
mDependencies.writeStats(dailyKeepaliveInfoReported);
diff --git a/service/src/com/android/server/connectivity/KeepaliveTracker.java b/service/src/com/android/server/connectivity/KeepaliveTracker.java
index 2ce186f..ae70767 100644
--- a/service/src/com/android/server/connectivity/KeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveTracker.java
@@ -34,6 +34,8 @@
import static android.net.SocketKeepalive.SUCCESS;
import static android.net.SocketKeepalive.SUCCESS_PAUSED;
+import static com.android.net.module.util.FeatureVersions.FEATURE_CLAT_ADDRESS_TRANSLATE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -59,6 +61,7 @@
import com.android.connectivity.resources.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.HexDump;
import com.android.net.module.util.IpUtils;
@@ -86,6 +89,9 @@
public static final String PERMISSION = android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD;
+ private static final String CONFIG_DISABLE_CLAT_ADDRESS_TRANSLATE =
+ "disable_clat_address_translate";
+
/** Keeps track of keepalive requests. */
private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives =
new HashMap<> ();
@@ -113,7 +119,7 @@
}
@VisibleForTesting
- KeepaliveTracker(Context context, Handler handler, TcpKeepaliveController tcpController,
+ public KeepaliveTracker(Context context, Handler handler, TcpKeepaliveController tcpController,
Dependencies deps) {
mTcpController = tcpController;
mContext = context;
@@ -553,6 +559,8 @@
private KeepaliveInfo handleUpdateKeepaliveForClat(KeepaliveInfo ki)
throws InvalidSocketException, InvalidPacketException {
+ if (!mDependencies.isAddressTranslationEnabled(mContext)) return ki;
+
// Translation applies to only NAT-T keepalive
if (ki.mType != KeepaliveInfo.TYPE_NATT) return ki;
// Only try to translate address if the packet source address is the clat's source address.
@@ -973,5 +981,20 @@
public ConnectivityResources createConnectivityResources(@NonNull Context context) {
return new ConnectivityResources(context);
}
+
+ /**
+ * Return if keepalive address translation with clat feature is supported or not.
+ *
+ * This is controlled by both isFeatureSupported() and isFeatureEnabled(). The
+ * isFeatureSupported() checks whether device contains the minimal required module
+ * version for FEATURE_CLAT_ADDRESS_TRANSLATE. The isTetheringFeatureForceDisabled()
+ * checks the DeviceConfig flag that can be updated via DeviceConfig push to control
+ * the overall feature.
+ */
+ public boolean isAddressTranslationEnabled(@NonNull Context context) {
+ return DeviceConfigUtils.isFeatureSupported(context, FEATURE_CLAT_ADDRESS_TRANSLATE)
+ && !DeviceConfigUtils.isTetheringFeatureForceDisabled(
+ CONFIG_DISABLE_CLAT_ADDRESS_TRANSLATE);
+ }
}
}
diff --git a/service/src/com/android/server/connectivity/NetworkDiagnostics.java b/service/src/com/android/server/connectivity/NetworkDiagnostics.java
index a367d9d..e1e2585 100644
--- a/service/src/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/service/src/com/android/server/connectivity/NetworkDiagnostics.java
@@ -24,9 +24,12 @@
import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_MTU;
+import static com.android.net.module.util.NetworkStackConstants.IP_MTU;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TargetApi;
import android.net.InetAddresses;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -35,6 +38,7 @@
import android.net.TrafficStats;
import android.net.shared.PrivateDnsConfig;
import android.net.util.NetworkConstants;
+import android.os.Build;
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
@@ -213,14 +217,10 @@
mLinkProperties.addDnsServer(TEST_DNS6);
}
- final int lpMtu = mLinkProperties.getMtu();
- final int mtu = lpMtu > 0 ? lpMtu : ETHER_MTU;
for (RouteInfo route : mLinkProperties.getRoutes()) {
if (route.getType() == RouteInfo.RTN_UNICAST && route.hasGateway()) {
- InetAddress gateway = route.getGateway();
- // Use mtu in the route if exists. Otherwise, use the one in the link property.
- final int routeMtu = route.getMtu();
- prepareIcmpMeasurements(gateway, (routeMtu > 0) ? routeMtu : mtu);
+ final InetAddress gateway = route.getGateway();
+ prepareIcmpMeasurements(gateway);
if (route.isIPv6Default()) {
prepareExplicitSourceIcmpMeasurements(gateway);
}
@@ -228,7 +228,7 @@
}
for (InetAddress nameserver : mLinkProperties.getDnsServers()) {
- prepareIcmpMeasurements(nameserver, mtu);
+ prepareIcmpMeasurements(nameserver);
prepareDnsMeasurement(nameserver);
// Unlike the DnsResolver which doesn't do certificate validation in opportunistic mode,
@@ -285,24 +285,29 @@
// calculation.
if (addr instanceof Inet6Address) {
return IPV6_HEADER_LEN + ICMP_HEADER_LEN;
+ } else {
+ return IPV4_HEADER_MIN_LEN + ICMP_HEADER_LEN;
}
} catch (UnknownHostException e) {
- Log.e(TAG, "Create InetAddress fail(" + target + "): " + e);
+ throw new AssertionError("Create InetAddress fail(" + target + ")", e);
}
-
- return IPV4_HEADER_MIN_LEN + ICMP_HEADER_LEN;
}
- private void prepareIcmpMeasurements(@NonNull InetAddress target, int targetNetworkMtu) {
+ private void prepareIcmpMeasurements(@NonNull InetAddress target) {
+ int mtu = getMtuForTarget(target);
+ // If getMtuForTarget fails, it doesn't matter what mtu is used because connect can't
+ // succeed anyway
+ if (mtu <= 0) mtu = mLinkProperties.getMtu();
+ if (mtu <= 0) mtu = ETHER_MTU;
// Test with different size payload ICMP.
// 1. Test with 0 payload.
addPayloadIcmpMeasurement(target, 0);
final int header = getHeaderLen(target);
// 2. Test with full size MTU.
- addPayloadIcmpMeasurement(target, targetNetworkMtu - header);
+ addPayloadIcmpMeasurement(target, mtu - header);
// 3. If v6, make another measurement with the full v6 min MTU, unless that's what
// was done above.
- if ((target instanceof Inet6Address) && (targetNetworkMtu != IPV6_MIN_MTU)) {
+ if ((target instanceof Inet6Address) && (mtu != IPV6_MIN_MTU)) {
addPayloadIcmpMeasurement(target, IPV6_MIN_MTU - header);
}
}
@@ -321,6 +326,35 @@
}
}
+ /**
+ * Open a socket to the target address and return the mtu from that socket
+ *
+ * If the MTU can't be obtained for some reason (e.g. the target is unreachable) this will
+ * return -1.
+ *
+ * @param target the destination address
+ * @return the mtu to that destination, or -1
+ */
+ // getsockoptInt is S+, but this service code and only installs on S, so it's safe to ignore
+ // the lint warnings by using @TargetApi.
+ @TargetApi(Build.VERSION_CODES.S)
+ private int getMtuForTarget(InetAddress target) {
+ final int family = target instanceof Inet4Address ? AF_INET : AF_INET6;
+ try {
+ final FileDescriptor socket = Os.socket(family, SOCK_DGRAM, 0);
+ mNetwork.bindSocket(socket);
+ Os.connect(socket, target, 0);
+ if (family == AF_INET) {
+ return Os.getsockoptInt(socket, IPPROTO_IP, IP_MTU);
+ } else {
+ return Os.getsockoptInt(socket, IPPROTO_IPV6, IPV6_MTU);
+ }
+ } catch (ErrnoException | IOException e) {
+ Log.e(TAG, "Can't get MTU for destination " + target, e);
+ return -1;
+ }
+ }
+
private void prepareExplicitSourceIcmpMeasurements(InetAddress target) {
for (LinkAddress l : mLinkProperties.getLinkAddresses()) {
InetAddress source = l.getAddress();
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index c15f042..beaa174 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -1011,9 +1011,8 @@
* @param ranges The updated UID ranges under VPN Lockdown. This function does not treat the VPN
* app's UID in any special way. The caller is responsible for excluding the VPN
* app UID from the passed-in ranges.
- * Ranges can have duplications and/or contain the range that is already subject
- * to lockdown. However, ranges can not have overlaps with other ranges including
- * ranges that are currently subject to lockdown.
+ * Ranges can have duplications, overlaps, and/or contain the range that is
+ * already subject to lockdown.
*/
public synchronized void updateVpnLockdownUidRanges(boolean add, UidRange[] ranges) {
final Set<UidRange> affectedUidRanges = new HashSet<>();
@@ -1045,8 +1044,10 @@
// exclude privileged apps from the prohibit routing rules used to implement outgoing packet
// filtering, privileged apps can still bypass outgoing packet filtering because the
// prohibit rules observe the protected from VPN bit.
+ // If removing a UID, we ensure it is not present anywhere in the set first.
for (final int uid: affectedUids) {
- if (!hasRestrictedNetworksPermission(uid)) {
+ if (!hasRestrictedNetworksPermission(uid)
+ && (add || !UidRange.containsUid(mVpnLockdownUidRanges.getSet(), uid))) {
updateLockdownUidRule(uid, add);
}
}
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
new file mode 100644
index 0000000..77383ad
--- /dev/null
+++ b/tests/benchmark/Android.bp
@@ -0,0 +1,42 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "ConnectivityBenchmarkTests",
+ defaults: [
+ "framework-connectivity-internal-test-defaults",
+ ],
+ platform_apis: true,
+ srcs: [
+ "src/**/*.kt",
+ "src/**/*.aidl",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "mockito-target-minus-junit4",
+ "net-tests-utils",
+ "service-connectivity-pre-jarjar",
+ "service-connectivity-tiramisu-pre-jarjar",
+ ],
+ test_suites: ["device-tests"],
+ jarjar_rules: ":connectivity-jarjar-rules",
+}
+
diff --git a/tests/benchmark/AndroidManifest.xml b/tests/benchmark/AndroidManifest.xml
new file mode 100644
index 0000000..bd2fce5
--- /dev/null
+++ b/tests/benchmark/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.connectivity.benchmarktests">
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.connectivity.benchmarktests"
+ android:label="Connectivity Benchmark Tests" />
+</manifest>
diff --git a/tests/benchmark/OWNERS b/tests/benchmark/OWNERS
new file mode 100644
index 0000000..3101da5
--- /dev/null
+++ b/tests/benchmark/OWNERS
@@ -0,0 +1,2 @@
+# Bug template url: http://b/new?component=31808
+# TODO: move bug template config to common owners file once b/226427845 is resolved
\ No newline at end of file
diff --git a/tests/benchmark/res/raw/netstats-many-uids-zip b/tests/benchmark/res/raw/netstats-many-uids-zip
new file mode 100644
index 0000000..22e8254
--- /dev/null
+++ b/tests/benchmark/res/raw/netstats-many-uids-zip
Binary files differ
diff --git a/tests/benchmark/src/android/net/netstats/benchmarktests/NetworkStatsTest.kt b/tests/benchmark/src/android/net/netstats/benchmarktests/NetworkStatsTest.kt
new file mode 100644
index 0000000..e80548b
--- /dev/null
+++ b/tests/benchmark/src/android/net/netstats/benchmarktests/NetworkStatsTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.benchmarktests
+
+import android.net.NetworkStats.NonMonotonicObserver
+import android.net.NetworkStatsCollection
+import android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID
+import android.os.DropBoxManager
+import androidx.test.InstrumentationRegistry
+import com.android.internal.util.FileRotator
+import com.android.internal.util.FileRotator.Reader
+import com.android.server.connectivity.benchmarktests.R
+import com.android.server.net.NetworkStatsRecorder
+import java.io.BufferedInputStream
+import java.io.DataInputStream
+import java.io.File
+import java.io.FileOutputStream
+import java.nio.file.Files
+import java.util.concurrent.TimeUnit
+import java.util.zip.ZipInputStream
+import kotlin.test.assertTrue
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+
+@RunWith(JUnit4::class)
+class NetworkStatsTest {
+ companion object {
+ private val DEFAULT_BUFFER_SIZE = 8192
+ private val FILE_CACHE_WARM_UP_REPEAT_COUNT = 10
+ private val TEST_REPEAT_COUNT = 10
+ private val UID_COLLECTION_BUCKET_DURATION_MS = TimeUnit.HOURS.toMillis(2)
+ private val UID_RECORDER_ROTATE_AGE_MS = TimeUnit.DAYS.toMillis(15)
+ private val UID_RECORDER_DELETE_AGE_MS = TimeUnit.DAYS.toMillis(90)
+
+ private val testFilesDir by lazy {
+ // These file generated by using real user dataset which has many uid records
+ // and agreed to share the dataset for testing purpose. These dataset can be
+ // extracted from rooted devices by using
+ // "adb pull /data/misc/apexdata/com.android.tethering/netstats" command.
+ val zipInputStream =
+ ZipInputStream(getInputStreamForResource(R.raw.netstats_many_uids_zip))
+ unzipToTempDir(zipInputStream)
+ }
+
+ private val uidTestFiles: List<File> by lazy {
+ getSortedListForPrefix(testFilesDir, "uid")
+ }
+
+ // Test results shows the test cases who read the file first will take longer time to
+ // execute, and reading time getting shorter each time due to file caching mechanism.
+ // Read files several times prior to tests to minimize the impact.
+ // This cannot live in setUp() since the time spent on the file reading will be
+ // attributed to the time spent on the individual test case.
+ @JvmStatic
+ @BeforeClass
+ fun setUpOnce() {
+ repeat(FILE_CACHE_WARM_UP_REPEAT_COUNT) {
+ val collection = NetworkStatsCollection(UID_COLLECTION_BUCKET_DURATION_MS)
+ for (file in uidTestFiles) {
+ readFile(file, collection)
+ }
+ }
+ }
+
+ private fun getInputStreamForResource(resourceId: Int): DataInputStream =
+ DataInputStream(
+ InstrumentationRegistry.getContext()
+ .getResources().openRawResource(resourceId)
+ )
+
+ private fun unzipToTempDir(zis: ZipInputStream): File {
+ val statsDir =
+ Files.createTempDirectory(NetworkStatsTest::class.simpleName).toFile()
+ generateSequence { zis.nextEntry }.forEach { entry ->
+ FileOutputStream(File(statsDir, entry.name)).use {
+ zis.copyTo(it, DEFAULT_BUFFER_SIZE)
+ }
+ }
+ return statsDir
+ }
+
+ // List [xt|uid|uid_tag].<start>-<end> files under the given directory.
+ private fun getSortedListForPrefix(statsDir: File, prefix: String): List<File> {
+ assertTrue(statsDir.exists())
+ return statsDir.list() { dir, name -> name.startsWith("$prefix.") }
+ .orEmpty()
+ .map { it -> File(statsDir, it) }
+ .sorted()
+ }
+
+ private fun readFile(file: File, reader: Reader) =
+ BufferedInputStream(file.inputStream()).use {
+ reader.read(it)
+ }
+ }
+
+ @Test
+ fun testReadCollection_manyUids() {
+ // The file cache is warmed up by the @BeforeClass method, so now the test can repeat
+ // this a number of time to have a stable number.
+ repeat(TEST_REPEAT_COUNT) {
+ val collection = NetworkStatsCollection(UID_COLLECTION_BUCKET_DURATION_MS)
+ for (file in uidTestFiles) {
+ readFile(file, collection)
+ }
+ }
+ }
+
+ @Test
+ fun testReadFromRecorder_manyUids() {
+ val mockObserver = mock<NonMonotonicObserver<String>>()
+ val mockDropBox = mock<DropBoxManager>()
+ repeat(TEST_REPEAT_COUNT) {
+ val recorder = NetworkStatsRecorder(
+ FileRotator(
+ testFilesDir, PREFIX_UID, UID_RECORDER_ROTATE_AGE_MS, UID_RECORDER_DELETE_AGE_MS
+ ),
+ mockObserver,
+ mockDropBox,
+ PREFIX_UID,
+ UID_COLLECTION_BUCKET_DURATION_MS,
+ false /* includeTags */,
+ false /* wipeOnError */
+ )
+ recorder.orLoadCompleteLocked
+ }
+ }
+
+ inline fun <reified T> mock(): T = mock(T::class.java)
+}
diff --git a/tests/common/java/android/net/NattKeepalivePacketDataTest.kt b/tests/common/java/android/net/NattKeepalivePacketDataTest.kt
index dde1d86..e5806a6 100644
--- a/tests/common/java/android/net/NattKeepalivePacketDataTest.kt
+++ b/tests/common/java/android/net/NattKeepalivePacketDataTest.kt
@@ -28,6 +28,7 @@
import com.android.testutils.assertEqualBothWays
import com.android.testutils.assertParcelingIsLossless
import com.android.testutils.parcelingRoundTrip
+import java.net.Inet6Address
import java.net.InetAddress
import kotlin.test.assertFailsWith
import org.junit.Assert.assertEquals
@@ -44,10 +45,33 @@
private val TEST_PORT = 4243
private val TEST_PORT2 = 4244
+ // ::FFFF:1.2.3.4
+ private val SRC_V4_MAPPED_V6_ADDRESS_BYTES = byteArrayOf(
+ 0x00.toByte(),
+ 0x00.toByte(),
+ 0x00.toByte(),
+ 0x00.toByte(),
+ 0x00.toByte(),
+ 0x00.toByte(),
+ 0x00.toByte(),
+ 0x00.toByte(),
+ 0x00.toByte(),
+ 0x00.toByte(),
+ 0xff.toByte(),
+ 0xff.toByte(),
+ 0x01.toByte(),
+ 0x02.toByte(),
+ 0x03.toByte(),
+ 0x04.toByte()
+ )
private val TEST_SRC_ADDRV4 = "198.168.0.2".address()
private val TEST_DST_ADDRV4 = "198.168.0.1".address()
private val TEST_ADDRV6 = "2001:db8::1".address()
- private val TEST_ADDRV4MAPPEDV6 = "::ffff:1.2.3.4".address()
+ // This constant requires to be an Inet6Address, but InetAddresses.parseNumericAddress() will
+ // convert v4 mapped v6 address into an Inet4Address. So use Inet6Address.getByAddress() to
+ // create the address.
+ private val TEST_ADDRV4MAPPEDV6 = Inet6Address.getByAddress(null /* host */,
+ SRC_V4_MAPPED_V6_ADDRESS_BYTES, -1 /* scope_id */)
private val TEST_ADDRV4 = "1.2.3.4".address()
private fun String.address() = InetAddresses.parseNumericAddress(this)
diff --git a/tests/cts/OWNERS b/tests/cts/OWNERS
index 8c2408b..286f9c8 100644
--- a/tests/cts/OWNERS
+++ b/tests/cts/OWNERS
@@ -1,7 +1,7 @@
# Bug template url: http://b/new?component=31808
# TODO: move bug template config to common owners file once b/226427845 is resolved
set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking_xts
# IPsec
per-file **IpSec* = benedictwong@google.com, nharold@google.com
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index 891c2dd..e55ba63 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -32,6 +32,7 @@
// Only compile source java files in this apk.
srcs: ["src/**/*.java"],
libs: [
+ "net-tests-utils-host-device-common",
"cts-tradefed",
"tradefed",
],
diff --git a/tests/cts/hostside/TEST_MAPPING b/tests/cts/hostside/TEST_MAPPING
index 2cfd7af..dc86fb1 100644
--- a/tests/cts/hostside/TEST_MAPPING
+++ b/tests/cts/hostside/TEST_MAPPING
@@ -11,6 +11,20 @@
},
{
"exclude-annotation": "android.platform.test.annotations.RequiresDevice"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ // Postsubmit on virtual devices to monitor flakiness of @SkipPresubmit methods
+ "name": "CtsHostsideNetworkTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
}
]
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractExpeditedJobTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractExpeditedJobTest.java
index a850e3b..7cac2af 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractExpeditedJobTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractExpeditedJobTest.java
@@ -74,6 +74,7 @@
@RequiredProperties({APP_STANDBY_MODE})
public void testNetworkAccess_appIdleState() throws Exception {
turnBatteryOn();
+ setAppIdle(false);
assertBackgroundNetworkAccess(true);
assertExpeditedJobHasNetworkAccess();
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 6b21dac..1c99722 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -29,6 +29,7 @@
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackgroundInternal;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -52,9 +53,9 @@
import android.os.BatteryManager;
import android.os.Binder;
import android.os.Bundle;
+import android.os.PowerManager;
import android.os.RemoteCallback;
import android.os.SystemClock;
-import android.os.PowerManager;
import android.provider.DeviceConfig;
import android.service.notification.NotificationListenerService;
import android.util.Log;
@@ -180,6 +181,12 @@
mServiceClient.bind();
mPowerManager = mContext.getSystemService(PowerManager.class);
executeShellCommand("cmd netpolicy start-watching " + mUid);
+ // Some of the test cases assume that Data saver mode is initially disabled, which might not
+ // always be the case. Therefore, explicitly disable it before running the tests.
+ // Invoke setRestrictBackgroundInternal() directly instead of going through
+ // setRestrictBackground(), as some devices do not fully support the Data saver mode but
+ // still have certain parts of it enabled by default.
+ setRestrictBackgroundInternal(false);
setAppIdle(false);
mLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java
index 78ae7b8..07434b1 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java
@@ -64,6 +64,8 @@
"dumpsys usagestats appstandby",
"dumpsys connectivity trafficcontroller",
"dumpsys netd trafficcontroller",
+ "dumpsys platform_compat", // TODO (b/279829773): Remove this dump
+ "dumpsys jobscheduler " + TEST_APP2_PKG, // TODO (b/288220398): Remove this dump
}) {
dumpCommandOutput(out, cmd);
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index deca6a2..8c38b44 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -344,7 +344,7 @@
setRestrictBackgroundInternal(enabled);
}
- private static void setRestrictBackgroundInternal(boolean enabled) {
+ static void setRestrictBackgroundInternal(boolean enabled) {
executeShellCommand("cmd netpolicy set restrict-background " + enabled);
final String output = executeShellCommand("cmd netpolicy get restrict-background");
final String expectedSuffix = enabled ? "enabled" : "disabled";
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java
index 4266aad..35f1f1c 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java
@@ -57,14 +57,18 @@
@Test
public void testNetworkAccess_withBatterySaver() throws Exception {
setBatterySaverMode(true);
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
+ try {
+ addPowerSaveModeWhitelist(TEST_APP2_PKG);
+ assertBackgroundNetworkAccess(true);
- setRestrictedNetworkingMode(true);
- // App would be denied network access since Restricted mode is on.
- assertBackgroundNetworkAccess(false);
- setRestrictedNetworkingMode(false);
- // Given that Restricted mode is turned off, app should be able to access network again.
- assertBackgroundNetworkAccess(true);
+ setRestrictedNetworkingMode(true);
+ // App would be denied network access since Restricted mode is on.
+ assertBackgroundNetworkAccess(false);
+ setRestrictedNetworkingMode(false);
+ // Given that Restricted mode is turned off, app should be able to access network again.
+ assertBackgroundNetworkAccess(true);
+ } finally {
+ setBatterySaverMode(false);
+ }
}
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
index a7d5590..849ac7c 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
@@ -18,25 +18,26 @@
import android.platform.test.annotations.FlakyTest;
-import org.junit.After;
-import org.junit.Before;
+import com.android.testutils.SkipPresubmit;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+
import org.junit.Test;
+@SkipPresubmit(reason = "Out of SLO flakiness")
public class HostsideConnOnActivityStartTest extends HostsideNetworkTestCase {
private static final String TEST_CLASS = TEST_PKG + ".ConnOnActivityStartTest";
- @Before
- public void setUp() throws Exception {
- super.setUp();
-
- uninstallPackage(TEST_APP2_PKG, false);
- installPackage(TEST_APP2_APK);
+ @BeforeClassWithInfo
+ public static void setUpOnce(TestInformation testInfo) throws Exception {
+ uninstallPackage(testInfo, TEST_APP2_PKG, false);
+ installPackage(testInfo, TEST_APP2_APK);
}
- @After
- public void tearDown() throws Exception {
- super.tearDown();
-
- uninstallPackage(TEST_APP2_PKG, true);
+ @AfterClassWithInfo
+ public static void tearDownOnce(TestInformation testInfo) throws DeviceNotAvailableException {
+ uninstallPackage(testInfo, TEST_APP2_PKG, true);
}
@Test
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
index 5d7ad62..04bd1ad 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
@@ -15,22 +15,23 @@
*/
package com.android.cts.net;
+import com.android.testutils.SkipPresubmit;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+@SkipPresubmit(reason = "Out of SLO flakiness")
public class HostsideNetworkCallbackTests extends HostsideNetworkTestCase {
@Before
public void setUp() throws Exception {
- super.setUp();
uninstallPackage(TEST_APP2_PKG, false);
installPackage(TEST_APP2_APK);
}
@After
public void tearDown() throws Exception {
- super.tearDown();
uninstallPackage(TEST_APP2_PKG, true);
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
index 40f5f59..3ddb88b 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
@@ -23,14 +23,12 @@
public class HostsideNetworkPolicyManagerTests extends HostsideNetworkTestCase {
@Before
public void setUp() throws Exception {
- super.setUp();
uninstallPackage(TEST_APP2_PKG, false);
installPackage(TEST_APP2_APK);
}
@After
public void tearDown() throws Exception {
- super.tearDown();
uninstallPackage(TEST_APP2_PKG, true);
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
index c896168..b89ab1f 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -16,16 +16,21 @@
package com.android.cts.net;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import com.android.ddmlib.Log;
import com.android.modules.utils.build.testing.DeviceSdkLevel;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.targetprep.BuildError;
import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.targetprep.suite.SuiteApkInstaller;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
import com.android.tradefed.util.RunUtil;
import org.junit.runner.RunWith;
@@ -40,34 +45,62 @@
protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
protected static final String TEST_APP2_APK = "CtsHostsideNetworkTestsApp2.apk";
- protected void setUp() throws Exception {
- DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(getDevice());
- String testApk = deviceSdkLevel.isDeviceAtLeastT() ? TEST_APK_NEXT
- : TEST_APK;
+ @BeforeClassWithInfo
+ public static void setUpOnceBase(TestInformation testInfo) throws Exception {
+ DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(testInfo.getDevice());
+ String testApk = deviceSdkLevel.isDeviceAtLeastT() ? TEST_APK_NEXT : TEST_APK;
- uninstallPackage(TEST_PKG, false);
- installPackage(testApk);
+ uninstallPackage(testInfo, TEST_PKG, false);
+ installPackage(testInfo, testApk);
}
- protected void tearDown() throws Exception {
- uninstallPackage(TEST_PKG, true);
+ @AfterClassWithInfo
+ public static void tearDownOnceBase(TestInformation testInfo)
+ throws DeviceNotAvailableException {
+ uninstallPackage(testInfo, TEST_PKG, true);
+ }
+
+ // Custom static method to install the specified package, this is used to bypass auto-cleanup
+ // per test in BaseHostJUnit4.
+ protected static void installPackage(TestInformation testInfo, String apk)
+ throws DeviceNotAvailableException, TargetSetupError {
+ assertNotNull(testInfo);
+ final int userId = testInfo.getDevice().getCurrentUser();
+ final SuiteApkInstaller installer = new SuiteApkInstaller();
+ // Force the apk clean up
+ installer.setCleanApk(true);
+ installer.addTestFileName(apk);
+ installer.setUserId(userId);
+ installer.setShouldGrantPermission(true);
+ installer.addInstallArg("-t");
+ try {
+ installer.setUp(testInfo);
+ } catch (BuildError e) {
+ throw new TargetSetupError(
+ e.getMessage(), e, testInfo.getDevice().getDeviceDescriptor(), e.getErrorId());
+ }
}
protected void installPackage(String apk) throws DeviceNotAvailableException, TargetSetupError {
- final DeviceTestRunOptions installOptions = new DeviceTestRunOptions(
- null /* packageName */);
- final int userId = getDevice().getCurrentUser();
- installPackageAsUser(apk, true /* grantPermission */, userId, "-t");
+ installPackage(getTestInformation(), apk);
}
- protected void uninstallPackage(String packageName, boolean shouldSucceed)
+ protected static void uninstallPackage(TestInformation testInfo, String packageName,
+ boolean shouldSucceed)
throws DeviceNotAvailableException {
- final String result = uninstallPackage(packageName);
+ assertNotNull(testInfo);
+ final String result = testInfo.getDevice().uninstallPackage(packageName);
if (shouldSucceed) {
assertNull("uninstallPackage(" + packageName + ") failed: " + result, result);
}
}
+ protected void uninstallPackage(String packageName,
+ boolean shouldSucceed)
+ throws DeviceNotAvailableException {
+ uninstallPackage(getTestInformation(), packageName, shouldSucceed);
+ }
+
protected void assertPackageUninstalled(String packageName) throws DeviceNotAvailableException,
InterruptedException {
final String command = "cmd package list packages " + packageName;
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
index 0977deb..9c3751d 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
@@ -21,6 +21,7 @@
import android.platform.test.annotations.SecurityTest;
import com.android.ddmlib.Log;
+import com.android.testutils.SkipPresubmit;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.util.RunUtil;
@@ -28,20 +29,17 @@
import org.junit.Before;
import org.junit.Test;
+@SkipPresubmit(reason = "Out of SLO flakiness")
public class HostsideRestrictBackgroundNetworkTests extends HostsideNetworkTestCase {
@Before
public void setUp() throws Exception {
- super.setUp();
-
uninstallPackage(TEST_APP2_PKG, false);
installPackage(TEST_APP2_APK);
}
@After
public void tearDown() throws Exception {
- super.tearDown();
-
uninstallPackage(TEST_APP2_PKG, true);
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
index 242fd5d..4f21af7 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
@@ -18,6 +18,8 @@
import android.platform.test.annotations.RequiresDevice;
+import com.android.testutils.SkipPresubmit;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -26,24 +28,22 @@
@Before
public void setUp() throws Exception {
- super.setUp();
-
uninstallPackage(TEST_APP2_PKG, false);
installPackage(TEST_APP2_APK);
}
@After
public void tearDown() throws Exception {
- super.tearDown();
-
uninstallPackage(TEST_APP2_PKG, true);
}
+ @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testChangeUnderlyingNetworks() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testChangeUnderlyingNetworks");
}
+ @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testDefault() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testDefault");
@@ -166,6 +166,7 @@
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testBlockIncomingPackets");
}
+ @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testSetVpnDefaultForUids() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetVpnDefaultForUids");
diff --git a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
index dc22369..1d79806 100644
--- a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
+++ b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
@@ -49,6 +49,7 @@
import com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTP_URL
import com.android.testutils.DeviceConfigRule
import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.SkipMainlinePresubmit
import com.android.testutils.TestHttpServer
import com.android.testutils.TestHttpServer.Request
import com.android.testutils.TestableNetworkCallback
@@ -137,6 +138,7 @@
}
@Test
+ @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
fun testCaptivePortalIsNotDefaultNetwork() {
assumeTrue(pm.hasSystemFeature(FEATURE_TELEPHONY))
assumeTrue(pm.hasSystemFeature(FEATURE_WIFI))
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index a0508e1..e978664 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -197,6 +197,8 @@
import com.android.testutils.DeviceInfoUtils;
import com.android.testutils.DumpTestUtils;
import com.android.testutils.RecorderCallback.CallbackEntry;
+import com.android.testutils.SkipMainlinePresubmit;
+import com.android.testutils.SkipPresubmit;
import com.android.testutils.TestHttpServer;
import com.android.testutils.TestNetworkTracker;
import com.android.testutils.TestableNetworkCallback;
@@ -1017,6 +1019,7 @@
@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
public void testIsPrivateDnsBroken() throws InterruptedException {
final String invalidPrivateDnsServer = "invalidhostname.example.com";
final String goodPrivateDnsServer = "dns.google";
@@ -1124,6 +1127,7 @@
*/
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
@Test
+ @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
public void testRegisterNetworkCallback_withPendingIntent() {
assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
@@ -1267,6 +1271,7 @@
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
@Test
+ @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
public void testRegisterNetworkRequest_identicalPendingIntents() throws Exception {
runIdenticalPendingIntentsRequestTest(false /* useListen */);
}
@@ -2129,6 +2134,7 @@
*/
@AppModeFull(reason = "NETWORK_AIRPLANE_MODE permission can't be granted to instant apps")
@Test
+ @SkipPresubmit(reason = "Out of SLO flakiness")
public void testSetAirplaneMode() throws Exception{
// Starting from T, wifi supports airplane mode enhancement which may not disconnect wifi
// when airplane mode is on. The actual behavior that the device will have could only be
@@ -2560,10 +2566,9 @@
assertThrows(SecurityException.class, () -> mCm.factoryReset());
}
- // @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
- // @Test
- // Temporarily disable the unreliable test, which is blocked by b/254183718.
- private void testFactoryReset() throws Exception {
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
+ public void testFactoryReset() throws Exception {
assumeTrue(TestUtils.shouldTestSApis());
// Store current settings.
@@ -2592,6 +2597,7 @@
// prevent the race condition between airplane mode enabled and the followed
// up wifi tethering enabled.
tetherEventCallback.expectNoTetheringActive();
+ tetherUtils.expectSoftApDisabled();
// start wifi tethering
tetherUtils.startWifiTethering(tetherEventCallback);
@@ -2738,6 +2744,7 @@
*/
@AppModeFull(reason = "Instant apps cannot create test networks")
@Test
+ @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
public void testSetOemNetworkPreferenceForTestOnlyPref() throws Exception {
// Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
// shims, and @IgnoreUpTo does not check that.
@@ -2892,6 +2899,7 @@
@AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
@Test
+ @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
public void testRejectPartialConnectivity_TearDownNetwork() throws Exception {
assumeTrue(TestUtils.shouldTestSApis());
assumeTrue("testAcceptPartialConnectivity_validatedNetwork cannot execute"
@@ -3197,6 +3205,7 @@
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
@Test
+ @SkipPresubmit(reason = "Out of SLO flakiness")
public void testMobileDataPreferredUids() throws Exception {
assumeTrue(TestUtils.shouldTestSApis());
final boolean canRunTest = mPackageManager.hasSystemFeature(FEATURE_WIFI)
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index 3821cea..308aead 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -59,6 +59,7 @@
import com.android.net.module.util.DnsPacket;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DnsResolverModuleTest;
import com.android.testutils.SkipPresubmit;
import org.junit.After;
@@ -317,51 +318,61 @@
}
@Test
+ @DnsResolverModuleTest
public void testRawQuery() throws Exception {
doTestRawQuery(mExecutor);
}
@Test
+ @DnsResolverModuleTest
public void testRawQueryInline() throws Exception {
doTestRawQuery(mExecutorInline);
}
@Test
+ @DnsResolverModuleTest
public void testRawQueryBlob() throws Exception {
doTestRawQueryBlob(mExecutor);
}
@Test
+ @DnsResolverModuleTest
public void testRawQueryBlobInline() throws Exception {
doTestRawQueryBlob(mExecutorInline);
}
@Test
+ @DnsResolverModuleTest
public void testRawQueryRoot() throws Exception {
doTestRawQueryRoot(mExecutor);
}
@Test
+ @DnsResolverModuleTest
public void testRawQueryRootInline() throws Exception {
doTestRawQueryRoot(mExecutorInline);
}
@Test
+ @DnsResolverModuleTest
public void testRawQueryNXDomain() throws Exception {
doTestRawQueryNXDomain(mExecutor);
}
@Test
+ @DnsResolverModuleTest
public void testRawQueryNXDomainInline() throws Exception {
doTestRawQueryNXDomain(mExecutorInline);
}
@Test
+ @DnsResolverModuleTest
public void testRawQueryNXDomainWithPrivateDns() throws Exception {
doTestRawQueryNXDomainWithPrivateDns(mExecutor);
}
@Test
+ @DnsResolverModuleTest
public void testRawQueryNXDomainInlineWithPrivateDns() throws Exception {
doTestRawQueryNXDomainWithPrivateDns(mExecutorInline);
}
@@ -610,41 +621,49 @@
}
@Test
+ @DnsResolverModuleTest
public void testQueryForInetAddress() throws Exception {
doTestQueryForInetAddress(mExecutor);
}
@Test
+ @DnsResolverModuleTest
public void testQueryForInetAddressInline() throws Exception {
doTestQueryForInetAddress(mExecutorInline);
}
@Test
+ @DnsResolverModuleTest
public void testQueryForInetAddressIpv4() throws Exception {
doTestQueryForInetAddressIpv4(mExecutor);
}
@Test
+ @DnsResolverModuleTest
public void testQueryForInetAddressIpv4Inline() throws Exception {
doTestQueryForInetAddressIpv4(mExecutorInline);
}
@Test
+ @DnsResolverModuleTest
public void testQueryForInetAddressIpv6() throws Exception {
doTestQueryForInetAddressIpv6(mExecutor);
}
@Test
+ @DnsResolverModuleTest
public void testQueryForInetAddressIpv6Inline() throws Exception {
doTestQueryForInetAddressIpv6(mExecutorInline);
}
@Test
+ @DnsResolverModuleTest
public void testContinuousQueries() throws Exception {
doTestContinuousQueries(mExecutor);
}
@Test
+ @DnsResolverModuleTest
@SkipPresubmit(reason = "Flaky: b/159762682; add to presubmit after fixing")
public void testContinuousQueriesInline() throws Exception {
doTestContinuousQueries(mExecutorInline);
diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
index 805dd65..81834a9 100644
--- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
+++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
@@ -71,6 +71,8 @@
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.RecorderCallback.CallbackEntry;
+import com.android.testutils.SkipMainlinePresubmit;
+import com.android.testutils.SkipPresubmit;
import com.android.testutils.TestableNetworkCallback;
import org.bouncycastle.x509.X509V1CertificateGenerator;
@@ -640,6 +642,7 @@
}
@Test
+ @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
public void testStartStopVpnProfileV4() throws Exception {
doTestStartStopVpnProfile(false /* testIpv6Only */, false /* requiresValidation */,
false /* testSessionKey */, false /* testIkeTunConnParams */);
@@ -653,12 +656,14 @@
}
@Test
+ @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
public void testStartStopVpnProfileV6() throws Exception {
doTestStartStopVpnProfile(true /* testIpv6Only */, false /* requiresValidation */,
false /* testSessionKey */, false /* testIkeTunConnParams */);
}
@Test @IgnoreUpTo(SC_V2)
+ @SkipPresubmit(reason = "Out of SLO flakiness")
public void testStartStopVpnProfileV6WithValidation() throws Exception {
assumeTrue(TestUtils.shouldTestTApis());
doTestStartStopVpnProfile(true /* testIpv6Only */, true /* requiresValidation */,
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index cc0a5df..fe86a90 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -81,6 +81,7 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.SkipMainlinePresubmit;
import org.junit.Rule;
import org.junit.Test;
@@ -716,6 +717,7 @@
}
@Test
+ @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
public void testIkeOverUdpEncapSocket() throws Exception {
// IPv6 not supported for UDP-encap-ESP
InetAddress local = InetAddress.getByName(IPV4_LOOPBACK);
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
index 83b9b81..7bccbde 100644
--- a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -82,9 +82,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.UnknownHostException;
@@ -220,7 +220,7 @@
} else {
Log.w(LOG_TAG, "Network: " + networkInfo.toString());
}
- InputStreamReader in = null;
+ BufferedInputStream in = null;
HttpURLConnection urlc = null;
String originalKeepAlive = System.getProperty("http.keepAlive");
System.setProperty("http.keepAlive", "false");
@@ -236,10 +236,10 @@
urlc.connect();
boolean ping = urlc.getResponseCode() == 200;
if (ping) {
- in = new InputStreamReader((InputStream) urlc.getContent());
- // Since the test doesn't really care about the precise amount of data, instead
- // of reading all contents, just read few bytes at the beginning.
- in.read();
+ in = new BufferedInputStream((InputStream) urlc.getContent());
+ while (in.read() != -1) {
+ // Comments to suppress lint error.
+ }
}
} catch (Exception e) {
Log.i(LOG_TAG, "Badness during exercising remote server: " + e);
@@ -377,9 +377,14 @@
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build(), callback);
synchronized (this) {
- try {
- wait((int) (TIMEOUT_MILLIS * 2.4));
- } catch (InterruptedException e) {
+ long now = System.currentTimeMillis();
+ final long deadline = (long) (now + TIMEOUT_MILLIS * 2.4);
+ while (!callback.success && now < deadline) {
+ try {
+ wait(deadline - now);
+ } catch (InterruptedException e) {
+ }
+ now = System.currentTimeMillis();
}
}
if (callback.success) {
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 6c411cf..7731ac9 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -16,6 +16,7 @@
package android.net.cts
import android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.Manifest.permission.NETWORK_SETTINGS
import android.app.compat.CompatChanges
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
@@ -60,6 +61,8 @@
import android.net.nsd.NsdManager.RegistrationListener
import android.net.nsd.NsdManager.ResolveListener
import android.net.nsd.NsdServiceInfo
+import android.net.nsd.OffloadEngine
+import android.net.nsd.OffloadServiceInfo
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
@@ -80,8 +83,6 @@
import com.android.modules.utils.build.SdkLevel.isAtLeastU
import com.android.net.module.util.ArrayTrackRecord
import com.android.net.module.util.TrackRecord
-import com.android.networkstack.apishim.NsdShimImpl
-import com.android.networkstack.apishim.common.NsdShim
import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
@@ -133,8 +134,6 @@
private const val DBG = false
private const val TEST_PORT = 12345
-private val nsdShim = NsdShimImpl.newInstance()
-
@AppModeFull(reason = "Socket cannot bind in instant app mode")
@RunWith(DevSdkIgnoreRunner::class)
@SmallTest
@@ -293,7 +292,7 @@
val serviceFound = expectCallbackEventually<ServiceFound> {
it.serviceInfo.serviceName == serviceName &&
(expectedNetwork == null ||
- expectedNetwork == nsdShim.getNetwork(it.serviceInfo))
+ expectedNetwork == it.serviceInfo.network)
}.serviceInfo
// Discovered service types have a dot at the end
assertEquals("$serviceType.", serviceFound.serviceType)
@@ -331,7 +330,7 @@
}
}
- private class NsdServiceInfoCallbackRecord : NsdShim.ServiceInfoCallbackShim,
+ private class NsdServiceInfoCallbackRecord : NsdManager.ServiceInfoCallback,
NsdRecord<NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent>() {
sealed class ServiceInfoCallbackEvent : NsdEvent {
data class RegisterCallbackFailed(val errorCode: Int) : ServiceInfoCallbackEvent()
@@ -357,15 +356,29 @@
}
}
+ private class TestNsdOffloadEngine : OffloadEngine,
+ NsdRecord<TestNsdOffloadEngine.OffloadEvent>() {
+ sealed class OffloadEvent : NsdEvent {
+ data class AddOrUpdateEvent(val info: OffloadServiceInfo) : OffloadEvent()
+ data class RemoveEvent(val info: OffloadServiceInfo) : OffloadEvent()
+ }
+
+ override fun onOffloadServiceUpdated(info: OffloadServiceInfo) {
+ add(OffloadEvent.AddOrUpdateEvent(info))
+ }
+
+ override fun onOffloadServiceRemoved(info: OffloadServiceInfo) {
+ add(OffloadEvent.RemoveEvent(info))
+ }
+ }
+
@Before
fun setUp() {
handlerThread.start()
- if (TestUtils.shouldTestTApis()) {
- runAsShell(MANAGE_TEST_NETWORKS) {
- testNetwork1 = createTestNetwork()
- testNetwork2 = createTestNetwork()
- }
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ testNetwork1 = createTestNetwork()
+ testNetwork2 = createTestNetwork()
}
}
@@ -450,12 +463,10 @@
@After
fun tearDown() {
- if (TestUtils.shouldTestTApis()) {
- runAsShell(MANAGE_TEST_NETWORKS) {
- // Avoid throwing here if initializing failed in setUp
- if (this::testNetwork1.isInitialized) testNetwork1.close(cm)
- if (this::testNetwork2.isInitialized) testNetwork2.close(cm)
- }
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ // Avoid throwing here if initializing failed in setUp
+ if (this::testNetwork1.isInitialized) testNetwork1.close(cm)
+ if (this::testNetwork2.isInitialized) testNetwork2.close(cm)
}
handlerThread.waitForIdle(TIMEOUT_MS)
handlerThread.quitSafely()
@@ -601,9 +612,6 @@
@Test
fun testNsdManager_DiscoverOnNetwork() {
- // This test requires shims supporting T+ APIs (discovering on specific network)
- assumeTrue(TestUtils.shouldTestTApis())
-
val si = NsdServiceInfo()
si.serviceType = serviceType
si.serviceName = this.serviceName
@@ -614,19 +622,19 @@
tryTest {
val discoveryRecord = NsdDiscoveryRecord()
- nsdShim.discoverServices(nsdManager, serviceType, NsdManager.PROTOCOL_DNS_SD,
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
testNetwork1.network, Executor { it.run() }, discoveryRecord)
val foundInfo = discoveryRecord.waitForServiceDiscovered(
serviceName, serviceType, testNetwork1.network)
- assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo))
+ assertEquals(testNetwork1.network, foundInfo.network)
// Rewind to ensure the service is not found on the other interface
discoveryRecord.nextEvents.rewind(0)
assertNull(discoveryRecord.nextEvents.poll(timeoutMs = 100L) {
it is ServiceFound &&
it.serviceInfo.serviceName == registeredInfo.serviceName &&
- nsdShim.getNetwork(it.serviceInfo) != testNetwork1.network
+ it.serviceInfo.network != testNetwork1.network
}, "The service should not be found on this network")
} cleanup {
nsdManager.unregisterService(registrationRecord)
@@ -635,9 +643,6 @@
@Test
fun testNsdManager_DiscoverWithNetworkRequest() {
- // This test requires shims supporting T+ APIs (discovering on network request)
- assumeTrue(TestUtils.shouldTestTApis())
-
val si = NsdServiceInfo()
si.serviceType = serviceType
si.serviceName = this.serviceName
@@ -652,7 +657,7 @@
tryTest {
val specifier = TestNetworkSpecifier(testNetwork1.iface.interfaceName)
- nsdShim.discoverServices(nsdManager, serviceType, NsdManager.PROTOCOL_DNS_SD,
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
NetworkRequest.Builder()
.removeCapability(NET_CAPABILITY_TRUSTED)
.addTransportType(TRANSPORT_TEST)
@@ -667,27 +672,27 @@
assertEquals(registeredInfo1.serviceName, serviceDiscovered.serviceInfo.serviceName)
// Discovered service types have a dot at the end
assertEquals("$serviceType.", serviceDiscovered.serviceInfo.serviceType)
- assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceDiscovered.serviceInfo))
+ assertEquals(testNetwork1.network, serviceDiscovered.serviceInfo.network)
// Unregister, then register the service back: it should be lost and found again
nsdManager.unregisterService(registrationRecord)
val serviceLost1 = discoveryRecord.expectCallback<ServiceLost>()
assertEquals(registeredInfo1.serviceName, serviceLost1.serviceInfo.serviceName)
- assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceLost1.serviceInfo))
+ assertEquals(testNetwork1.network, serviceLost1.serviceInfo.network)
registrationRecord.expectCallback<ServiceUnregistered>()
val registeredInfo2 = registerService(registrationRecord, si, executor)
val serviceDiscovered2 = discoveryRecord.expectCallback<ServiceFound>()
assertEquals(registeredInfo2.serviceName, serviceDiscovered2.serviceInfo.serviceName)
assertEquals("$serviceType.", serviceDiscovered2.serviceInfo.serviceType)
- assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceDiscovered2.serviceInfo))
+ assertEquals(testNetwork1.network, serviceDiscovered2.serviceInfo.network)
// Teardown, then bring back up a network on the test interface: the service should
// go away, then come back
testNetwork1.agent.unregister()
val serviceLost = discoveryRecord.expectCallback<ServiceLost>()
assertEquals(registeredInfo2.serviceName, serviceLost.serviceInfo.serviceName)
- assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceLost.serviceInfo))
+ assertEquals(testNetwork1.network, serviceLost.serviceInfo.network)
val newAgent = runAsShell(MANAGE_TEST_NETWORKS) {
registerTestNetworkAgent(testNetwork1.iface.interfaceName)
@@ -696,7 +701,7 @@
val serviceDiscovered3 = discoveryRecord.expectCallback<ServiceFound>()
assertEquals(registeredInfo2.serviceName, serviceDiscovered3.serviceInfo.serviceName)
assertEquals("$serviceType.", serviceDiscovered3.serviceInfo.serviceType)
- assertEquals(newNetwork, nsdShim.getNetwork(serviceDiscovered3.serviceInfo))
+ assertEquals(newNetwork, serviceDiscovered3.serviceInfo.network)
} cleanupStep {
nsdManager.stopServiceDiscovery(discoveryRecord)
discoveryRecord.expectCallback<DiscoveryStopped>()
@@ -707,9 +712,6 @@
@Test
fun testNsdManager_DiscoverWithNetworkRequest_NoMatchingNetwork() {
- // This test requires shims supporting T+ APIs (discovering on network request)
- assumeTrue(TestUtils.shouldTestTApis())
-
val si = NsdServiceInfo()
si.serviceType = serviceType
si.serviceName = this.serviceName
@@ -722,7 +724,7 @@
val specifier = TestNetworkSpecifier(testNetwork1.iface.interfaceName)
tryTest {
- nsdShim.discoverServices(nsdManager, serviceType, NsdManager.PROTOCOL_DNS_SD,
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
NetworkRequest.Builder()
.removeCapability(NET_CAPABILITY_TRUSTED)
.addTransportType(TRANSPORT_TEST)
@@ -754,9 +756,6 @@
@Test
fun testNsdManager_ResolveOnNetwork() {
- // This test requires shims supporting T+ APIs (NsdServiceInfo.network)
- assumeTrue(TestUtils.shouldTestTApis())
-
val si = NsdServiceInfo()
si.serviceType = serviceType
si.serviceName = this.serviceName
@@ -772,21 +771,21 @@
val foundInfo1 = discoveryRecord.waitForServiceDiscovered(
serviceName, serviceType, testNetwork1.network)
- assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo1))
+ assertEquals(testNetwork1.network, foundInfo1.network)
// Rewind as the service could be found on each interface in any order
discoveryRecord.nextEvents.rewind(0)
val foundInfo2 = discoveryRecord.waitForServiceDiscovered(
serviceName, serviceType, testNetwork2.network)
- assertEquals(testNetwork2.network, nsdShim.getNetwork(foundInfo2))
+ assertEquals(testNetwork2.network, foundInfo2.network)
- nsdShim.resolveService(nsdManager, foundInfo1, Executor { it.run() }, resolveRecord)
+ nsdManager.resolveService(foundInfo1, Executor { it.run() }, resolveRecord)
val cb = resolveRecord.expectCallback<ServiceResolved>()
cb.serviceInfo.let {
// Resolved service type has leading dot
assertEquals(".$serviceType", it.serviceType)
assertEquals(registeredInfo.serviceName, it.serviceName)
assertEquals(si.port, it.port)
- assertEquals(testNetwork1.network, nsdShim.getNetwork(it))
+ assertEquals(testNetwork1.network, it.network)
checkAddressScopeId(testNetwork1.iface, it.hostAddresses)
}
// TODO: check that MDNS packets are sent only on testNetwork1.
@@ -799,9 +798,6 @@
@Test
fun testNsdManager_RegisterOnNetwork() {
- // This test requires shims supporting T+ APIs (NsdServiceInfo.network)
- assumeTrue(TestUtils.shouldTestTApis())
-
val si = NsdServiceInfo()
si.serviceType = serviceType
si.serviceName = this.serviceName
@@ -817,27 +813,27 @@
tryTest {
// Discover service on testNetwork1.
- nsdShim.discoverServices(nsdManager, serviceType, NsdManager.PROTOCOL_DNS_SD,
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
testNetwork1.network, Executor { it.run() }, discoveryRecord)
// Expect that service is found on testNetwork1
val foundInfo = discoveryRecord.waitForServiceDiscovered(
serviceName, serviceType, testNetwork1.network)
- assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo))
+ assertEquals(testNetwork1.network, foundInfo.network)
// Discover service on testNetwork2.
- nsdShim.discoverServices(nsdManager, serviceType, NsdManager.PROTOCOL_DNS_SD,
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
testNetwork2.network, Executor { it.run() }, discoveryRecord2)
// Expect that discovery is started then no other callbacks.
discoveryRecord2.expectCallback<DiscoveryStarted>()
discoveryRecord2.assertNoCallback()
// Discover service on all networks (not specify any network).
- nsdShim.discoverServices(nsdManager, serviceType, NsdManager.PROTOCOL_DNS_SD,
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
null as Network? /* network */, Executor { it.run() }, discoveryRecord3)
// Expect that service is found on testNetwork1
val foundInfo3 = discoveryRecord3.waitForServiceDiscovered(
serviceName, serviceType, testNetwork1.network)
- assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo3))
+ assertEquals(testNetwork1.network, foundInfo3.network)
} cleanupStep {
nsdManager.stopServiceDiscovery(discoveryRecord2)
discoveryRecord2.expectCallback<DiscoveryStopped>()
@@ -881,6 +877,52 @@
}
}
+ fun checkOffloadServiceInfo(serviceInfo: OffloadServiceInfo) {
+ assertEquals(serviceName, serviceInfo.key.serviceName)
+ assertEquals(serviceType, serviceInfo.key.serviceType)
+ assertEquals(listOf<String>("_subtype"), serviceInfo.subtypes)
+ assertTrue(serviceInfo.hostname.startsWith("Android_"))
+ assertTrue(serviceInfo.hostname.endsWith("local"))
+ assertEquals(0, serviceInfo.priority)
+ assertEquals(OffloadEngine.OFFLOAD_TYPE_REPLY.toLong(), serviceInfo.offloadType)
+ }
+
+ @Test
+ fun testNsdManager_registerOffloadEngine() {
+ val targetSdkVersion = context.packageManager
+ .getTargetSdkVersion(context.applicationInfo.packageName)
+ // The offload callbacks are only supported with the new backend,
+ // enabled with target SDK U+.
+ assumeTrue(isAtLeastU() || targetSdkVersion > Build.VERSION_CODES.TIRAMISU)
+ val offloadEngine = TestNsdOffloadEngine()
+ runAsShell(NETWORK_SETTINGS) {
+ nsdManager.registerOffloadEngine(testNetwork1.iface.interfaceName,
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong(),
+ OffloadEngine.OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK.toLong(),
+ { it.run() }, offloadEngine)
+ }
+
+ val si = NsdServiceInfo()
+ si.serviceType = "$serviceType,_subtype"
+ si.serviceName = serviceName
+ si.network = testNetwork1.network
+ si.port = 12345
+ val record = NsdRegistrationRecord()
+ nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, record)
+ val addOrUpdateEvent = offloadEngine
+ .expectCallback<TestNsdOffloadEngine.OffloadEvent.AddOrUpdateEvent>()
+ checkOffloadServiceInfo(addOrUpdateEvent.info)
+
+ nsdManager.unregisterService(record)
+ val unregisterEvent = offloadEngine
+ .expectCallback<TestNsdOffloadEngine.OffloadEvent.RemoveEvent>()
+ checkOffloadServiceInfo(unregisterEvent.info)
+
+ runAsShell(NETWORK_SETTINGS) {
+ nsdManager.unregisterOffloadEngine(offloadEngine)
+ }
+ }
+
private fun checkConnectSocketToMdnsd(shouldFail: Boolean) {
val discoveryRecord = NsdDiscoveryRecord()
val localSocket = LocalSocket()
@@ -970,9 +1012,6 @@
@Test
fun testStopServiceResolution() {
- // This test requires shims supporting U+ APIs (NsdManager.stopServiceResolution)
- assumeTrue(TestUtils.shouldTestUApis())
-
val si = NsdServiceInfo()
si.serviceType = this@NsdManagerTest.serviceType
si.serviceName = this@NsdManagerTest.serviceName
@@ -981,8 +1020,8 @@
val resolveRecord = NsdResolveRecord()
// Try to resolve an unknown service then stop it immediately.
// Expected ResolutionStopped callback.
- nsdShim.resolveService(nsdManager, si, { it.run() }, resolveRecord)
- nsdShim.stopServiceResolution(nsdManager, resolveRecord)
+ nsdManager.resolveService(si, { it.run() }, resolveRecord)
+ nsdManager.stopServiceResolution(resolveRecord)
val stoppedCb = resolveRecord.expectCallback<ResolutionStopped>()
assertEquals(si.serviceName, stoppedCb.serviceInfo.serviceName)
assertEquals(si.serviceType, stoppedCb.serviceInfo.serviceType)
@@ -990,9 +1029,6 @@
@Test
fun testRegisterServiceInfoCallback() {
- // This test requires shims supporting U+ APIs (NsdManager.registerServiceInfoCallback)
- assumeTrue(TestUtils.shouldTestUApis())
-
val lp = cm.getLinkProperties(testNetwork1.network)
assertNotNull(lp)
val addresses = lp.addresses
@@ -1013,13 +1049,13 @@
val cbRecord = NsdServiceInfoCallbackRecord()
tryTest {
// Discover service on the network.
- nsdShim.discoverServices(nsdManager, serviceType, NsdManager.PROTOCOL_DNS_SD,
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
testNetwork1.network, Executor { it.run() }, discoveryRecord)
val foundInfo = discoveryRecord.waitForServiceDiscovered(
serviceName, serviceType, testNetwork1.network)
// Register service callback and check the addresses are the same as network addresses
- nsdShim.registerServiceInfoCallback(nsdManager, foundInfo, { it.run() }, cbRecord)
+ nsdManager.registerServiceInfoCallback(foundInfo, { it.run() }, cbRecord)
val serviceInfoCb = cbRecord.expectCallback<ServiceUpdated>()
assertEquals(foundInfo.serviceName, serviceInfoCb.serviceInfo.serviceName)
val hostAddresses = serviceInfoCb.serviceInfo.hostAddresses
@@ -1035,7 +1071,7 @@
cbRecord.expectCallback<ServiceUpdatedLost>()
} cleanupStep {
// Cancel subscription and check stop callback received.
- nsdShim.unregisterServiceInfoCallback(nsdManager, cbRecord)
+ nsdManager.unregisterServiceInfoCallback(cbRecord)
cbRecord.expectCallback<UnregisterCallbackSucceeded>()
} cleanup {
nsdManager.stopServiceDiscovery(discoveryRecord)
@@ -1045,9 +1081,6 @@
@Test
fun testStopServiceResolutionFailedCallback() {
- // This test requires shims supporting U+ APIs (NsdManager.stopServiceResolution)
- assumeTrue(TestUtils.shouldTestUApis())
-
// It's not possible to make ResolutionListener#onStopResolutionFailed callback sending
// because it is only sent in very edge-case scenarios when the legacy implementation is
// used, and the legacy implementation is never used in the current AOSP builds. Considering
@@ -1115,7 +1148,7 @@
si: NsdServiceInfo,
executor: Executor = Executor { it.run() }
): NsdServiceInfo {
- nsdShim.registerService(nsdManager, si, NsdManager.PROTOCOL_DNS_SD, executor, record)
+ nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, executor, record)
// We may not always get the name that we tried to register;
// This events tells us the name that was registered.
val cb = record.expectCallback<ServiceRegistered>(REGISTRATION_TIMEOUT_MS)
@@ -1124,7 +1157,7 @@
private fun resolveService(discoveredInfo: NsdServiceInfo): NsdServiceInfo {
val record = NsdResolveRecord()
- nsdShim.resolveService(nsdManager, discoveredInfo, Executor { it.run() }, record)
+ nsdManager.resolveService(discoveredInfo, Executor { it.run() }, record)
val resolvedCb = record.expectCallback<ServiceResolved>()
assertEquals(discoveredInfo.serviceName, resolvedCb.serviceInfo.serviceName)
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
index f506c23..dffd9d5 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
@@ -393,21 +393,28 @@
}
public void assumeTetheringSupported() {
+ assumeTrue(isTetheringSupported());
+ }
+
+ private boolean isTetheringSupported() {
final ArrayTrackRecord<CallbackValue>.ReadHead history =
mHistory.newReadHead();
- assertNotNull("No onSupported callback", history.poll(TIMEOUT_MS, (cv) -> {
- if (cv.callbackType != CallbackType.ON_SUPPORTED) return false;
+ final CallbackValue result = history.poll(TIMEOUT_MS, (cv) -> {
+ return cv.callbackType == CallbackType.ON_SUPPORTED;
+ });
- assumeTrue(cv.callbackParam2 == 1 /* supported */);
- return true;
- }));
+ assertNotNull("No onSupported callback", result);
+ return result.callbackParam2 == 1 /* supported */;
}
public void assumeWifiTetheringSupported(final Context ctx) throws Exception {
- assumeTetheringSupported();
+ assumeTrue(isWifiTetheringSupported(ctx));
+ }
- assumeTrue(!getTetheringInterfaceRegexps().getTetherableWifiRegexs().isEmpty());
- assumeTrue(isPortableHotspotSupported(ctx));
+ public boolean isWifiTetheringSupported(final Context ctx) throws Exception {
+ return isTetheringSupported()
+ && !getTetheringInterfaceRegexps().getTetherableWifiRegexs().isEmpty()
+ && isPortableHotspotSupported(ctx);
}
public TetheringInterfaceRegexps getTetheringInterfaceRegexps() {
diff --git a/tests/native/connectivity_native_test/OWNERS b/tests/native/connectivity_native_test/OWNERS
index fbfcf92..c9bfc40 100644
--- a/tests/native/connectivity_native_test/OWNERS
+++ b/tests/native/connectivity_native_test/OWNERS
@@ -1,4 +1,4 @@
# Bug template url: http://b/new?component=31808
# TODO: move bug template config to common owners file once b/226427845 is resolved
set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking_xts
diff --git a/tests/unit/java/android/net/NetworkStatsTest.java b/tests/unit/java/android/net/NetworkStatsTest.java
index 126ad55..4ff131b 100644
--- a/tests/unit/java/android/net/NetworkStatsTest.java
+++ b/tests/unit/java/android/net/NetworkStatsTest.java
@@ -1073,30 +1073,35 @@
final NetworkStats.Entry entry1 = new NetworkStats.Entry(
"test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 1024L, 50L, 100L, 20L, 0L);
-
final NetworkStats.Entry entry2 = new NetworkStats.Entry(
"test2", 10101, SET_DEFAULT, 0xF0DD, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 51200, 25L, 101010L, 50L, 0L);
+ final NetworkStats.Entry entry3 = new NetworkStats.Entry(
+ "test3", 10101, SET_DEFAULT, 0xF0DD, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1, 2L, 3L, 4L, 5L);
stats.insertEntry(entry1);
stats.insertEntry(entry2);
+ stats.insertEntry(entry3);
// Verify that the interfaces have indeed been recorded.
- assertEquals(2, stats.size());
+ assertEquals(3, stats.size());
assertValues(stats, 0, "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO,
ROAMING_NO, DEFAULT_NETWORK_NO, 1024L, 50L, 100L, 20L, 0L);
assertValues(stats, 1, "test2", 10101, SET_DEFAULT, 0xF0DD, METERED_NO,
ROAMING_NO, DEFAULT_NETWORK_NO, 51200, 25L, 101010L, 50L, 0L);
+ assertValues(stats, 2, "test3", 10101, SET_DEFAULT, 0xF0DD, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 1, 2L, 3L, 4L, 5L);
// Clear interfaces.
- stats.clearInterfaces();
+ final NetworkStats ifaceClearedStats = stats.clearInterfaces();
- // Verify that the interfaces are cleared.
- assertEquals(2, stats.size());
- assertValues(stats, 0, null /* iface */, 10100, SET_DEFAULT, TAG_NONE, METERED_NO,
- ROAMING_NO, DEFAULT_NETWORK_NO, 1024L, 50L, 100L, 20L, 0L);
- assertValues(stats, 1, null /* iface */, 10101, SET_DEFAULT, 0xF0DD, METERED_NO,
- ROAMING_NO, DEFAULT_NETWORK_NO, 51200, 25L, 101010L, 50L, 0L);
+ // Verify that the interfaces are cleared, and key-duplicated items are merged.
+ assertEquals(2, ifaceClearedStats.size());
+ assertValues(ifaceClearedStats, 0, null /* iface */, 10100, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1024L, 50L, 100L, 20L, 0L);
+ assertValues(ifaceClearedStats, 1, null /* iface */, 10101, SET_DEFAULT, 0xF0DD,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 51201, 27L, 101013L, 54L, 5L);
}
private static void assertContains(NetworkStats stats, String iface, int uid, int set,
diff --git a/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
new file mode 100644
index 0000000..961c422
--- /dev/null
+++ b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.metrics
+
+import android.os.Build
+import android.stats.connectivity.MdnsQueryResult
+import android.stats.connectivity.NsdEventType
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+class NetworkNsdReportedMetricsTest {
+ private val deps = mock(NetworkNsdReportedMetrics.Dependencies::class.java)
+
+ @Test
+ fun testReportServiceRegistrationSucceeded() {
+ val clientId = 99
+ val transactionId = 100
+ val durationMs = 10L
+ val metrics = NetworkNsdReportedMetrics(true /* isLegacy */, clientId, deps)
+ metrics.reportServiceRegistrationSucceeded(transactionId, durationMs)
+
+ val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
+ verify(deps).statsWrite(eventCaptor.capture())
+ eventCaptor.value.let {
+ assertTrue(it.isLegacy)
+ assertEquals(clientId, it.clientId)
+ assertEquals(transactionId, it.transactionId)
+ assertEquals(NsdEventType.NET_REGISTER, it.type)
+ assertEquals(MdnsQueryResult.MQR_SERVICE_REGISTERED, it.queryResult)
+ assertEquals(durationMs, it.eventDurationMillisec)
+ }
+ }
+
+ @Test
+ fun testReportServiceRegistrationFailed() {
+ val clientId = 99
+ val transactionId = 100
+ val durationMs = 10L
+ val metrics = NetworkNsdReportedMetrics(false /* isLegacy */, clientId, deps)
+ metrics.reportServiceRegistrationFailed(transactionId, durationMs)
+
+ val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
+ verify(deps).statsWrite(eventCaptor.capture())
+ eventCaptor.value.let {
+ assertFalse(it.isLegacy)
+ assertEquals(clientId, it.clientId)
+ assertEquals(transactionId, it.transactionId)
+ assertEquals(NsdEventType.NET_REGISTER, it.type)
+ assertEquals(MdnsQueryResult.MQR_SERVICE_REGISTRATION_FAILED, it.queryResult)
+ assertEquals(durationMs, it.eventDurationMillisec)
+ }
+ }
+
+ @Test
+ fun testReportServiceUnregistration() {
+ val clientId = 99
+ val transactionId = 100
+ val durationMs = 10L
+ val metrics = NetworkNsdReportedMetrics(true /* isLegacy */, clientId, deps)
+ metrics.reportServiceUnregistration(transactionId, durationMs)
+
+ val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
+ verify(deps).statsWrite(eventCaptor.capture())
+ eventCaptor.value.let {
+ assertTrue(it.isLegacy)
+ assertEquals(clientId, it.clientId)
+ assertEquals(transactionId, it.transactionId)
+ assertEquals(NsdEventType.NET_REGISTER, it.type)
+ assertEquals(MdnsQueryResult.MQR_SERVICE_UNREGISTERED, it.queryResult)
+ assertEquals(durationMs, it.eventDurationMillisec)
+ }
+ }
+}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index bbde9b4..3688d83 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -402,6 +402,7 @@
import com.android.server.connectivity.ClatCoordinator;
import com.android.server.connectivity.ConnectivityFlags;
import com.android.server.connectivity.ConnectivityResources;
+import com.android.server.connectivity.KeepaliveTracker;
import com.android.server.connectivity.MultinetworkPolicyTracker;
import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies;
import com.android.server.connectivity.Nat464Xlat;
@@ -410,6 +411,7 @@
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
+import com.android.server.connectivity.TcpKeepaliveController;
import com.android.server.connectivity.UidRangeUtils;
import com.android.server.connectivity.Vpn;
import com.android.server.connectivity.VpnProfileStore;
@@ -422,6 +424,7 @@
import com.android.testutils.FunctionalUtils.ThrowingRunnable;
import com.android.testutils.HandlerUtils;
import com.android.testutils.RecorderCallback.CallbackEntry;
+import com.android.testutils.SkipPresubmit;
import com.android.testutils.TestableNetworkCallback;
import com.android.testutils.TestableNetworkOfferCallback;
@@ -626,6 +629,7 @@
@Mock ActivityManager mActivityManager;
@Mock DestroySocketsWrapper mDestroySocketsWrapper;
@Mock SubscriptionManager mSubscriptionManager;
+ @Mock KeepaliveTracker.Dependencies mMockKeepaliveTrackerDependencies;
// BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
// underlying binder calls.
@@ -1897,6 +1901,12 @@
doReturn(mResources).when(mockResContext).getResources();
ConnectivityResources.setResourcesContextForTest(mockResContext);
mDeps = new ConnectivityServiceDependencies(mockResContext);
+ doReturn(true).when(mMockKeepaliveTrackerDependencies)
+ .isAddressTranslationEnabled(mServiceContext);
+ doReturn(new ConnectivityResources(mockResContext)).when(mMockKeepaliveTrackerDependencies)
+ .createConnectivityResources(mServiceContext);
+ doReturn(new int[] {1, 3, 0, 0}).when(mMockKeepaliveTrackerDependencies)
+ .getSupportedKeepalives(mServiceContext);
mAutoOnOffKeepaliveDependencies =
new AutomaticOnOffKeepaliveTrackerDependencies(mServiceContext);
mService = new ConnectivityService(mServiceContext,
@@ -2296,6 +2306,12 @@
// Assuming enabled here to focus on ConnectivityService tests.
return true;
}
+ public KeepaliveTracker newKeepaliveTracker(@NonNull Context context,
+ @NonNull Handler connectivityserviceHander) {
+ return new KeepaliveTracker(context, connectivityserviceHander,
+ new TcpKeepaliveController(connectivityserviceHander),
+ mMockKeepaliveTrackerDependencies);
+ }
}
private static void initAlarmManager(final AlarmManager am, final Handler alarmHandler) {
@@ -7414,6 +7430,7 @@
assertPinnedToWifiWithCellDefault();
}
+ @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testNetworkCallbackMaximum() throws Exception {
final int MAX_REQUESTS = 100;
@@ -9036,6 +9053,18 @@
mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback);
vpnNetworkCallback.assertNoCallback();
+ // Lingering timer is short and cell might be disconnected if the device is particularly
+ // slow running the test, unless it's requested. Make sure the networks the test needs
+ // are all requested.
+ final NetworkCallback cellCallback = new NetworkCallback() {};
+ final NetworkCallback wifiCallback = new NetworkCallback() {};
+ mCm.requestNetwork(
+ new NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).build(),
+ cellCallback);
+ mCm.requestNetwork(
+ new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(),
+ wifiCallback);
+
mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
false /* privateDnsProbeSent */);
assertUidRangesUpdatedForMyUid(true);
@@ -9192,6 +9221,8 @@
assertDefaultNetworkCapabilities(userId /* no networks */);
mMockVpn.disconnect();
+ mCm.unregisterNetworkCallback(cellCallback);
+ mCm.unregisterNetworkCallback(wifiCallback);
}
@Test
@@ -11318,17 +11349,18 @@
}
@Test
- public void testOnNetworkActive_NewEthernetConnects_Callback() throws Exception {
- // On T-, LegacyNetworkActivityTracker calls onNetworkActive callback only for networks that
+ public void testOnNetworkActive_NewEthernetConnects_CallbackNotCalled() throws Exception {
+ // LegacyNetworkActivityTracker calls onNetworkActive callback only for networks that
// tracker adds the idle timer to. And the tracker does not set the idle timer for the
// ethernet network.
// So onNetworkActive is not called when the ethernet becomes the default network
- doTestOnNetworkActive_NewNetworkConnects(TRANSPORT_ETHERNET, mDeps.isAtLeastU());
+ doTestOnNetworkActive_NewNetworkConnects(TRANSPORT_ETHERNET, false /* expectCallback */);
}
@Test
public void testIsDefaultNetworkActiveNoDefaultNetwork() throws Exception {
- assertFalse(mCm.isDefaultNetworkActive());
+ // isDefaultNetworkActive returns true if there is no default network, which is known issue.
+ assertTrue(mCm.isDefaultNetworkActive());
final LinkProperties cellLp = new LinkProperties();
cellLp.setInterfaceName(MOBILE_IFNAME);
@@ -11340,7 +11372,7 @@
mCellAgent.disconnect();
waitForIdle();
- assertFalse(mCm.isDefaultNetworkActive());
+ assertTrue(mCm.isDefaultNetworkActive());
}
@Test
@@ -18515,12 +18547,7 @@
waitForIdle();
- final Set<Integer> exemptUids = new ArraySet();
- final UidRange frozenUidRange = new UidRange(TEST_FROZEN_UID, TEST_FROZEN_UID);
- final Set<UidRange> ranges = Collections.singleton(frozenUidRange);
-
- verify(mDestroySocketsWrapper).destroyLiveTcpSockets(eq(UidRange.toIntRanges(ranges)),
- eq(exemptUids));
+ verify(mDestroySocketsWrapper).destroyLiveTcpSocketsByOwnerUids(Set.of(TEST_FROZEN_UID));
}
@Test
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index f51b28d..55384b3 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -43,7 +43,9 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.any;
@@ -95,6 +97,7 @@
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
+import com.android.metrics.NetworkNsdReportedMetrics;
import com.android.server.NsdService.Dependencies;
import com.android.server.connectivity.mdns.MdnsAdvertiser;
import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
@@ -104,6 +107,7 @@
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.server.connectivity.mdns.util.MdnsUtils;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
@@ -138,6 +142,7 @@
static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
private static final long CLEANUP_DELAY_MS = 500;
private static final long TIMEOUT_MS = 500;
+ private static final long TEST_TIME_MS = 123L;
private static final String SERVICE_NAME = "a_name";
private static final String SERVICE_TYPE = "_test._tcp";
private static final String SERVICE_FULL_NAME = SERVICE_NAME + "." + SERVICE_TYPE;
@@ -164,6 +169,8 @@
@Mock WifiManager mWifiManager;
@Mock WifiManager.MulticastLock mMulticastLock;
@Mock ActivityManager mActivityManager;
+ @Mock NetworkNsdReportedMetrics mMetrics;
+ @Mock MdnsUtils.Clock mClock;
SocketRequestMonitor mSocketRequestMonitor;
OnUidImportanceListener mUidImportanceListener;
HandlerThread mThread;
@@ -210,6 +217,9 @@
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());
+ doReturn(mMetrics).when(mDeps).makeNetworkNsdReportedMetrics(anyBoolean(), anyInt());
+ doReturn(mClock).when(mDeps).makeClock();
+ doReturn(TEST_TIME_MS).when(mClock).elapsedRealtime();
mService = makeService();
final ArgumentCaptor<SocketRequestMonitor> cbMonitorCaptor =
ArgumentCaptor.forClass(SocketRequestMonitor.class);
@@ -512,14 +522,16 @@
eq(SERVICE_NAME), eq(SERVICE_TYPE), eq(PORT), any(), eq(IFACE_IDX_ANY));
// Register service successfully.
+ final int regId = regIdCaptor.getValue();
final RegistrationInfo registrationInfo = new RegistrationInfo(
- regIdCaptor.getValue(),
+ regId,
IMDnsEventListener.SERVICE_REGISTERED,
SERVICE_NAME,
SERVICE_TYPE,
PORT,
new byte[0] /* txtRecord */,
IFACE_IDX_ANY);
+ doReturn(TEST_TIME_MS + 10L).when(mClock).elapsedRealtime();
eventListener.onServiceRegistrationStatus(registrationInfo);
final ArgumentCaptor<NsdServiceInfo> registeredInfoCaptor =
@@ -528,19 +540,22 @@
.onServiceRegistered(registeredInfoCaptor.capture());
final NsdServiceInfo registeredInfo = registeredInfoCaptor.getValue();
assertEquals(SERVICE_NAME, registeredInfo.getServiceName());
+ verify(mMetrics).reportServiceRegistrationSucceeded(regId, 10L /* durationMs */);
// Fail to register service.
final RegistrationInfo registrationFailedInfo = new RegistrationInfo(
- regIdCaptor.getValue(),
+ regId,
IMDnsEventListener.SERVICE_REGISTRATION_FAILED,
null /* serviceName */,
null /* registrationType */,
0 /* port */,
new byte[0] /* txtRecord */,
IFACE_IDX_ANY);
+ doReturn(TEST_TIME_MS + 20L).when(mClock).elapsedRealtime();
eventListener.onServiceRegistrationStatus(registrationFailedInfo);
verify(regListener, timeout(TIMEOUT_MS))
.onRegistrationFailed(any(), eq(FAILURE_INTERNAL_ERROR));
+ verify(mMetrics).reportServiceRegistrationFailed(regId, 20L /* durationMs */);
}
@Test
@@ -1215,17 +1230,22 @@
// Verify onServiceRegistered callback
final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
- cb.onRegisterServiceSucceeded(idCaptor.getValue(), regInfo);
+ final int regId = idCaptor.getValue();
+ doReturn(TEST_TIME_MS + 10L).when(mClock).elapsedRealtime();
+ cb.onRegisterServiceSucceeded(regId, regInfo);
verify(regListener, timeout(TIMEOUT_MS)).onServiceRegistered(argThat(info -> matches(info,
new NsdServiceInfo(regInfo.getServiceName(), null))));
+ verify(mMetrics).reportServiceRegistrationSucceeded(regId, 10L /* durationMs */);
+ doReturn(TEST_TIME_MS + 100L).when(mClock).elapsedRealtime();
client.unregisterService(regListener);
waitForIdle();
verify(mAdvertiser).removeService(idCaptor.getValue());
verify(regListener, timeout(TIMEOUT_MS)).onServiceUnregistered(
argThat(info -> matches(info, regInfo)));
verify(mSocketProvider, timeout(TIMEOUT_MS)).requestStopWhenInactive();
+ verify(mMetrics).reportServiceUnregistration(regId, 100L /* durationMs */);
}
@Test
@@ -1251,6 +1271,7 @@
verify(regListener, timeout(TIMEOUT_MS)).onRegistrationFailed(
argThat(info -> matches(info, regInfo)), eq(FAILURE_INTERNAL_ERROR));
+ verify(mMetrics).reportServiceRegistrationFailed(anyInt(), anyLong());
}
@Test
@@ -1280,10 +1301,13 @@
// Verify onServiceRegistered callback
final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
- cb.onRegisterServiceSucceeded(idCaptor.getValue(), regInfo);
+ final int regId = idCaptor.getValue();
+ doReturn(TEST_TIME_MS + 10L).when(mClock).elapsedRealtime();
+ cb.onRegisterServiceSucceeded(regId, regInfo);
verify(regListener, timeout(TIMEOUT_MS)).onServiceRegistered(
argThat(info -> matches(info, new NsdServiceInfo(regInfo.getServiceName(), null))));
+ verify(mMetrics).reportServiceRegistrationSucceeded(regId, 10L /* durationMs */);
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
index b30c9ce..b69b042 100644
--- a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
@@ -78,7 +78,6 @@
import androidx.annotation.NonNull;
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;
@@ -96,6 +95,7 @@
import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
+import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
@@ -128,7 +128,7 @@
@Mock AlarmManager mAlarmManager;
@Mock NetworkAgentInfo mNai;
@Mock SubscriptionManager mSubscriptionManager;
-
+ @Mock KeepaliveTracker.Dependencies mKeepaliveTrackerDeps;
KeepaliveStatsTracker mKeepaliveStatsTracker;
TestKeepaliveTracker mKeepaliveTracker;
AOOTestHandler mTestHandler;
@@ -267,7 +267,7 @@
TestKeepaliveTracker(@NonNull final Context context, @NonNull final Handler handler,
@NonNull final TcpKeepaliveController tcpController) {
- super(context, handler, tcpController, new Dependencies());
+ super(context, handler, tcpController, mKeepaliveTrackerDeps);
}
public void setReturnedKeepaliveInfo(@NonNull final KeepaliveInfo ki) {
@@ -336,8 +336,6 @@
anyInt() /* pid */, anyInt() /* uid */);
ConnectivityResources.setResourcesContextForTest(mCtx);
final Resources mockResources = mock(Resources.class);
- doReturn(new String[] { "0,3", "3,3" }).when(mockResources)
- .getStringArray(R.array.config_networkSupportedKeepaliveCount);
doReturn(mockResources).when(mCtx).getResources();
doReturn(mNetd).when(mDependencies).getNetd();
doReturn(mAlarmManager).when(mDependencies).getAlarmManager(any());
@@ -345,6 +343,10 @@
.getFwmarkForNetwork(TEST_NETID);
doNothing().when(mDependencies).sendRequest(any(), any());
+ doReturn(true).when(mKeepaliveTrackerDeps).isAddressTranslationEnabled(mCtx);
+ doReturn(new ConnectivityResources(mCtx)).when(mKeepaliveTrackerDeps)
+ .createConnectivityResources(mCtx);
+ doReturn(new int[] {3, 0, 0, 3}).when(mKeepaliveTrackerDeps).getSupportedKeepalives(mCtx);
mHandlerThread = new HandlerThread("KeepaliveTrackerTest");
mHandlerThread.start();
@@ -408,22 +410,22 @@
@Test
public void testIsAnyTcpSocketConnected_withTargetNetId() throws Exception {
setupResponseWithSocketExisting();
- mTestHandler.post(
- () -> assertTrue(mAOOKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID)));
+ assertTrue(visibleOnHandlerThread(mTestHandler,
+ () -> mAOOKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID)));
}
@Test
public void testIsAnyTcpSocketConnected_withIncorrectNetId() throws Exception {
setupResponseWithSocketExisting();
- mTestHandler.post(
- () -> assertFalse(mAOOKeepaliveTracker.isAnyTcpSocketConnected(OTHER_NETID)));
+ assertFalse(visibleOnHandlerThread(mTestHandler,
+ () -> mAOOKeepaliveTracker.isAnyTcpSocketConnected(OTHER_NETID)));
}
@Test
public void testIsAnyTcpSocketConnected_noSocketExists() throws Exception {
setupResponseWithoutSocketExisting();
- mTestHandler.post(
- () -> assertFalse(mAOOKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID)));
+ assertFalse(visibleOnHandlerThread(mTestHandler,
+ () -> mAOOKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID)));
}
private void triggerEventKeepalive(int slot, int reason) {
@@ -500,9 +502,7 @@
final AlarmManager.OnAlarmListener listener = listenerCaptor.getValue();
// For realism, the listener should be posted on the handler
- mTestHandler.post(() -> listener.onAlarm());
- // Wait for the listener to be called. The listener enqueues a message to the handler.
- HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
+ visibleOnHandlerThread(mTestHandler, () -> listener.onAlarm());
// Wait for the message posted by the listener to be processed.
HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
@@ -525,8 +525,7 @@
doReturn(METRICS_COLLECTION_DURATION_MS).when(mDependencies).getElapsedRealtime();
// For realism, the listener should be posted on the handler
- mTestHandler.post(() -> listener.onAlarm());
- HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
+ visibleOnHandlerThread(mTestHandler, () -> listener.onAlarm());
verify(mKeepaliveStatsTracker).writeAndResetMetrics();
// Alarm is rescheduled.
@@ -662,6 +661,25 @@
}
@Test
+ public void testStartNattKeepalive_addressTranslationOnClatNotSupported() throws Exception {
+ // Disable address translation feature and verify the behavior
+ doReturn(false).when(mKeepaliveTrackerDeps).isAddressTranslationEnabled(mCtx);
+
+ setupTestNaiForClat(InetAddresses.parseNumericAddress("2001:db8::1"),
+ InetAddresses.parseNumericAddress("2001:db8::2"));
+
+ doStartNattKeepalive();
+ final ArgumentCaptor<NattKeepalivePacketData> kpdCaptor =
+ ArgumentCaptor.forClass(NattKeepalivePacketData.class);
+ verify(mNai).onStartNattSocketKeepalive(
+ eq(TEST_SLOT), eq(TEST_KEEPALIVE_INTERVAL_SEC), kpdCaptor.capture());
+ // Verify that address translation is not triggered so the addresses are still v4.
+ final NattKeepalivePacketData kpd = kpdCaptor.getValue();
+ assertTrue(kpd.getSrcAddress() instanceof Inet4Address);
+ assertTrue(kpd.getDstAddress() instanceof Inet4Address);
+ }
+
+ @Test
public void testStartNattKeepalive_addressTranslationOnClat() throws Exception {
final InetAddress v6AddrSrc = InetAddresses.parseNumericAddress("2001:db8::1");
final InetAddress v6AddrDst = InetAddresses.parseNumericAddress("2001:db8::2");
diff --git a/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java b/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
index 0d1b548..fa703eb 100644
--- a/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
@@ -25,10 +25,13 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
@@ -118,16 +121,25 @@
@Mock private KeepaliveStatsTracker.Dependencies mDependencies;
@Mock private SubscriptionManager mSubscriptionManager;
- private void triggerBroadcastDefaultSubId(int subId) {
+ private BroadcastReceiver getBroadcastReceiver() {
final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
- verify(mContext).registerReceiver(receiverCaptor.capture(), /* filter= */ any(),
- /* broadcastPermission= */ any(), eq(mTestHandler));
+ verify(mContext).registerReceiver(
+ receiverCaptor.capture(),
+ argThat(intentFilter -> intentFilter.matchAction(
+ SubscriptionManager.ACTION_DEFAULT_SUBSCRIPTION_CHANGED)),
+ /* broadcastPermission= */ any(),
+ eq(mTestHandler));
+
+ return receiverCaptor.getValue();
+ }
+
+ private void triggerBroadcastDefaultSubId(int subId) {
final Intent intent =
- new Intent(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED);
+ new Intent(SubscriptionManager.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
- receiverCaptor.getValue().onReceive(mContext, intent);
+ getBroadcastReceiver().onReceive(mContext, intent);
}
private OnSubscriptionsChangedListener getOnSubscriptionsChangedListener() {
@@ -433,6 +445,24 @@
assertCarrierLifetimeMetrics(expectKeepaliveCarrierStatsArray, actualCarrierLifetime);
}
+ // The KeepaliveStatsTracker will be disabled when an error occurs with the keepalive states.
+ // Most tests should assert that the tracker is still active to ensure no errors occurred.
+ private void assertKeepaliveStatsTrackerActive() {
+ assertTrue(mKeepaliveStatsTracker.isEnabled());
+ }
+
+ private void assertKeepaliveStatsTrackerDisabled() {
+ assertFalse(mKeepaliveStatsTracker.isEnabled());
+
+ final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener();
+ // BackgroundThread will remove the OnSubscriptionsChangedListener.
+ HandlerUtils.waitForIdle(BackgroundThread.getHandler(), TIMEOUT_MS);
+ verify(mSubscriptionManager).removeOnSubscriptionsChangedListener(listener);
+
+ final BroadcastReceiver receiver = getBroadcastReceiver();
+ verify(mContext).unregisterReceiver(receiver);
+ }
+
@Test
public void testNoKeepalive() {
final int writeTime = 5000;
@@ -452,6 +482,7 @@
expectRegisteredDurations,
expectActiveDurations,
new KeepaliveCarrierStats[0]);
+ assertKeepaliveStatsTrackerActive();
}
/*
@@ -485,6 +516,7 @@
new KeepaliveCarrierStats[] {
getDefaultCarrierStats(expectRegisteredDurations[1], expectActiveDurations[1])
});
+ assertKeepaliveStatsTrackerActive();
}
/*
@@ -523,6 +555,7 @@
new KeepaliveCarrierStats[] {
getDefaultCarrierStats(expectRegisteredDurations[1], expectActiveDurations[1])
});
+ assertKeepaliveStatsTrackerActive();
}
/*
@@ -567,6 +600,7 @@
new KeepaliveCarrierStats[] {
getDefaultCarrierStats(expectRegisteredDurations[1], expectActiveDurations[1])
});
+ assertKeepaliveStatsTrackerActive();
}
/*
@@ -615,6 +649,7 @@
new KeepaliveCarrierStats[] {
getDefaultCarrierStats(expectRegisteredDurations[1], expectActiveDurations[1])
});
+ assertKeepaliveStatsTrackerActive();
}
/*
@@ -657,6 +692,7 @@
new KeepaliveCarrierStats[] {
getDefaultCarrierStats(expectRegisteredDurations[1], expectActiveDurations[1])
});
+ assertKeepaliveStatsTrackerActive();
}
/*
@@ -708,6 +744,7 @@
new KeepaliveCarrierStats[] {
getDefaultCarrierStats(expectRegisteredDurations[1], expectActiveDurations[1])
});
+ assertKeepaliveStatsTrackerActive();
}
/*
@@ -788,6 +825,7 @@
expectRegisteredDurations[1] + 2 * expectRegisteredDurations[2],
expectActiveDurations[1] + 2 * expectActiveDurations[2])
});
+ assertKeepaliveStatsTrackerActive();
}
/*
@@ -857,6 +895,7 @@
new KeepaliveCarrierStats[] {
getDefaultCarrierStats(expectRegisteredDurations2[1], expectActiveDurations2[1])
});
+ assertKeepaliveStatsTrackerActive();
}
/*
@@ -946,6 +985,7 @@
expectRegisteredDurations2,
expectActiveDurations2,
new KeepaliveCarrierStats[] {expectKeepaliveCarrierStats3});
+ assertKeepaliveStatsTrackerActive();
}
@Test
@@ -957,7 +997,10 @@
onStartKeepalive(startTime1, TEST_SLOT);
// Attempt to use the same (network, slot)
- assertThrows(IllegalArgumentException.class, () -> onStartKeepalive(startTime2, TEST_SLOT));
+ onStartKeepalive(startTime2, TEST_SLOT);
+ // Starting a 2nd keepalive on the same slot is unexpected and an error so the stats tracker
+ // is disabled.
+ assertKeepaliveStatsTrackerDisabled();
final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
buildKeepaliveMetrics(writeTime);
@@ -1018,6 +1061,7 @@
new KeepaliveCarrierStats[] {
getDefaultCarrierStats(expectRegisteredDurations[1], expectActiveDurations[1])
});
+ assertKeepaliveStatsTrackerActive();
}
@Test
@@ -1065,6 +1109,7 @@
new KeepaliveCarrierStats[] {
expectKeepaliveCarrierStats1, expectKeepaliveCarrierStats2
});
+ assertKeepaliveStatsTrackerActive();
}
@Test
@@ -1106,6 +1151,7 @@
/* expectRegisteredDurations= */ new int[] {startTime, writeTime - startTime},
/* expectActiveDurations= */ new int[] {startTime, writeTime - startTime},
new KeepaliveCarrierStats[] {expectKeepaliveCarrierStats});
+ assertKeepaliveStatsTrackerActive();
}
@Test
@@ -1148,6 +1194,7 @@
writeTime * 3 - startTime1 - startTime2 - startTime3,
writeTime * 3 - startTime1 - startTime2 - startTime3)
});
+ assertKeepaliveStatsTrackerActive();
}
@Test
@@ -1197,6 +1244,7 @@
new KeepaliveCarrierStats[] {
expectKeepaliveCarrierStats1, expectKeepaliveCarrierStats2
});
+ assertKeepaliveStatsTrackerActive();
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 967083e..b319c30 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -50,6 +50,7 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
@@ -60,6 +61,7 @@
import android.telephony.TelephonyManager;
import android.testing.PollingCheck;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -428,6 +430,22 @@
// UiDevice.getLauncherPackageName() requires the test manifest to have a <queries> tag for
// the launcher intent.
+ // Attempted workaround for b/286550950 where Settings is reported as the launcher
+ PollingCheck.check(
+ "Launcher package name was still settings after " + TEST_TIMEOUT_MS + "ms",
+ TEST_TIMEOUT_MS,
+ () -> {
+ if ("com.android.settings".equals(uiDevice.getLauncherPackageName())) {
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_HOME);
+ final List<ResolveInfo> acts = ctx.getPackageManager()
+ .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ Log.e(NetworkNotificationManagerTest.class.getSimpleName(),
+ "Got settings as launcher name; launcher activities: " + acts);
+ return false;
+ }
+ return true;
+ });
final String launcherPackageName = uiDevice.getLauncherPackageName();
assertTrue(String.format("Launcher (%s) is not shown", launcherPackageName),
uiDevice.wait(Until.hasObject(By.pkg(launcherPackageName)),
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index cf02e3a..8dcfffa 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -55,6 +55,7 @@
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.intThat;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
@@ -126,12 +127,14 @@
private static final int MOCK_APPID1 = 10001;
private static final int MOCK_APPID2 = 10086;
private static final int MOCK_APPID3 = 10110;
+ private static final int MOCK_APPID4 = 10111;
private static final int SYSTEM_APPID1 = 1100;
private static final int SYSTEM_APPID2 = 1108;
private static final int VPN_APPID = 10002;
private static final int MOCK_UID11 = MOCK_USER1.getUid(MOCK_APPID1);
private static final int MOCK_UID12 = MOCK_USER1.getUid(MOCK_APPID2);
private static final int MOCK_UID13 = MOCK_USER1.getUid(MOCK_APPID3);
+ private static final int MOCK_UID14 = MOCK_USER1.getUid(MOCK_APPID4);
private static final int SYSTEM_APP_UID11 = MOCK_USER1.getUid(SYSTEM_APPID1);
private static final int VPN_UID = MOCK_USER1.getUid(VPN_APPID);
private static final int MOCK_UID21 = MOCK_USER2.getUid(MOCK_APPID1);
@@ -965,6 +968,66 @@
}
@Test
+ public void testLockdownUidFilteringWithLockdownEnableDisableWithMultiAddAndOverlap() {
+ doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
+ CONNECTIVITY_USE_RESTRICTED_NETWORKS),
+ buildPackageInfo(MOCK_PACKAGE1, MOCK_UID13),
+ buildPackageInfo(MOCK_PACKAGE2, MOCK_UID14),
+ buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
+ .when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
+ startMonitoring();
+ // MOCK_UID13 is subject to the VPN.
+ final UidRange range1 = new UidRange(MOCK_UID13, MOCK_UID13);
+ final UidRange[] lockdownRange1 = {range1};
+
+ // Add Lockdown uid range at 1st time, expect a rule to be set up
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, lockdownRange1);
+ verify(mBpfNetMaps).updateUidLockdownRule(anyInt(), eq(true) /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID13, true /* add */);
+
+ reset(mBpfNetMaps);
+
+ // MOCK_UID13 and MOCK_UID14 are sequential and subject to the VPN in a separate range.
+ final UidRange range2 = new UidRange(MOCK_UID13, MOCK_UID14);
+ final UidRange[] lockdownRange2 = {range2};
+
+ // Add overlapping multiple-UID range. Rule may be set again, which is functionally
+ // a no-op, so it is fine.
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, lockdownRange2);
+ verify(mBpfNetMaps, atLeast(1)).updateUidLockdownRule(anyInt(), eq(true) /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID14, true /* add */);
+
+ reset(mBpfNetMaps);
+
+ // Remove the multiple-UID range. UID from first rule should not be removed.
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, lockdownRange2);
+ verify(mBpfNetMaps, times(1)).updateUidLockdownRule(anyInt(), eq(false) /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID14, false /* add */);
+
+ reset(mBpfNetMaps);
+
+ // Add the multiple-UID range back again to be able to test removing the first range, too.
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, lockdownRange2);
+ verify(mBpfNetMaps, atLeast(1)).updateUidLockdownRule(anyInt(), eq(true) /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID14, true /* add */);
+
+ reset(mBpfNetMaps);
+
+ // Remove the single-UID range. The rule for MOCK_UID11 should not change because it is
+ // still covered by the second, multiple-UID range rule.
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, lockdownRange1);
+ verify(mBpfNetMaps, never()).updateUidLockdownRule(anyInt(), anyBoolean());
+
+ reset(mBpfNetMaps);
+
+ // Remove the multiple-UID range. Expect both UID rules to be torn down.
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, lockdownRange2);
+ verify(mBpfNetMaps, times(2)).updateUidLockdownRule(anyInt(), eq(false) /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID13, false /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID14, false /* add */);
+ }
+
+ @Test
public void testLockdownUidFilteringWithLockdownEnableDisableWithDuplicates() {
doReturn(List.of(
buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 9ae727d..385f831 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -1345,7 +1345,8 @@
final ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
final int verifyTimes = profileState.length;
- verify(userContext, times(verifyTimes)).startService(intentArgumentCaptor.capture());
+ verify(userContext, timeout(TEST_TIMEOUT_MS).times(verifyTimes))
+ .startService(intentArgumentCaptor.capture());
for (int i = 0; i < verifyTimes; i++) {
final Intent intent = intentArgumentCaptor.getAllValues().get(i);
@@ -1657,7 +1658,7 @@
verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
} else {
final IkeSessionCallback ikeCb = captor.getValue();
- ikeCb.onClosedWithException(exception);
+ mExecutor.execute(() -> ikeCb.onClosedWithException(exception));
}
verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
@@ -1676,7 +1677,7 @@
int retryIndex = 0;
final IkeSessionCallback ikeCb2 = verifyRetryAndGetNewIkeCb(retryIndex++);
- ikeCb2.onClosedWithException(exception);
+ mExecutor.execute(() -> ikeCb2.onClosedWithException(exception));
verifyRetryAndGetNewIkeCb(retryIndex++);
}
}
@@ -1687,11 +1688,8 @@
// Verify retry is scheduled
final long expectedDelayMs = mTestDeps.getNextRetryDelayMs(retryIndex);
- final ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
- verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), delayCaptor.capture(),
- eq(TimeUnit.MILLISECONDS));
- final List<Long> delays = delayCaptor.getAllValues();
- assertEquals(expectedDelayMs, (long) delays.get(delays.size() - 1));
+ verify(mExecutor, timeout(TEST_TIMEOUT_MS)).schedule(any(Runnable.class),
+ eq(expectedDelayMs), eq(TimeUnit.MILLISECONDS));
verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS + expectedDelayMs))
.createIkeSession(any(), any(), any(), any(), ikeCbCaptor.capture(), any());
@@ -2467,7 +2465,8 @@
if (expectedReadFromCarrierConfig) {
final ArgumentCaptor<NetworkCapabilities> ncCaptor =
ArgumentCaptor.forClass(NetworkCapabilities.class);
- verify(mMockNetworkAgent).doSendNetworkCapabilities(ncCaptor.capture());
+ verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS))
+ .doSendNetworkCapabilities(ncCaptor.capture());
final VpnTransportInfo info =
(VpnTransportInfo) ncCaptor.getValue().getTransportInfo();
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManagerTests.java b/tests/unit/java/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManagerTests.java
index 8fb7be1..bb59e0d 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManagerTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManagerTests.java
@@ -31,6 +31,7 @@
import android.net.Network;
import android.net.NetworkRequest;
+import com.android.net.module.util.SharedLog;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -49,6 +50,7 @@
@Mock private Context mContext;
@Mock private ConnectivityMonitor.Listener mockListener;
@Mock private ConnectivityManager mConnectivityManager;
+ @Mock private SharedLog sharedLog;
private ConnectivityMonitorWithConnectivityManager monitor;
@@ -57,7 +59,7 @@
MockitoAnnotations.initMocks(this);
doReturn(mConnectivityManager).when(mContext)
.getSystemService(Context.CONNECTIVITY_SERVICE);
- monitor = new ConnectivityMonitorWithConnectivityManager(mContext, mockListener);
+ monitor = new ConnectivityMonitorWithConnectivityManager(mContext, mockListener, sharedLog);
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
index 6a0334f..9b38fea 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
@@ -20,6 +20,8 @@
import android.net.LinkAddress
import android.net.Network
import android.net.nsd.NsdServiceInfo
+import android.net.nsd.OffloadEngine
+import android.net.nsd.OffloadServiceInfo
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
@@ -60,6 +62,8 @@
private val TEST_SOCKETKEY_2 = SocketKey(1002 /* interfaceIndex */)
private val TEST_HOSTNAME = arrayOf("Android_test", "local")
private const val TEST_SUBTYPE = "_subtype"
+private val TEST_INTERFACE1 = "test_iface1"
+private val TEST_INTERFACE2 = "test_iface2"
private val SERVICE_1 = NsdServiceInfo("TestServiceName", "_advertisertest._tcp").apply {
port = 12345
@@ -94,6 +98,24 @@
network = null
}
+private val OFFLOAD_SERVICEINFO = OffloadServiceInfo(
+ OffloadServiceInfo.Key("TestServiceName", "_advertisertest._tcp"),
+ listOf(TEST_SUBTYPE),
+ "Android_test.local",
+ null, /* rawOffloadPacket */
+ 0, /* priority */
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+)
+
+private val OFFLOAD_SERVICEINFO_NO_SUBTYPE = OffloadServiceInfo(
+ OffloadServiceInfo.Key("TestServiceName", "_advertisertest._tcp"),
+ listOf(),
+ "Android_test.local",
+ null, /* rawOffloadPacket */
+ 0, /* priority */
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+)
+
@RunWith(DevSdkIgnoreRunner::class)
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
class MdnsAdvertiserTest {
@@ -123,6 +145,8 @@
doReturn(true).`when`(mockInterfaceAdvertiser2).isProbing(anyInt())
doReturn(createEmptyNetworkInterface()).`when`(mockSocket1).getInterface()
doReturn(createEmptyNetworkInterface()).`when`(mockSocket2).getInterface()
+ doReturn(TEST_INTERFACE1).`when`(mockInterfaceAdvertiser1).socketInterfaceName
+ doReturn(TEST_INTERFACE2).`when`(mockInterfaceAdvertiser2).socketInterfaceName
}
@After
@@ -160,12 +184,15 @@
)
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
- postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded(
+ postSync { intAdvCbCaptor.value.onServiceProbingSucceeded(
mockInterfaceAdvertiser1, SERVICE_ID_1) }
verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1), argThat { it.matches(SERVICE_1) })
+ verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE))
postSync { socketCb.onInterfaceDestroyed(TEST_SOCKETKEY_1, mockSocket1) }
verify(mockInterfaceAdvertiser1).destroyNow()
+ postSync { intAdvCbCaptor.value.onDestroyed(mockSocket1) }
+ verify(cb).onOffloadStop(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE))
}
@Test
@@ -195,14 +222,16 @@
anyInt(), eq(ALL_NETWORKS_SERVICE), eq(TEST_SUBTYPE))
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
- postSync { intAdvCbCaptor1.value.onRegisterServiceSucceeded(
+ postSync { intAdvCbCaptor1.value.onServiceProbingSucceeded(
mockInterfaceAdvertiser1, SERVICE_ID_1) }
+ verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO))
// Need both advertisers to finish probing and call onRegisterServiceSucceeded
verify(cb, never()).onRegisterServiceSucceeded(anyInt(), any())
doReturn(false).`when`(mockInterfaceAdvertiser2).isProbing(SERVICE_ID_1)
- postSync { intAdvCbCaptor2.value.onRegisterServiceSucceeded(
+ postSync { intAdvCbCaptor2.value.onServiceProbingSucceeded(
mockInterfaceAdvertiser2, SERVICE_ID_1) }
+ verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE2), eq(OFFLOAD_SERVICEINFO))
verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1),
argThat { it.matches(ALL_NETWORKS_SERVICE) })
@@ -210,6 +239,8 @@
postSync { advertiser.removeService(SERVICE_ID_1) }
verify(mockInterfaceAdvertiser1).removeService(SERVICE_ID_1)
verify(mockInterfaceAdvertiser2).removeService(SERVICE_ID_1)
+ verify(cb).onOffloadStop(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO))
+ verify(cb).onOffloadStop(eq(TEST_INTERFACE2), eq(OFFLOAD_SERVICEINFO))
// Interface advertisers call onDestroyed after sending exit announcements
postSync { intAdvCbCaptor1.value.onDestroyed(mockSocket1) }
@@ -285,12 +316,12 @@
argThat { it.matches(expectedCaseInsensitiveRenamed) }, eq(null))
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
- postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded(
+ postSync { intAdvCbCaptor.value.onServiceProbingSucceeded(
mockInterfaceAdvertiser1, SERVICE_ID_1) }
verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1), argThat { it.matches(SERVICE_1) })
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_2)
- postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded(
+ postSync { intAdvCbCaptor.value.onServiceProbingSucceeded(
mockInterfaceAdvertiser1, SERVICE_ID_2) }
verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_2),
argThat { it.matches(expectedRenamed) })
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
index 7c6cb3e..12faa50 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
@@ -21,6 +21,7 @@
import android.os.HandlerThread
import android.os.SystemClock
import com.android.internal.util.HexDump
+import com.android.net.module.util.SharedLog
import com.android.server.connectivity.mdns.MdnsAnnouncer.AnnouncementInfo
import com.android.server.connectivity.mdns.MdnsAnnouncer.BaseAnnouncementInfo
import com.android.server.connectivity.mdns.MdnsRecordRepository.getReverseDnsAddress
@@ -52,6 +53,7 @@
private val thread = HandlerThread(MdnsAnnouncerTest::class.simpleName)
private val socket = mock(MdnsInterfaceSocket::class.java)
+ private val sharedLog = mock(SharedLog::class.java)
private val buffer = ByteArray(1500)
@Before
@@ -80,11 +82,11 @@
@Test
fun testAnnounce() {
- val replySender = MdnsReplySender("testiface", thread.looper, socket, buffer)
+ val replySender = MdnsReplySender( thread.looper, socket, buffer, sharedLog)
@Suppress("UNCHECKED_CAST")
val cb = mock(MdnsPacketRepeater.PacketRepeaterCallback::class.java)
as MdnsPacketRepeater.PacketRepeaterCallback<BaseAnnouncementInfo>
- val announcer = MdnsAnnouncer("testiface", thread.looper, replySender, cb)
+ val announcer = MdnsAnnouncer(thread.looper, replySender, cb, sharedLog)
/*
The expected packet replicates records announced when registering a service, as observed in
the legacy mDNS implementation (some ordering differs to be more readable).
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
index 1a4ae5d..e869b91 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
@@ -53,6 +53,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
/** Tests for {@link MdnsDiscoveryManager}. */
@RunWith(DevSdkIgnoreRunner.class)
@@ -80,6 +81,7 @@
private static final Pair<String, SocketKey> PER_SOCKET_SERVICE_TYPE_2_NETWORK_2 =
Pair.create(SERVICE_TYPE_2, SOCKET_KEY_NETWORK_2);
@Mock private ExecutorProvider executorProvider;
+ @Mock private ScheduledExecutorService mockExecutorService;
@Mock private MdnsSocketClientBase socketClient;
@Mock private MdnsServiceTypeClient mockServiceTypeClientType1NullNetwork;
@Mock private MdnsServiceTypeClient mockServiceTypeClientType1Network1;
@@ -128,6 +130,7 @@
return null;
}
};
+ doReturn(mockExecutorService).when(mockServiceTypeClientType1NullNetwork).getExecutor();
}
@After
@@ -165,11 +168,25 @@
when(mockServiceTypeClientType1NullNetwork.stopSendAndReceive(mockListenerOne))
.thenReturn(true);
runOnHandler(() -> discoveryManager.unregisterListener(SERVICE_TYPE_1, mockListenerOne));
+ verify(executorProvider).shutdownExecutorService(mockExecutorService);
verify(mockServiceTypeClientType1NullNetwork).stopSendAndReceive(mockListenerOne);
verify(socketClient).stopDiscovery();
}
@Test
+ public void onSocketDestroy_shutdownExecutorService() throws IOException {
+ final MdnsSearchOptions options =
+ MdnsSearchOptions.newBuilder().setNetwork(null /* network */).build();
+ final SocketCreationCallback callback = expectSocketCreationCallback(
+ SERVICE_TYPE_1, mockListenerOne, options);
+ runOnHandler(() -> callback.onSocketCreated(SOCKET_KEY_NULL_NETWORK));
+ verify(mockServiceTypeClientType1NullNetwork).startSendAndReceive(mockListenerOne, options);
+
+ runOnHandler(() -> callback.onSocketDestroyed(SOCKET_KEY_NULL_NETWORK));
+ verify(executorProvider).shutdownExecutorService(mockExecutorService);
+ }
+
+ @Test
public void registerMultipleListeners() throws IOException {
final MdnsSearchOptions options =
MdnsSearchOptions.newBuilder().setNetwork(null /* network */).build();
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
index dd458b8..c19747e 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
@@ -108,9 +108,9 @@
doReturn(repository).`when`(deps).makeRecordRepository(any(),
eq(TEST_HOSTNAME)
)
- doReturn(replySender).`when`(deps).makeReplySender(anyString(), any(), any(), any())
- doReturn(announcer).`when`(deps).makeMdnsAnnouncer(anyString(), any(), any(), any())
- doReturn(prober).`when`(deps).makeMdnsProber(anyString(), any(), any(), any())
+ doReturn(replySender).`when`(deps).makeReplySender(anyString(), any(), any(), any(), any())
+ doReturn(announcer).`when`(deps).makeMdnsAnnouncer(anyString(), any(), any(), any(), any())
+ doReturn(prober).`when`(deps).makeMdnsProber(anyString(), any(), any(), any(), any())
val knownServices = mutableSetOf<Int>()
doAnswer { inv ->
@@ -132,8 +132,8 @@
advertiser.start()
verify(socket).addPacketHandler(packetHandlerCaptor.capture())
- verify(deps).makeMdnsProber(any(), any(), any(), probeCbCaptor.capture())
- verify(deps).makeMdnsAnnouncer(any(), any(), any(), announceCbCaptor.capture())
+ verify(deps).makeMdnsProber(any(), any(), any(), probeCbCaptor.capture(), any())
+ verify(deps).makeMdnsAnnouncer(any(), any(), any(), announceCbCaptor.capture(), any())
}
@After
@@ -150,7 +150,7 @@
0L /* initialDelayMs */)
thread.waitForIdle(TIMEOUT_MS)
- verify(cb).onRegisterServiceSucceeded(advertiser, TEST_SERVICE_ID_1)
+ verify(cb).onServiceProbingSucceeded(advertiser, TEST_SERVICE_ID_1)
// Remove the service: expect exit announcements
val testExitInfo = mock(ExitAnnouncementInfo::class.java)
@@ -256,7 +256,7 @@
val mockProbingInfo = mock(ProbingInfo::class.java)
doReturn(mockProbingInfo).`when`(repository).setServiceProbing(TEST_SERVICE_ID_1)
- advertiser.restartProbingForConflict(TEST_SERVICE_ID_1)
+ advertiser.maybeRestartProbingForConflict(TEST_SERVICE_ID_1)
verify(prober).restartForConflict(mockProbingInfo)
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
index 29de272..3701b0c 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
@@ -36,6 +36,7 @@
import android.os.HandlerThread;
import com.android.net.module.util.HexDump;
+import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.MdnsSocketClientBase.SocketCreationCallback;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -66,6 +67,7 @@
@Mock private MdnsServiceBrowserListener mListener;
@Mock private MdnsSocketClientBase.Callback mCallback;
@Mock private SocketCreationCallback mSocketCreationCallback;
+ @Mock private SharedLog mSharedLog;
private MdnsMultinetworkSocketClient mSocketClient;
private Handler mHandler;
private SocketKey mSocketKey;
@@ -78,7 +80,7 @@
thread.start();
mHandler = new Handler(thread.getLooper());
mSocketKey = new SocketKey(1000 /* interfaceIndex */);
- mSocketClient = new MdnsMultinetworkSocketClient(thread.getLooper(), mProvider);
+ mSocketClient = new MdnsMultinetworkSocketClient(thread.getLooper(), mProvider, mSharedLog);
mHandler.post(() -> mSocketClient.setCallback(mCallback));
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
index 0a8d78d..5ca4dd6 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
@@ -21,6 +21,7 @@
import android.os.HandlerThread
import android.os.Looper
import com.android.internal.util.HexDump
+import com.android.net.module.util.SharedLog
import com.android.server.connectivity.mdns.MdnsProber.ProbingInfo
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
@@ -55,6 +56,7 @@
class MdnsProberTest {
private val thread = HandlerThread(MdnsProberTest::class.simpleName)
private val socket = mock(MdnsInterfaceSocket::class.java)
+ private val sharedLog = mock(SharedLog::class.java)
@Suppress("UNCHECKED_CAST")
private val cb = mock(MdnsPacketRepeater.PacketRepeaterCallback::class.java)
as MdnsPacketRepeater.PacketRepeaterCallback<ProbingInfo>
@@ -82,8 +84,9 @@
private class TestProber(
looper: Looper,
replySender: MdnsReplySender,
- cb: PacketRepeaterCallback<ProbingInfo>
- ) : MdnsProber("testiface", looper, replySender, cb) {
+ cb: PacketRepeaterCallback<ProbingInfo>,
+ sharedLog: SharedLog
+ ) : MdnsProber(looper, replySender, cb, sharedLog) {
override fun getInitialDelay() = 0L
}
@@ -116,8 +119,8 @@
@Test
fun testProbe() {
- val replySender = MdnsReplySender("testiface", thread.looper, socket, buffer)
- val prober = TestProber(thread.looper, replySender, cb)
+ val replySender = MdnsReplySender(thread.looper, socket, buffer, sharedLog)
+ val prober = TestProber(thread.looper, replySender, cb, sharedLog)
val probeInfo = TestProbeInfo(
listOf(makeServiceRecord(TEST_SERVICE_NAME_1, 37890)))
prober.startProbing(probeInfo)
@@ -140,8 +143,8 @@
@Test
fun testProbeMultipleRecords() {
- val replySender = MdnsReplySender("testiface", thread.looper, socket, buffer)
- val prober = TestProber(thread.looper, replySender, cb)
+ val replySender = MdnsReplySender(thread.looper, socket, buffer, sharedLog)
+ val prober = TestProber(thread.looper, replySender, cb, sharedLog)
val probeInfo = TestProbeInfo(listOf(
makeServiceRecord(TEST_SERVICE_NAME_1, 37890),
makeServiceRecord(TEST_SERVICE_NAME_2, 37891),
@@ -178,8 +181,8 @@
@Test
fun testStopProbing() {
- val replySender = MdnsReplySender("testiface", thread.looper, socket, buffer)
- val prober = TestProber(thread.looper, replySender, cb)
+ val replySender = MdnsReplySender(thread.looper, socket, buffer, sharedLog)
+ val prober = TestProber(thread.looper, replySender, cb, sharedLog)
val probeInfo = TestProbeInfo(
listOf(makeServiceRecord(TEST_SERVICE_NAME_1, 37890)),
// delayMs is the delay between each probe, so does not apply to the first one
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
index 05eca84..d71bea4 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
@@ -18,7 +18,7 @@
import static android.net.InetAddresses.parseNumericAddress;
-import static com.android.server.connectivity.mdns.MdnsResponseDecoder.Clock;
+import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertArrayEquals;
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index ad5583b..1fdfe09 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -52,7 +52,7 @@
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
-import com.android.server.connectivity.mdns.MdnsServiceTypeClient.QueryTaskConfig;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
@@ -92,6 +92,7 @@
private static final int INTERFACE_INDEX = 999;
private static final long DEFAULT_TIMEOUT = 2000L;
private static final String SERVICE_TYPE = "_googlecast._tcp.local";
+ private static final String SUBTYPE = "_subtype";
private static final String[] SERVICE_TYPE_LABELS = TextUtils.split(SERVICE_TYPE, "\\.");
private static final InetSocketAddress IPV4_ADDRESS = new InetSocketAddress(
MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT);
@@ -113,7 +114,7 @@
@Mock
private Network mockNetwork;
@Mock
- private MdnsResponseDecoder.Clock mockDecoderClock;
+ private MdnsUtils.Clock mockDecoderClock;
@Mock
private SharedLog mockSharedLog;
@Mock
@@ -131,6 +132,7 @@
private SocketKey socketKey;
private HandlerThread thread;
private Handler handler;
+ private MdnsServiceCache serviceCache;
private long latestDelayMs = 0;
private Message delayMessage = null;
private Handler realHandler = null;
@@ -190,6 +192,7 @@
thread = new HandlerThread("MdnsServiceTypeClientTests");
thread.start();
handler = new Handler(thread.getLooper());
+ serviceCache = new MdnsServiceCache(thread.getLooper());
doAnswer(inv -> {
latestDelayMs = 0;
@@ -204,9 +207,17 @@
return true;
}).when(mockDeps).sendMessageDelayed(any(Handler.class), any(Message.class), anyLong());
+ doAnswer(inv -> {
+ final Handler handler = (Handler) inv.getArguments()[0];
+ final Message message = (Message) inv.getArguments()[1];
+ runOnHandler(() -> handler.dispatchMessage(message));
+ return true;
+ }).when(mockDeps).sendMessage(any(Handler.class), any(Message.class));
+
client =
new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps) {
+ mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
+ serviceCache) {
@Override
MdnsPacketWriter createMdnsPacketWriter() {
return mockPacketWriter;
@@ -251,7 +262,7 @@
@Test
public void sendQueries_activeScanMode() {
MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(false).build();
+ MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(false).build();
startSendAndReceive(mockListenerOne, searchOptions);
// Always try to remove the task.
verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
@@ -303,7 +314,7 @@
@Test
public void sendQueries_reentry_activeScanMode() {
MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(false).build();
+ MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(false).build();
startSendAndReceive(mockListenerOne, searchOptions);
// Always try to remove the task.
verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
@@ -314,8 +325,8 @@
// After the first query is sent, change the subtypes, and restart.
searchOptions =
MdnsSearchOptions.newBuilder()
- .addSubtype("12345")
- .addSubtype("abcde")
+ .addSubtype(SUBTYPE)
+ .addSubtype("_subtype2")
.setIsPassiveMode(false)
.build();
startSendAndReceive(mockListenerOne, searchOptions);
@@ -337,7 +348,7 @@
@Test
public void sendQueries_passiveScanMode() {
MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(true).build();
+ MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(true).build();
startSendAndReceive(mockListenerOne, searchOptions);
// Always try to remove the task.
verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
@@ -363,7 +374,7 @@
@Test
public void sendQueries_activeScanWithQueryBackoff() {
MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(
+ MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(
false).setNumOfQueriesBeforeBackoff(11).build();
startSendAndReceive(mockListenerOne, searchOptions);
// Always try to remove the task.
@@ -422,7 +433,7 @@
@Test
public void sendQueries_passiveScanWithQueryBackoff() {
MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(
+ MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(
true).setNumOfQueriesBeforeBackoff(3).build();
startSendAndReceive(mockListenerOne, searchOptions);
// Always try to remove the task.
@@ -481,7 +492,7 @@
@Test
public void sendQueries_reentry_passiveScanMode() {
MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(true).build();
+ MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(true).build();
startSendAndReceive(mockListenerOne, searchOptions);
// Always try to remove the task.
verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
@@ -492,8 +503,8 @@
// After the first query is sent, change the subtypes, and restart.
searchOptions =
MdnsSearchOptions.newBuilder()
- .addSubtype("12345")
- .addSubtype("abcde")
+ .addSubtype(SUBTYPE)
+ .addSubtype("_subtype2")
.setIsPassiveMode(true)
.build();
startSendAndReceive(mockListenerOne, searchOptions);
@@ -517,7 +528,7 @@
public void testQueryTaskConfig_alwaysAskForUnicastResponse() {
//MdnsConfigsFlagsImpl.alwaysAskForUnicastResponseInEachBurst.override(true);
MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(false).build();
+ MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(false).build();
QueryTaskConfig config = new QueryTaskConfig(
searchOptions.getSubtypes(), searchOptions.isPassiveMode(),
false /* onlyUseIpv6OnIpv6OnlyNetworks */, 3 /* numOfQueriesBeforeBackoff */,
@@ -548,7 +559,7 @@
@Test
public void testQueryTaskConfig_askForUnicastInFirstQuery() {
MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(false).build();
+ MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(false).build();
QueryTaskConfig config = new QueryTaskConfig(
searchOptions.getSubtypes(), searchOptions.isPassiveMode(),
false /* onlyUseIpv6OnIpv6OnlyNetworks */, 3 /* numOfQueriesBeforeBackoff */,
@@ -579,15 +590,15 @@
@Test
public void testIfPreviousTaskIsCanceledWhenNewSessionStarts() {
MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(true).build();
+ MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(true).build();
startSendAndReceive(mockListenerOne, searchOptions);
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
// Change the sutypes and start a new session.
searchOptions =
MdnsSearchOptions.newBuilder()
- .addSubtype("12345")
- .addSubtype("abcde")
+ .addSubtype(SUBTYPE)
+ .addSubtype("_subtype2")
.setIsPassiveMode(true)
.build();
startSendAndReceive(mockListenerOne, searchOptions);
@@ -608,7 +619,7 @@
public void testIfPreviousTaskIsCanceledWhenSessionStops() {
//MdnsConfigsFlagsImpl.shouldCancelScanTaskWhenFutureIsNull.override(true);
MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(true).build();
+ MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(true).build();
startSendAndReceive(mockListenerOne, searchOptions);
// Change the sutypes and start a new session.
stopSendAndReceive(mockListenerOne);
@@ -697,14 +708,12 @@
// Process the initial response.
processResponse(createResponse(
- "service-instance-1", ipV4Address, 5353,
- /* subtype= */ "ABCDE",
+ "service-instance-1", ipV4Address, 5353, SUBTYPE,
Collections.emptyMap(), TEST_TTL), socketKey);
// Process a second response with a different port and updated text attributes.
processResponse(createResponse(
- "service-instance-1", ipV4Address, 5354,
- /* subtype= */ "ABCDE",
+ "service-instance-1", ipV4Address, 5354, SUBTYPE,
Collections.singletonMap("key", "value"), TEST_TTL),
socketKey);
@@ -716,7 +725,7 @@
List.of(ipV4Address) /* ipv4Address */,
List.of() /* ipv6Address */,
5353 /* port */,
- Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonList(SUBTYPE) /* subTypes */,
Collections.singletonMap("key", null) /* attributes */,
socketKey);
@@ -726,7 +735,7 @@
assertEquals(initialServiceInfo.getServiceInstanceName(), "service-instance-1");
assertEquals(initialServiceInfo.getIpv4Address(), ipV4Address);
assertEquals(initialServiceInfo.getPort(), 5353);
- assertEquals(initialServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
+ assertEquals(initialServiceInfo.getSubtypes(), Collections.singletonList(SUBTYPE));
assertNull(initialServiceInfo.getAttributeByKey("key"));
assertEquals(socketKey.getInterfaceIndex(), initialServiceInfo.getInterfaceIndex());
assertEquals(socketKey.getNetwork(), initialServiceInfo.getNetwork());
@@ -738,7 +747,7 @@
assertEquals(updatedServiceInfo.getIpv4Address(), ipV4Address);
assertEquals(updatedServiceInfo.getPort(), 5354);
assertTrue(updatedServiceInfo.hasSubtypes());
- assertEquals(updatedServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
+ assertEquals(updatedServiceInfo.getSubtypes(), Collections.singletonList(SUBTYPE));
assertEquals(updatedServiceInfo.getAttributeByKey("key"), "value");
assertEquals(socketKey.getInterfaceIndex(), updatedServiceInfo.getInterfaceIndex());
assertEquals(socketKey.getNetwork(), updatedServiceInfo.getNetwork());
@@ -751,14 +760,12 @@
// Process the initial response.
processResponse(createResponse(
- "service-instance-1", ipV6Address, 5353,
- /* subtype= */ "ABCDE",
+ "service-instance-1", ipV6Address, 5353, SUBTYPE,
Collections.emptyMap(), TEST_TTL), socketKey);
// Process a second response with a different port and updated text attributes.
processResponse(createResponse(
- "service-instance-1", ipV6Address, 5354,
- /* subtype= */ "ABCDE",
+ "service-instance-1", ipV6Address, 5354, SUBTYPE,
Collections.singletonMap("key", "value"), TEST_TTL),
socketKey);
@@ -770,7 +777,7 @@
List.of() /* ipv4Address */,
List.of(ipV6Address) /* ipv6Address */,
5353 /* port */,
- Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonList(SUBTYPE) /* subTypes */,
Collections.singletonMap("key", null) /* attributes */,
socketKey);
@@ -780,7 +787,7 @@
assertEquals(initialServiceInfo.getServiceInstanceName(), "service-instance-1");
assertEquals(initialServiceInfo.getIpv6Address(), ipV6Address);
assertEquals(initialServiceInfo.getPort(), 5353);
- assertEquals(initialServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
+ assertEquals(initialServiceInfo.getSubtypes(), Collections.singletonList(SUBTYPE));
assertNull(initialServiceInfo.getAttributeByKey("key"));
assertEquals(socketKey.getInterfaceIndex(), initialServiceInfo.getInterfaceIndex());
assertEquals(socketKey.getNetwork(), initialServiceInfo.getNetwork());
@@ -792,7 +799,7 @@
assertEquals(updatedServiceInfo.getIpv6Address(), ipV6Address);
assertEquals(updatedServiceInfo.getPort(), 5354);
assertTrue(updatedServiceInfo.hasSubtypes());
- assertEquals(updatedServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
+ assertEquals(updatedServiceInfo.getSubtypes(), Collections.singletonList(SUBTYPE));
assertEquals(updatedServiceInfo.getAttributeByKey("key"), "value");
assertEquals(socketKey.getInterfaceIndex(), updatedServiceInfo.getInterfaceIndex());
assertEquals(socketKey.getNetwork(), updatedServiceInfo.getNetwork());
@@ -854,8 +861,7 @@
public void reportExistingServiceToNewlyRegisteredListeners() throws Exception {
// Process the initial response.
processResponse(createResponse(
- "service-instance-1", "192.168.1.1", 5353,
- /* subtype= */ "ABCDE",
+ "service-instance-1", "192.168.1.1", 5353, SUBTYPE,
Collections.emptyMap(), TEST_TTL), socketKey);
startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
@@ -868,7 +874,7 @@
List.of("192.168.1.1") /* ipv4Address */,
List.of() /* ipv6Address */,
5353 /* port */,
- Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonList(SUBTYPE) /* subTypes */,
Collections.singletonMap("key", null) /* attributes */,
socketKey);
@@ -878,7 +884,7 @@
assertEquals(existingServiceInfo.getServiceInstanceName(), "service-instance-1");
assertEquals(existingServiceInfo.getIpv4Address(), "192.168.1.1");
assertEquals(existingServiceInfo.getPort(), 5353);
- assertEquals(existingServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
+ assertEquals(existingServiceInfo.getSubtypes(), Collections.singletonList(SUBTYPE));
assertNull(existingServiceInfo.getAttributeByKey("key"));
// Process a goodbye message for the existing response.
@@ -901,7 +907,8 @@
final String serviceInstanceName = "service-instance-1";
client =
new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps) {
+ mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
+ serviceCache) {
@Override
MdnsPacketWriter createMdnsPacketWriter() {
return mockPacketWriter;
@@ -916,7 +923,7 @@
// Process the initial response.
processResponse(createResponse(
- serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
+ serviceInstanceName, "192.168.1.1", 5353, SUBTYPE,
Collections.emptyMap(), TEST_TTL), socketKey);
// Clear the scheduled runnable.
@@ -925,6 +932,7 @@
// Simulate the case where the response is under TTL.
doReturn(TEST_ELAPSED_REALTIME + TEST_TTL - 1L).when(mockDecoderClock).elapsedRealtime();
firstMdnsTask.run();
+ verify(mockDeps, times(1)).sendMessage(any(), any(Message.class));
// Verify removed callback was not called.
verifyServiceRemovedNoCallback(mockListenerOne);
@@ -932,6 +940,7 @@
// Simulate the case where the response is after TTL.
doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
firstMdnsTask.run();
+ verify(mockDeps, times(2)).sendMessage(any(), any(Message.class));
// Verify removed callback was called.
verifyServiceRemovedCallback(
@@ -944,7 +953,8 @@
final String serviceInstanceName = "service-instance-1";
client =
new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps) {
+ mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
+ serviceCache) {
@Override
MdnsPacketWriter createMdnsPacketWriter() {
return mockPacketWriter;
@@ -955,7 +965,7 @@
// Process the initial response.
processResponse(createResponse(
- serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
+ serviceInstanceName, "192.168.1.1", 5353, SUBTYPE,
Collections.emptyMap(), TEST_TTL), socketKey);
// Clear the scheduled runnable.
@@ -977,7 +987,8 @@
final String serviceInstanceName = "service-instance-1";
client =
new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps) {
+ mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
+ serviceCache) {
@Override
MdnsPacketWriter createMdnsPacketWriter() {
return mockPacketWriter;
@@ -988,7 +999,7 @@
// Process the initial response.
processResponse(createResponse(
- serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
+ serviceInstanceName, "192.168.1.1", 5353, SUBTYPE,
Collections.emptyMap(), TEST_TTL), socketKey);
// Clear the scheduled runnable.
@@ -1012,19 +1023,18 @@
InOrder inOrder = inOrder(mockListenerOne);
// Process the initial response which is incomplete.
- final String subtype = "ABCDE";
processResponse(createResponse(
- serviceName, null, 5353, subtype,
+ serviceName, null, 5353, SUBTYPE,
Collections.emptyMap(), TEST_TTL), socketKey);
// Process a second response which has ip address to make response become complete.
processResponse(createResponse(
- serviceName, ipV4Address, 5353, subtype,
+ serviceName, ipV4Address, 5353, SUBTYPE,
Collections.emptyMap(), TEST_TTL), socketKey);
// Process a third response with a different ip address, port and updated text attributes.
processResponse(createResponse(
- serviceName, ipV6Address, 5354, subtype,
+ serviceName, ipV6Address, 5354, SUBTYPE,
Collections.singletonMap("key", "value"), TEST_TTL), socketKey);
// Process the last response which is goodbye message (with the main type, not subtype).
@@ -1041,7 +1051,7 @@
List.of() /* ipv4Address */,
List.of() /* ipv6Address */,
5353 /* port */,
- Collections.singletonList(subtype) /* subTypes */,
+ Collections.singletonList(SUBTYPE) /* subTypes */,
Collections.singletonMap("key", null) /* attributes */,
socketKey);
@@ -1053,7 +1063,7 @@
List.of(ipV4Address) /* ipv4Address */,
List.of() /* ipv6Address */,
5353 /* port */,
- Collections.singletonList(subtype) /* subTypes */,
+ Collections.singletonList(SUBTYPE) /* subTypes */,
Collections.singletonMap("key", null) /* attributes */,
socketKey);
@@ -1065,7 +1075,7 @@
List.of(ipV4Address) /* ipv4Address */,
List.of(ipV6Address) /* ipv6Address */,
5354 /* port */,
- Collections.singletonList(subtype) /* subTypes */,
+ Collections.singletonList(SUBTYPE) /* subTypes */,
Collections.singletonMap("key", "value") /* attributes */,
socketKey);
@@ -1077,7 +1087,7 @@
List.of(ipV4Address) /* ipv4Address */,
List.of(ipV6Address) /* ipv6Address */,
5354 /* port */,
- Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonList(SUBTYPE) /* subTypes */,
Collections.singletonMap("key", "value") /* attributes */,
socketKey);
@@ -1089,7 +1099,7 @@
List.of(ipV4Address) /* ipv4Address */,
List.of(ipV6Address) /* ipv6Address */,
5354 /* port */,
- Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonList(SUBTYPE) /* subTypes */,
Collections.singletonMap("key", "value") /* attributes */,
socketKey);
}
@@ -1097,7 +1107,8 @@
@Test
public void testProcessResponse_Resolve() throws Exception {
client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps);
+ mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
+ serviceCache);
final String instanceName = "service-instance";
final String[] hostname = new String[] { "testhost "};
@@ -1118,6 +1129,7 @@
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
srvTxtQueryCaptor.capture(),
eq(socketKey), eq(false));
+ verify(mockDeps, times(1)).sendMessage(any(), any(Message.class));
assertNotNull(delayMessage);
final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
@@ -1189,7 +1201,8 @@
@Test
public void testRenewTxtSrvInResolve() throws Exception {
client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps);
+ mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
+ serviceCache);
final String instanceName = "service-instance";
final String[] hostname = new String[] { "testhost "};
@@ -1210,6 +1223,7 @@
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
srvTxtQueryCaptor.capture(),
eq(socketKey), eq(false));
+ verify(mockDeps, times(1)).sendMessage(any(), any(Message.class));
assertNotNull(delayMessage);
final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
@@ -1249,6 +1263,7 @@
// Advance time so 75% of TTL passes and re-execute
doReturn(TEST_ELAPSED_REALTIME + (long) (TEST_TTL * 0.75))
.when(mockDecoderClock).elapsedRealtime();
+ verify(mockDeps, times(2)).sendMessage(any(), any(Message.class));
assertNotNull(delayMessage);
dispatchMessage();
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
@@ -1260,12 +1275,13 @@
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingMulticastResponse(
renewalQueryCaptor.capture(),
eq(socketKey), eq(false));
+ verify(mockDeps, times(3)).sendMessage(any(), any(Message.class));
+ assertNotNull(delayMessage);
inOrder.verify(mockListenerOne).onDiscoveryQuerySent(any(), anyInt());
final MdnsPacket renewalPacket = MdnsPacket.parse(
new MdnsPacketReader(renewalQueryCaptor.getValue()));
assertTrue(hasQuestion(renewalPacket, MdnsRecord.TYPE_ANY, serviceName));
inOrder.verifyNoMoreInteractions();
- assertNotNull(delayMessage);
long updatedReceiptTime = TEST_ELAPSED_REALTIME + TEST_TTL;
final MdnsPacket refreshedSrvTxtResponse = new MdnsPacket(
@@ -1299,7 +1315,8 @@
@Test
public void testProcessResponse_ResolveExcludesOtherServices() {
client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps);
+ mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
+ serviceCache);
final String requestedInstance = "instance1";
final String otherInstance = "instance2";
@@ -1363,7 +1380,8 @@
@Test
public void testProcessResponse_SubtypeDiscoveryLimitedToSubtype() {
client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps);
+ mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
+ serviceCache);
final String matchingInstance = "instance1";
final String subtype = "_subtype";
@@ -1444,7 +1462,8 @@
@Test
public void testNotifySocketDestroyed() throws Exception {
client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps);
+ mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
+ serviceCache);
final String requestedInstance = "instance1";
final String otherInstance = "instance2";
@@ -1503,12 +1522,106 @@
inOrder2.verify(mockListenerTwo).onServiceNameDiscovered(
matchServiceName(requestedInstance));
inOrder2.verify(mockListenerTwo).onServiceFound(matchServiceName(requestedInstance));
- inOrder2.verify(mockListenerTwo).onServiceNameDiscovered(matchServiceName(otherInstance));
- inOrder2.verify(mockListenerTwo).onServiceFound(matchServiceName(otherInstance));
- inOrder2.verify(mockListenerTwo).onServiceRemoved(matchServiceName(otherInstance));
- inOrder2.verify(mockListenerTwo).onServiceNameRemoved(matchServiceName(otherInstance));
inOrder2.verify(mockListenerTwo).onServiceRemoved(matchServiceName(requestedInstance));
inOrder2.verify(mockListenerTwo).onServiceNameRemoved(matchServiceName(requestedInstance));
+ verify(mockListenerTwo).onServiceNameDiscovered(matchServiceName(otherInstance));
+ verify(mockListenerTwo).onServiceFound(matchServiceName(otherInstance));
+ verify(mockListenerTwo).onServiceRemoved(matchServiceName(otherInstance));
+ verify(mockListenerTwo).onServiceNameRemoved(matchServiceName(otherInstance));
+ }
+
+ @Test
+ public void testServicesAreCached() throws Exception {
+ final String serviceName = "service-instance";
+ final String ipV4Address = "192.0.2.0";
+ // Register a listener
+ startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
+ verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+ InOrder inOrder = inOrder(mockListenerOne);
+
+ // Process a response which has ip address to make response become complete.
+
+ processResponse(createResponse(
+ serviceName, ipV4Address, 5353, SUBTYPE,
+ Collections.emptyMap(), TEST_TTL),
+ socketKey);
+
+ // Verify that onServiceNameDiscovered is called.
+ inOrder.verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
+ serviceName,
+ SERVICE_TYPE_LABELS,
+ List.of(ipV4Address) /* ipv4Address */,
+ List.of() /* ipv6Address */,
+ 5353 /* port */,
+ Collections.singletonList(SUBTYPE) /* subTypes */,
+ Collections.singletonMap("key", null) /* attributes */,
+ socketKey);
+
+ // Verify that onServiceFound is called.
+ inOrder.verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(1),
+ serviceName,
+ SERVICE_TYPE_LABELS,
+ List.of(ipV4Address) /* ipv4Address */,
+ List.of() /* ipv6Address */,
+ 5353 /* port */,
+ Collections.singletonList(SUBTYPE) /* subTypes */,
+ Collections.singletonMap("key", null) /* attributes */,
+ socketKey);
+
+ // Unregister the listener
+ stopSendAndReceive(mockListenerOne);
+ verify(mockDeps, times(2)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+
+ // Register another listener.
+ startSendAndReceive(mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
+ verify(mockDeps, times(3)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+ InOrder inOrder2 = inOrder(mockListenerTwo);
+
+ // The services are cached in MdnsServiceCache, verify that onServiceNameDiscovered is
+ // called immediately.
+ inOrder2.verify(mockListenerTwo).onServiceNameDiscovered(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(2),
+ serviceName,
+ SERVICE_TYPE_LABELS,
+ List.of(ipV4Address) /* ipv4Address */,
+ List.of() /* ipv6Address */,
+ 5353 /* port */,
+ Collections.singletonList(SUBTYPE) /* subTypes */,
+ Collections.singletonMap("key", null) /* attributes */,
+ socketKey);
+
+ // The services are cached in MdnsServiceCache, verify that onServiceFound is
+ // called immediately.
+ inOrder2.verify(mockListenerTwo).onServiceFound(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(3),
+ serviceName,
+ SERVICE_TYPE_LABELS,
+ List.of(ipV4Address) /* ipv4Address */,
+ List.of() /* ipv6Address */,
+ 5353 /* port */,
+ Collections.singletonList(SUBTYPE) /* subTypes */,
+ Collections.singletonMap("key", null) /* attributes */,
+ socketKey);
+
+ // Process a response with a different ip address, port and updated text attributes.
+ final String ipV6Address = "2001:db8::";
+ processResponse(createResponse(
+ serviceName, ipV6Address, 5354, SUBTYPE,
+ Collections.singletonMap("key", "value"), TEST_TTL), socketKey);
+
+ // Verify the onServiceUpdated is called.
+ inOrder2.verify(mockListenerTwo).onServiceUpdated(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(4),
+ serviceName,
+ SERVICE_TYPE_LABELS,
+ List.of(ipV4Address) /* ipv4Address */,
+ List.of(ipV6Address) /* ipv6Address */,
+ 5354 /* port */,
+ Collections.singletonList(SUBTYPE) /* subTypes */,
+ Collections.singletonMap("key", "value") /* attributes */,
+ socketKey);
}
private static MdnsServiceInfo matchServiceName(String name) {
@@ -1545,6 +1658,8 @@
expectedIPv6Packets[index], socketKey, false);
}
}
+ verify(mockDeps, times(index + 1))
+ .sendMessage(any(Handler.class), any(Message.class));
// Verify the task has been scheduled.
verify(mockDeps, times(scheduledCount))
.sendMessageDelayed(any(Handler.class), any(Message.class), anyLong());
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
index 69efc61..74f1c37 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
@@ -39,6 +39,7 @@
import android.text.format.DateUtils;
import com.android.net.module.util.HexDump;
+import com.android.net.module.util.SharedLog;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -74,6 +75,7 @@
@Mock private MdnsSocket mockUnicastSocket;
@Mock private MulticastLock mockMulticastLock;
@Mock private MdnsSocketClient.Callback mockCallback;
+ @Mock private SharedLog sharedLog;
private MdnsSocketClient mdnsClient;
@@ -84,9 +86,9 @@
when(mockWifiManager.createMulticastLock(ArgumentMatchers.anyString()))
.thenReturn(mockMulticastLock);
- mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock) {
+ mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog) {
@Override
- MdnsSocket createMdnsSocket(int port) throws IOException {
+ MdnsSocket createMdnsSocket(int port, SharedLog sharedLog) throws IOException {
if (port == MdnsConstants.MDNS_PORT) {
return mockMulticastSocket;
}
@@ -513,9 +515,9 @@
//MdnsConfigsFlagsImpl.allowNetworkInterfaceIndexPropagation.override(true);
when(mockMulticastSocket.getInterfaceIndex()).thenReturn(21);
- mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock) {
+ mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog) {
@Override
- MdnsSocket createMdnsSocket(int port) {
+ MdnsSocket createMdnsSocket(int port, SharedLog sharedLog) {
if (port == MdnsConstants.MDNS_PORT) {
return mockMulticastSocket;
}
@@ -536,9 +538,9 @@
//MdnsConfigsFlagsImpl.allowNetworkInterfaceIndexPropagation.override(false);
when(mockMulticastSocket.getInterfaceIndex()).thenReturn(21);
- mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock) {
+ mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog) {
@Override
- MdnsSocket createMdnsSocket(int port) {
+ MdnsSocket createMdnsSocket(int port, SharedLog sharedLog) {
if (port == MdnsConstants.MDNS_PORT) {
return mockMulticastSocket;
}
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 e971de7..c0b74e1 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
@@ -152,11 +152,11 @@
.getNetworkInterfaceByName(WIFI_P2P_IFACE_NAME);
doReturn(mTetheredIfaceWrapper).when(mDeps).getNetworkInterfaceByName(TETHERED_IFACE_NAME);
doReturn(mock(MdnsInterfaceSocket.class))
- .when(mDeps).createMdnsInterfaceSocket(any(), anyInt(), any(), any());
+ .when(mDeps).createMdnsInterfaceSocket(any(), anyInt(), any(), any(), any());
doReturn(TETHERED_IFACE_IDX).when(mDeps).getNetworkInterfaceIndexByName(
- TETHERED_IFACE_NAME);
+ eq(TETHERED_IFACE_NAME), any());
doReturn(789).when(mDeps).getNetworkInterfaceIndexByName(
- WIFI_P2P_IFACE_NAME);
+ eq(WIFI_P2P_IFACE_NAME), any());
final HandlerThread thread = new HandlerThread("MdnsSocketProviderTest");
thread.start();
mHandler = new Handler(thread.getLooper());
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketTests.java
index 73dbd38..5809684 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketTests.java
@@ -21,6 +21,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import com.android.net.module.util.SharedLog;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -50,6 +51,7 @@
@Mock private NetworkInterfaceWrapper mockNetworkInterfaceWrapper;
@Mock private MulticastSocket mockMulticastSocket;
@Mock private MulticastNetworkInterfaceProvider mockMulticastNetworkInterfaceProvider;
+ @Mock private SharedLog sharedLog;
private SocketAddress socketIPv4Address;
private SocketAddress socketIPv6Address;
@@ -75,7 +77,8 @@
@Test
public void mdnsSocket_basicFunctionality() throws IOException {
- mdnsSocket = new MdnsSocket(mockMulticastNetworkInterfaceProvider, mockMulticastSocket);
+ mdnsSocket = new MdnsSocket(mockMulticastNetworkInterfaceProvider, mockMulticastSocket,
+ sharedLog);
mdnsSocket.send(datagramPacket);
verify(mockMulticastSocket).setNetworkInterface(networkInterface);
verify(mockMulticastSocket).send(datagramPacket);
@@ -101,7 +104,8 @@
when(mockMulticastNetworkInterfaceProvider.getMulticastNetworkInterfaces())
.thenReturn(Collections.singletonList(mockNetworkInterfaceWrapper));
- mdnsSocket = new MdnsSocket(mockMulticastNetworkInterfaceProvider, mockMulticastSocket);
+ mdnsSocket = new MdnsSocket(mockMulticastNetworkInterfaceProvider, mockMulticastSocket,
+ sharedLog);
when(mockMulticastNetworkInterfaceProvider.isOnIpV6OnlyNetwork(
Collections.singletonList(mockNetworkInterfaceWrapper)))
@@ -125,7 +129,8 @@
when(mockMulticastNetworkInterfaceProvider.getMulticastNetworkInterfaces())
.thenReturn(Collections.singletonList(mockNetworkInterfaceWrapper));
- mdnsSocket = new MdnsSocket(mockMulticastNetworkInterfaceProvider, mockMulticastSocket);
+ mdnsSocket = new MdnsSocket(mockMulticastNetworkInterfaceProvider, mockMulticastSocket,
+ sharedLog);
when(mockMulticastNetworkInterfaceProvider.isOnIpV6OnlyNetwork(
Collections.singletonList(mockNetworkInterfaceWrapper)))
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProviderTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProviderTests.java
index 2268dfe..af233c9 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProviderTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProviderTests.java
@@ -30,6 +30,7 @@
import androidx.test.InstrumentationRegistry;
+import com.android.net.module.util.SharedLog;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -65,6 +66,8 @@
@Mock private NetworkInterfaceWrapper multicastInterfaceOne;
@Mock private NetworkInterfaceWrapper multicastInterfaceTwo;
+ @Mock private SharedLog sharedLog;
+
private final List<NetworkInterfaceWrapper> networkInterfaces = new ArrayList<>();
private MulticastNetworkInterfaceProvider provider;
private Context context;
@@ -156,7 +159,7 @@
false /* isIpv6 */);
provider =
- new MulticastNetworkInterfaceProvider(context) {
+ new MulticastNetworkInterfaceProvider(context, sharedLog) {
@Override
List<NetworkInterfaceWrapper> getNetworkInterfaces() {
return networkInterfaces;
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsBaseTest.java b/tests/unit/java/com/android/server/net/NetworkStatsBaseTest.java
index a058a46..2c9f212 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsBaseTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsBaseTest.java
@@ -41,6 +41,7 @@
abstract class NetworkStatsBaseTest {
static final String TEST_IFACE = "test0";
static final String TEST_IFACE2 = "test1";
+ static final String TEST_IFACE3 = "test2";
static final String TUN_IFACE = "test_nss_tun0";
static final String TUN_IFACE2 = "test_nss_tun1";
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index b8b0289..9453617 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -79,7 +79,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
@@ -240,7 +239,9 @@
private @Mock INetd mNetd;
private @Mock TetheringManager mTetheringManager;
private @Mock NetworkStatsFactory mStatsFactory;
- private @Mock NetworkStatsSettings mSettings;
+ @NonNull
+ private final TestNetworkStatsSettings mSettings =
+ new TestNetworkStatsSettings(HOUR_IN_MILLIS, WEEK_IN_MILLIS);
private @Mock IBinder mUsageCallbackBinder;
private TestableUsageCallback mUsageCallback;
private @Mock AlarmManager mAlarmManager;
@@ -533,7 +534,6 @@
mStatsDir = null;
mNetd = null;
- mSettings = null;
mSession.close();
mService = null;
@@ -1250,8 +1250,9 @@
TEST_IFACE2, IMSI_1, null /* wifiNetworkKey */,
false /* isTemporarilyNotMetered */, false /* isRoaming */);
- final NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {
- mobileState, buildWifiState()};
+ final NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{
+ mobileState, buildWifiState(false, TEST_IFACE, null),
+ buildWifiState(false, TEST_IFACE3, null)};
mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_LTE);
@@ -1266,16 +1267,22 @@
final NetworkStats.Entry entry3 = new NetworkStats.Entry(
TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xBEEF, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 1024L, 8L, 512L, 4L, 2L);
+ // Add an entry that with different wifi interface, but expected to be merged into entry3
+ // after clearing interface information.
+ final NetworkStats.Entry entry4 = new NetworkStats.Entry(
+ TEST_IFACE3, UID_BLUE, SET_DEFAULT, 0xBEEF, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1L, 2L, 3L, 4L, 5L);
final TetherStatsParcel[] emptyTetherStats = {};
// The interfaces that expect to be used to query the stats.
- final String[] wifiIfaces = {TEST_IFACE};
+ final String[] wifiIfaces = {TEST_IFACE, TEST_IFACE3};
incrementCurrentTime(HOUR_IN_MILLIS);
mockDefaultSettings();
- mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 4)
.insertEntry(entry1)
.insertEntry(entry2)
- .insertEntry(entry3), emptyTetherStats, wifiIfaces);
+ .insertEntry(entry3)
+ .insertEntry(entry4), emptyTetherStats, wifiIfaces);
// getUidStatsForTransport (through getNetworkStatsUidDetail) adds all operation counts
// with active interface, and the interface here is mobile interface, so this test makes
@@ -1293,7 +1300,7 @@
assertValues(wifiStats, null /* iface */, UID_RED, SET_DEFAULT, 0xF00D,
METERED_NO, ROAMING_NO, METERED_NO, 50L, 5L, 50L, 5L, 1L);
assertValues(wifiStats, null /* iface */, UID_BLUE, SET_DEFAULT, 0xBEEF,
- METERED_NO, ROAMING_NO, METERED_NO, 1024L, 8L, 512L, 4L, 2L);
+ METERED_NO, ROAMING_NO, METERED_NO, 1025L, 10L, 515L, 8L, 7L);
final String[] mobileIfaces = {TEST_IFACE2};
mockNetworkStatsUidDetail(buildEmptyStats(), emptyTetherStats, mobileIfaces);
@@ -1758,7 +1765,7 @@
}
private void setCombineSubtypeEnabled(boolean enable) {
- doReturn(enable).when(mSettings).getCombineSubtypeEnabled();
+ mSettings.setCombineSubtypeEnabled(enable);
mHandler.post(() -> mContentObserver.onChange(false, Settings.Global
.getUriFor(Settings.Global.NETSTATS_COMBINE_SUBTYPE_ENABLED)));
waitForIdle();
@@ -2282,21 +2289,80 @@
mockSettings(HOUR_IN_MILLIS, WEEK_IN_MILLIS);
}
- private void mockSettings(long bucketDuration, long deleteAge) throws Exception {
- doReturn(HOUR_IN_MILLIS).when(mSettings).getPollInterval();
- doReturn(0L).when(mSettings).getPollDelay();
- doReturn(true).when(mSettings).getSampleEnabled();
- doReturn(false).when(mSettings).getCombineSubtypeEnabled();
+ private void mockSettings(long bucketDuration, long deleteAge) {
+ mSettings.setConfig(new Config(bucketDuration, deleteAge, deleteAge));
+ }
- final Config config = new Config(bucketDuration, deleteAge, deleteAge);
- doReturn(config).when(mSettings).getXtConfig();
- doReturn(config).when(mSettings).getUidConfig();
- doReturn(config).when(mSettings).getUidTagConfig();
+ // Note that this object will be accessed from test main thread and service handler thread.
+ // Thus, it has to be thread safe in order to prevent from flakiness.
+ private static class TestNetworkStatsSettings
+ extends NetworkStatsService.DefaultNetworkStatsSettings {
- doReturn(MB_IN_BYTES).when(mSettings).getGlobalAlertBytes(anyLong());
- doReturn(MB_IN_BYTES).when(mSettings).getXtPersistBytes(anyLong());
- doReturn(MB_IN_BYTES).when(mSettings).getUidPersistBytes(anyLong());
- doReturn(MB_IN_BYTES).when(mSettings).getUidTagPersistBytes(anyLong());
+ @NonNull
+ private volatile Config mConfig;
+ private final AtomicBoolean mCombineSubtypeEnabled = new AtomicBoolean();
+
+ TestNetworkStatsSettings(long bucketDuration, long deleteAge) {
+ mConfig = new Config(bucketDuration, deleteAge, deleteAge);
+ }
+
+ void setConfig(@NonNull Config config) {
+ mConfig = config;
+ }
+
+ @Override
+ public long getPollDelay() {
+ return 0L;
+ }
+
+ @Override
+ public long getGlobalAlertBytes(long def) {
+ return MB_IN_BYTES;
+ }
+
+ @Override
+ public Config getXtConfig() {
+ return mConfig;
+ }
+
+ @Override
+ public Config getUidConfig() {
+ return mConfig;
+ }
+
+ @Override
+ public Config getUidTagConfig() {
+ return mConfig;
+ }
+
+ @Override
+ public long getXtPersistBytes(long def) {
+ return MB_IN_BYTES;
+ }
+
+ @Override
+ public long getUidPersistBytes(long def) {
+ return MB_IN_BYTES;
+ }
+
+ @Override
+ public long getUidTagPersistBytes(long def) {
+ return MB_IN_BYTES;
+ }
+
+ @Override
+ public boolean getCombineSubtypeEnabled() {
+ return mCombineSubtypeEnabled.get();
+ }
+
+ public void setCombineSubtypeEnabled(boolean enable) {
+ mCombineSubtypeEnabled.set(enable);
+ }
+
+ @Override
+ public boolean getAugmentEnabled() {
+ return false;
+ }
}
private void assertStatsFilesExist(boolean exist) {