Merge "Keep case in MdnsServiceInfo attributes"
diff --git a/Cronet/tests/common/Android.bp b/Cronet/tests/common/Android.bp
index f8bdb08..939a81c 100644
--- a/Cronet/tests/common/Android.bp
+++ b/Cronet/tests/common/Android.bp
@@ -27,7 +27,9 @@
 android_test {
     name: "NetHttpCoverageTests",
     defaults: ["CronetTestJavaDefaults"],
+    enforce_default_target_sdk_version: true,
     sdk_version: "test_current",
+    min_sdk_version: "30",
     test_suites: ["general-tests", "mts-tethering"],
     static_libs: [
         "modules-utils-native-coverage-listener",
diff --git a/Cronet/tests/common/AndroidManifest.xml b/Cronet/tests/common/AndroidManifest.xml
index efe880c..b00fc90 100644
--- a/Cronet/tests/common/AndroidManifest.xml
+++ b/Cronet/tests/common/AndroidManifest.xml
@@ -18,6 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           xmlns:tools="http://schemas.android.com/tools"
           package="com.android.net.http.tests.coverage">
+
     <!-- NetHttpCoverageTests combines CtsNetHttpTestCases and NetHttpTests targets,
      so permissions and others are declared in their respective manifests -->
     <application tools:replace="android:label"
diff --git a/Cronet/tests/common/AndroidTest.xml b/Cronet/tests/common/AndroidTest.xml
index ca298dd..2ac418f 100644
--- a/Cronet/tests/common/AndroidTest.xml
+++ b/Cronet/tests/common/AndroidTest.xml
@@ -20,8 +20,11 @@
     </target_preparer>
     <option name="test-tag" value="NetHttpCoverageTests" />
     <!-- Tethering/Connectivity is a SDK 30+ module -->
+    <!-- TODO Switch back to Sdk30 when b/270049141 is fixed -->
     <object type="module_controller"
-            class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+            class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
+    <option name="config-descriptor:metadata" key="mainline-param"
+            value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.net.http.tests.coverage" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
diff --git a/Cronet/tests/cts/Android.bp b/Cronet/tests/cts/Android.bp
index 945b220..d969b54 100644
--- a/Cronet/tests/cts/Android.bp
+++ b/Cronet/tests/cts/Android.bp
@@ -29,6 +29,10 @@
 java_defaults {
     name: "CronetTestJavaDefaultsEnabled",
     enabled: true,
+    // TODO(danstahr): move to unconditional static_libs once the T branch is abandoned
+    static_libs: [
+        "truth",
+    ],
 }
 
 java_defaults {
@@ -43,7 +47,12 @@
 
 android_library {
     name: "CtsNetHttpTestsLib",
+    defaults: [
+        "cts_defaults",
+        "CronetTestJavaDefaults",
+    ],
     sdk_version: "test_current",
+    min_sdk_version: "30",
     srcs: [
         "src/**/*.java",
         "src/**/*.kt",
@@ -62,6 +71,7 @@
         "framework-tethering",
         "org.apache.http.legacy",
     ],
+    lint: { test: true }
 }
 
 android_test {
diff --git a/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt b/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt
new file mode 100644
index 0000000..1888962
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.http.cts
+
+import android.net.http.QuicOptions
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class QuicOptionsTest {
+    @Test
+    fun testQuicOptions_defaultValues() {
+        val quicOptions = QuicOptions.Builder().build()
+        assertThat(quicOptions.quicHostAllowlist).isEmpty()
+        assertThat(quicOptions.handshakeUserAgent).isNull()
+        // TODO(danstahr): idleConnectionTimeout getter should be public
+        // assertThat(quicOptions.idleConnectionTimeout).isNull()
+        assertThat(quicOptions.inMemoryServerConfigsCacheSize).isNull()
+    }
+
+    @Test
+    fun testQuicOptions_quicHostAllowlist_returnsAddedValues() {
+        val quicOptions = QuicOptions.Builder()
+                .addAllowedQuicHost("foo")
+                .addAllowedQuicHost("bar")
+                .addAllowedQuicHost("foo")
+                .addAllowedQuicHost("baz")
+                .build()
+        assertThat(quicOptions.quicHostAllowlist)
+                .containsExactly("foo", "bar", "baz")
+                .inOrder()
+    }
+
+    // TODO(danstahr): idleConnectionTimeout getter should be public
+    /*
+    @Test
+    fun testQuicOptions_idleConnectionTimeout_returnsSetValue() {
+        val timeout = Duration.ofMinutes(10)
+        val quicOptions = QuicOptions.Builder()
+                .setIdleConnectionTimeout(timeout)
+                .build()
+        assertThat(quicOptions.idleConnectionTimeout)
+                .isEqualTo(timeout)
+    }
+    */
+
+    @Test
+    fun testQuicOptions_inMemoryServerConfigsCacheSize_returnsSetValue() {
+        val quicOptions = QuicOptions.Builder()
+                .setInMemoryServerConfigsCacheSize(42)
+                .build()
+        assertThat(quicOptions.inMemoryServerConfigsCacheSize)
+                .isEqualTo(42)
+    }
+}
diff --git a/Cronet/tests/mts/Android.bp b/Cronet/tests/mts/Android.bp
index 1cabd63..03d163c 100644
--- a/Cronet/tests/mts/Android.bp
+++ b/Cronet/tests/mts/Android.bp
@@ -20,6 +20,8 @@
 android_library {
     name: "NetHttpTestsLibPreJarJar",
     srcs: [":cronet_aml_javatests_sources"],
+    sdk_version: "test_current",
+    min_sdk_version: "30",
     static_libs: [
         "androidx.test.ext.junit",
         "androidx.test.rules",
@@ -28,7 +30,8 @@
     libs: [
         "android.test.base",
         "framework-tethering-pre-jarjar",
-    ]
+    ],
+    lint: { test: true }
 }
 
 android_test {
diff --git a/Cronet/tests/mts/AndroidTest.xml b/Cronet/tests/mts/AndroidTest.xml
index 8cb549e..0d780a1 100644
--- a/Cronet/tests/mts/AndroidTest.xml
+++ b/Cronet/tests/mts/AndroidTest.xml
@@ -16,8 +16,9 @@
   -->
 <configuration description="Runs NetHttp Mainline Tests.">
     <!-- Only run tests if the device under test is SDK version 30 or above. -->
+    <!-- TODO Switch back to Sdk30 when b/270049141 is fixed -->
     <object type="module_controller"
-            class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+            class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="test-file-name" value="NetHttpTests.apk" />
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 4d3ecdf..70c5f85 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -213,6 +213,9 @@
     },
     {
       "name": "libnetworkstats_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+    },
+    {
+      "name": "NetHttpCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
     }
   ],
   "mainline-postsubmit": [
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 84da79d..e068d8a 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -350,10 +350,10 @@
 
 static __always_inline inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid,
                                                   bool egress, const unsigned kver) {
-    if (skip_owner_match(skb, kver)) return PASS;
-
     if (is_system_uid(uid)) return PASS;
 
+    if (skip_owner_match(skb, kver)) return PASS;
+
     BpfConfig enabledRules = getConfig(UID_RULES_CONFIGURATION_KEY);
 
     UidOwnerValue* uidEntry = bpf_uid_owner_map_lookup_elem(&uid);
@@ -415,11 +415,6 @@
     }
 
     int match = bpf_owner_match(skb, sock_uid, egress, kver);
-    if (egress && (match == DROP)) {
-        // If an outbound packet is going to be dropped, we do not count that
-        // traffic.
-        return match;
-    }
 
 // Workaround for secureVPN with VpnIsolation enabled, refer to b/159994981 for details.
 // Keep TAG_SYSTEM_DNS in sync with DnsResolver/include/netd_resolv/resolv.h
@@ -432,6 +427,9 @@
         if (match == DROP_UNLESS_DNS) match = DROP;
     }
 
+    // If an outbound packet is going to be dropped, we do not count that traffic.
+    if (egress && (match == DROP)) return DROP;
+
     StatsKey key = {.uid = uid, .tag = tag, .counterSet = 0, .ifaceIndex = skb->ifindex};
 
     uint8_t* counterSet = bpf_uid_counterset_map_lookup_elem(&uid);
diff --git a/framework-t/src/android/net/NetworkTemplate.java b/framework-t/src/android/net/NetworkTemplate.java
index 4a1632b..55fcc4a 100644
--- a/framework-t/src/android/net/NetworkTemplate.java
+++ b/framework-t/src/android/net/NetworkTemplate.java
@@ -51,6 +51,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.NetworkIdentityUtils;
 
@@ -246,6 +247,38 @@
         return new NetworkTemplate.Builder(MATCH_ETHERNET).build();
     }
 
+    /**
+     * Template to combine all {@link ConnectivityManager#TYPE_BLUETOOTH} style
+     * networks together.
+     *
+     * @hide
+     */
+    // TODO(b/270089918): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
+    public static NetworkTemplate buildTemplateBluetooth() {
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException(
+                    "buildTemplateBluetooth is not supported on Android U devices or above");
+        }
+        return new NetworkTemplate.Builder(MATCH_BLUETOOTH).build();
+    }
+
+    /**
+     * Template to combine all {@link ConnectivityManager#TYPE_PROXY} style
+     * networks together.
+     *
+     * @hide
+     */
+    // TODO(b/270089918): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
+    public static NetworkTemplate buildTemplateProxy() {
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException(
+                    "buildTemplateProxy is not supported on Android U devices or above");
+        }
+        return new NetworkTemplate(MATCH_PROXY, null, null);
+    }
+
     private final int mMatchRule;
 
     /**
@@ -316,6 +349,10 @@
         if (matchRule == 6 || matchRule == 7) {
             Log.e(TAG, "Use MATCH_MOBILE with empty subscriberIds or MATCH_WIFI with empty "
                     + "wifiNetworkKeys instead of template with matchRule=" + matchRule);
+            if (SdkLevel.isAtLeastU()) {
+                throw new UnsupportedOperationException(
+                        "Wildcard templates are not supported on Android U devices or above");
+            }
         }
     }
 
@@ -337,6 +374,43 @@
     }
 
     /** @hide */
+    // TODO(b/270089918): Remove this method after no callers.
+    public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
+            String wifiNetworkKey) {
+        // Older versions used to only match MATCH_MOBILE and MATCH_MOBILE_WILDCARD templates
+        // to metered networks. It is now possible to match mobile with any meteredness, but
+        // in order to preserve backward compatibility of @UnsupportedAppUsage methods, this
+        // constructor passes METERED_YES for these types.
+        this(getBackwardsCompatibleMatchRule(matchRule), matchSubscriberIds,
+                wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0],
+                getMeterednessForBackwardsCompatibility(matchRule),
+                ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+                OEM_MANAGED_ALL);
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException(
+                    "This constructor is not supported on Android U devices or above");
+        }
+    }
+
+    /** @hide */
+    // TODO(b/269974916): Remove this method after Android U is released.
+    //  This is only used by CTS of Android T.
+    public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
+            String[] matchWifiNetworkKeys, int metered, int roaming,
+            int defaultNetwork, int ratType, int oemManaged, int subscriberIdMatchRule) {
+        // subscriberId and subscriberIdMatchRule aren't used since they are replaced by
+        // matchSubscriberIds, which could be null to indicate the intention of matching any
+        // subscriberIds.
+        this(getBackwardsCompatibleMatchRule(matchRule),
+                matchSubscriberIds == null ? new String[]{} : matchSubscriberIds,
+                matchWifiNetworkKeys, metered, roaming, defaultNetwork, ratType, oemManaged);
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException(
+                    "This constructor is not supported on Android U devices or above");
+        }
+    }
+
+    /** @hide */
     public NetworkTemplate(int matchRule, String[] matchSubscriberIds,
             String[] matchWifiNetworkKeys, int metered, int roaming, int defaultNetwork,
             int ratType, int oemManaged) {
@@ -436,9 +510,14 @@
         return false;
     }
 
-    // TODO(b/270089918): Remove this method after no callers.
+    // TODO(b/270089918): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
     /** @hide */
     public boolean isMatchRuleMobile() {
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException(
+                    "isMatchRuleMobile is not supported on Android U devices or above");
+        }
         switch (mMatchRule) {
             case MATCH_MOBILE:
             // Old MATCH_MOBILE_WILDCARD
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 427eac1..324f565 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -53,18 +53,70 @@
 import java.util.StringJoiner;
 
 /**
- * Representation of the capabilities of an active network. Instances are
- * typically obtained through
- * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)}
- * or {@link ConnectivityManager#getNetworkCapabilities(Network)}.
- * <p>
- * This replaces the old {@link ConnectivityManager#TYPE_MOBILE} method of
- * network selection. Rather than indicate a need for Wi-Fi because an
- * application needs high bandwidth and risk obsolescence when a new, fast
- * network appears (like LTE), the application should specify it needs high
- * bandwidth. Similarly if an application needs an unmetered network for a bulk
- * transfer it can specify that rather than assuming all cellular based
- * connections are metered and all Wi-Fi based connections are not.
+ * Representation of the capabilities of an active network.
+ *
+ * <p>@see <a href="https://developer.android.com/training/basics/network-ops/reading-network-state>
+ * this general guide</a> on how to use NetworkCapabilities and related classes.
+ *
+ * <p>NetworkCapabilities represent what a network can do and what its
+ * characteristics are like. The principal attribute of NetworkCapabilities
+ * is in the capabilities bits, which are checked with
+ * {@link #hasCapability(int)}. See the list of capabilities and each
+ * capability for a description of what it means.
+ *
+ * <p>Some prime examples include {@code NET_CAPABILITY_MMS}, which means that the
+ * network is capable of sending MMS. A network without this capability
+ * is not capable of sending MMS.
+ * <p>The {@code NET_CAPABILITY_INTERNET} capability means that the network is
+ * configured to reach the general Internet. It may or may not actually
+ * provide connectivity ; the {@code NET_CAPABILITY_VALIDATED} bit indicates that
+ * the system found actual connectivity to the general Internet the last
+ * time it checked. Apps interested in actual connectivity should usually
+ * look at both these capabilities.
+ * <p>The {@code NET_CAPABILITY_NOT_METERED} capability is set for networks that
+ * do not bill the user for consumption of bytes. Applications are
+ * encouraged to consult this to determine appropriate usage, and to
+ * limit usage of metered network where possible, including deferring
+ * big downloads until such a time that an unmetered network is connected.
+ * Also see {@link android.app.job.JobScheduler} to help with scheduling such
+ * downloads, in particular
+ * {@link android.app.job.JobInfo.Builder#setRequiredNetwork(NetworkRequest)}.
+ * <p>NetworkCapabilities contain a number of other capabilities that
+ * represent what modern networks can and can't do. Look up the individual
+ * capabilities in this class to learn about each of them.
+ *
+ * <p>NetworkCapabilities typically represent attributes that can apply to
+ * any network. The attributes that apply only to specific transports like
+ * cellular or Wi-Fi can be found in the specifier (for requestable attributes)
+ * or in the transport info (for non-requestable ones). See
+ * {@link #getNetworkSpecifier} and {@link #getTransportInfo}. An app would
+ * downcast these to the specific class for the transport they need if they
+ * are interested in transport-specific attributes. Also see
+ * {@link android.net.wifi.WifiNetworkSpecifier} or
+ * {@link android.net.wifi.WifiInfo} for some examples of each of these.
+ *
+ * <p>NetworkCapabilities also contains other attributes like the estimated
+ * upstream and downstream bandwidth and the specific transport of that
+ * network (e.g. {@link #TRANSPORT_CELLULAR}). Generally, apps should normally
+ * have little reason to check for the type of transport ; for example, to
+ * query whether a network costs money to the user, do not look at the
+ * transport, but instead look at the absence or presence of
+ * {@link #NET_CAPABILITY_NOT_METERED} which will correctly account for
+ * metered Wi-Fis and free of charge cell connections.
+ *
+ * <p>The system communicates with apps about connected networks and uses
+ * NetworkCapabilities to express these capabilities about these networks.
+ * Apps should register callbacks with the {@link ConnectivityManager#requestNetwork}
+ * or {@link ConnectivityManager#registerNetworkCallback} family of methods
+ * to learn about the capabilities of a network on a continuous basis
+ * and be able to react to changes to capabilities. For quick debugging Android also
+ * provides {@link ConnectivityManager#getNetworkCapabilities(Network)},
+ * but the dynamic nature of networking makes this ill-suited to production
+ * code since capabilities obtained in this way can go stale immediately.
+ *
+ * <p>Also see {@link NetworkRequest} which uses the same capabilities
+ * together with {@link ConnectivityManager#requestNetwork} for how to
+ * request the system brings up the kind of network your application needs.
  */
 public final class NetworkCapabilities implements Parcelable {
     private static final String TAG = "NetworkCapabilities";
@@ -622,11 +674,19 @@
 
     /**
      * Indicates that this network should be able to prioritize latency for the internet.
+     *
+     * Starting with {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, requesting this capability with
+     * {@link ConnectivityManager#requestNetwork} requires declaration in the self-certified
+     * network capabilities. See {@link NetworkRequest} for the self-certification documentation.
      */
     public static final int NET_CAPABILITY_PRIORITIZE_LATENCY = 34;
 
     /**
      * Indicates that this network should be able to prioritize bandwidth for the internet.
+     *
+     * Starting with {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, requesting this capability with
+     * {@link ConnectivityManager#requestNetwork} requires declaration in the self-certified
+     * network capabilities. See {@link NetworkRequest} for the self-certification documentation.
      */
     public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35;
 
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index b7a6076..6c351d0 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -54,9 +54,92 @@
 import java.util.Set;
 
 /**
- * Defines a request for a network, made through {@link NetworkRequest.Builder} and used
- * to request a network via {@link ConnectivityManager#requestNetwork} or listen for changes
- * via {@link ConnectivityManager#registerNetworkCallback}.
+ * An object describing a network that the application is interested in.
+ *
+ * <p>@see <a href="https://developer.android.com/training/basics/network-ops/reading-network-state>
+ * this general guide</a> on how to use NetworkCapabilities and related classes.
+ *
+ * NetworkRequest defines a request for a network, made through
+ * {@link NetworkRequest.Builder} and used to request a network via
+ * {@link ConnectivityManager#requestNetwork} or to listen for changes
+ * via the {@link ConnectivityManager#registerNetworkCallback} family of
+ * functions.
+ *
+ * <p>{@link ConnectivityManager#requestNetwork} will try to find a connected
+ * network matching the NetworkRequest, and return it if there is one.
+ * As long as the request is outstanding, the system will try to find the best
+ * possible network that matches the request. The request will keep up the
+ * currently best connected network, and if a better one is found (e.g. cheaper
+ * or faster) the system will bring up that better network to better serve the
+ * request. A request filed with {@link ConnectivityManager#requestNetwork} will
+ * only match one network at a time (the one the system thinks is best), even if
+ * other networks can match the request that are being kept up by other requests.
+ *
+ * For example, an application needing a network with
+ * {@link NetworkCapabilities#NET_CAPABILITY_INTERNET} should use
+ * {@link ConnectivityManager#requestNetwork} to request the system keeps one up.
+ * A general cellular network can satisfy this request, but if the system finds
+ * a free Wi-Fi network which is expected to be faster, it will try and connect
+ * to that Wi-Fi network and switch the request over to it once it is connected.
+ * The cell network may stay connected if there are outstanding requests (from
+ * the same app or from other apps on the system) that match the cell network
+ * but not the Wi-Fi network, such as a request with {@link NetworkCapabilities#NET_CAPABILITY_MMS}.
+ *
+ * When a network is no longer needed to serve any request, the system can
+ * tear it down at any time and usually does so immediately, so make sure to
+ * keep up requests for the networks your app needs.
+ *
+ * <p>By contrast, requests filed with {@link ConnectivityManager#registerNetworkCallback}
+ * will receive callbacks for all matching networks, and will not cause the system to
+ * keep up the networks they match. Use this to listen to networks that the device is
+ * connected to, but that you don't want the system to keep up for your use case.
+ *
+ * <p>Applications build a NetworkRequest and pass it to one of the
+ * {@link ConnectivityManager} methods above together with a
+ * {@link ConnectivityManager.NetworkCallback} object. The callback
+ * will then start receiving method calls about networks that match
+ * the request.
+ *
+ * <p>Networks are brought up and/or matched according to the capabilities
+ * set in the builder. For example, a request with
+ * {@link NetworkCapabilities#NET_CAPABILITY_MMS} lets the system match
+ * and/or bring up a network that is capable to send MMS. A request with
+ * {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED} matches a network
+ * that doesn't charge the user for usage. See
+ * {@link NetworkCapabilities} for a list of capabilities and their
+ * description.
+ *
+ * <p>While all capabilities can be matched with the
+ * {@link ConnectivityManager#registerNetworkCallback} family of methods,
+ * not all capabilities can be used to request that the system brings
+ * up a network with {@link ConnectivityManager#requestNetwork}. For example,
+ * an application cannot use {@link ConnectivityManager#requestNetwork} to
+ * ask the system to bring up a network with
+ * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}, because the
+ * system won't know if a network has a captive portal before it connects
+ * to that network. Similarly, some capabilities may require a specific
+ * permission or privilege to be requested.
+ *
+ * Look up the specific capability and the {@link ConnectivityManager#requestNetwork}
+ * method for limitations applicable to each capability.
+ *
+ * <p>Also, starting with {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, some capabilities
+ * require the application to self-certify by explicitly adding the
+ * {@link android.content.pm.PackageManager#PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES}
+ * property in the AndroidManifest.xml, which points to an XML resource file. In the
+ * XML resource file, the application declares what kind of network capabilities the application
+ * wants to have.
+ *
+ * Here is an example self-certification XML resource file :
+ * <pre>
+ *  {@code
+ *  <network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+ *     <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+ *     <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+ * </network-capabilities-declaration>
+ *  }
+ *  </pre>
+ * Look up the specific capability to learn whether its usage requires this self-certification.
  */
 public class NetworkRequest implements Parcelable {
     /**
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 3bc09db..619b64d 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -20,6 +20,7 @@
 import static android.net.nsd.NsdManager.MDNS_DISCOVERY_MANAGER_EVENT;
 import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT;
 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -79,6 +80,7 @@
 import java.nio.charset.Charset;
 import java.nio.charset.CharsetEncoder;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -110,6 +112,34 @@
      */
     private static final String MDNS_ADVERTISER_VERSION = "mdns_advertiser_version";
 
+    /**
+     * Comma-separated list of type:flag mappings indicating the flags to use to allowlist
+     * discovery/advertising using MdnsDiscoveryManager / MdnsAdvertiser for a given type.
+     *
+     * For example _mytype._tcp.local and _othertype._tcp.local would be configured with:
+     * _mytype._tcp:mytype,_othertype._tcp.local:othertype
+     *
+     * In which case the flags:
+     * "mdns_discovery_manager_allowlist_mytype_version",
+     * "mdns_advertiser_allowlist_mytype_version",
+     * "mdns_discovery_manager_allowlist_othertype_version",
+     * "mdns_advertiser_allowlist_othertype_version"
+     * would be used to toggle MdnsDiscoveryManager / MdnsAdvertiser for each type. The flags will
+     * be read with
+     * {@link DeviceConfigUtils#isFeatureEnabled(Context, String, String, String, boolean)}.
+     *
+     * @see #MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX
+     * @see #MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX
+     * @see #MDNS_ALLOWLIST_FLAG_SUFFIX
+     */
+    private static final String MDNS_TYPE_ALLOWLIST_FLAGS = "mdns_type_allowlist_flags";
+
+    private static final String MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX =
+            "mdns_discovery_manager_allowlist_";
+    private static final String MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX =
+            "mdns_advertiser_allowlist_";
+    private static final String MDNS_ALLOWLIST_FLAG_SUFFIX = "_version";
+
     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;
@@ -496,31 +526,6 @@
                 clientInfo.mClientIdForServiceUpdates = 0;
             }
 
-            /**
-             * Check the given service type is valid and construct it to a service type
-             * which can use for discovery / resolution service.
-             *
-             * <p> The valid service type should be 2 labels, or 3 labels if the query is for a
-             * subtype (see RFC6763 7.1). Each label is up to 63 characters and must start with an
-             * underscore; they are alphanumerical characters or dashes or underscore, except the
-             * last one that is just alphanumerical. The last label must be _tcp or _udp.
-             *
-             * @param serviceType the request service type for discovery / resolution service
-             * @return constructed service type or null if the given service type is invalid.
-             */
-            @Nullable
-            private String constructServiceType(String serviceType) {
-                if (TextUtils.isEmpty(serviceType)) return null;
-
-                final Pattern serviceTypePattern = Pattern.compile(
-                        "^(_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]\\.)?"
-                                + "(_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]\\._(?:tcp|udp))$");
-                final Matcher matcher = serviceTypePattern.matcher(serviceType);
-                if (!matcher.matches()) return null;
-                return matcher.group(1) == null
-                        ? serviceType
-                        : matcher.group(1) + "_sub." + matcher.group(2);
-            }
 
             /**
              * Truncate a service name to up to 63 UTF-8 bytes.
@@ -545,6 +550,12 @@
                 return new String(out.array(), 0, out.position(), utf8);
             }
 
+            private void stopDiscoveryManagerRequest(ClientRequest request, int clientId, int id,
+                    ClientInfo clientInfo) {
+                clientInfo.unregisterMdnsListenerFromRequest(request);
+                removeRequestMap(clientId, id, clientInfo);
+            }
+
             @Override
             public boolean processMessage(Message msg) {
                 final ClientInfo clientInfo;
@@ -572,8 +583,9 @@
 
                         final NsdServiceInfo info = args.serviceInfo;
                         id = getUniqueId();
-                        if (mDeps.isMdnsDiscoveryManagerEnabled(mContext)) {
-                            final String serviceType = constructServiceType(info.getServiceType());
+                        final String serviceType = constructServiceType(info.getServiceType());
+                        if (mDeps.isMdnsDiscoveryManagerEnabled(mContext)
+                                || useDiscoveryManagerForType(serviceType)) {
                             if (serviceType == null) {
                                 clientInfo.onDiscoverServicesFailed(clientId,
                                         NsdManager.FAILURE_INTERNAL_ERROR);
@@ -631,11 +643,7 @@
                         // point, so this needs to check the type of the original request to
                         // unregister instead of looking at the flag value.
                         if (request instanceof DiscoveryManagerRequest) {
-                            final MdnsListener listener =
-                                    ((DiscoveryManagerRequest) request).mListener;
-                            mMdnsDiscoveryManager.unregisterListener(
-                                    listener.getListenedServiceType(), listener);
-                            removeRequestMap(clientId, id, clientInfo);
+                            stopDiscoveryManagerRequest(request, clientId, id, clientInfo);
                             clientInfo.onStopDiscoverySucceeded(clientId);
                         } else {
                             removeRequestMap(clientId, id, clientInfo);
@@ -667,10 +675,11 @@
                         }
 
                         id = getUniqueId();
-                        if (mDeps.isMdnsAdvertiserEnabled(mContext)) {
-                            final NsdServiceInfo serviceInfo = args.serviceInfo;
-                            final String serviceType = serviceInfo.getServiceType();
-                            final String registerServiceType = constructServiceType(serviceType);
+                        final NsdServiceInfo serviceInfo = args.serviceInfo;
+                        final String serviceType = serviceInfo.getServiceType();
+                        final String registerServiceType = constructServiceType(serviceType);
+                        if (mDeps.isMdnsAdvertiserEnabled(mContext)
+                                || useAdvertiserForType(registerServiceType)) {
                             if (registerServiceType == null) {
                                 Log.e(TAG, "Invalid service type: " + serviceType);
                                 clientInfo.onRegisterServiceFailed(clientId,
@@ -686,7 +695,7 @@
                             storeAdvertiserRequestMap(clientId, id, clientInfo);
                         } else {
                             maybeStartDaemon();
-                            if (registerService(id, args.serviceInfo)) {
+                            if (registerService(id, serviceInfo)) {
                                 if (DBG) Log.d(TAG, "Register " + clientId + " " + id);
                                 storeLegacyRequestMap(clientId, id, clientInfo, msg.what);
                                 // Return success after mDns reports success
@@ -748,8 +757,9 @@
 
                         final NsdServiceInfo info = args.serviceInfo;
                         id = getUniqueId();
-                        if (mDeps.isMdnsDiscoveryManagerEnabled(mContext)) {
-                            final String serviceType = constructServiceType(info.getServiceType());
+                        final String serviceType = constructServiceType(info.getServiceType());
+                        if (mDeps.isMdnsDiscoveryManagerEnabled(mContext)
+                                || useDiscoveryManagerForType(serviceType)) {
                             if (serviceType == null) {
                                 clientInfo.onResolveServiceFailed(clientId,
                                         NsdManager.FAILURE_INTERNAL_ERROR);
@@ -804,15 +814,22 @@
                             break;
                         }
                         id = request.mGlobalId;
-                        removeRequestMap(clientId, id, clientInfo);
-                        if (stopResolveService(id)) {
+                        // Note isMdnsDiscoveryManagerEnabled may have changed to false at this
+                        // point, so this needs to check the type of the original request to
+                        // unregister instead of looking at the flag value.
+                        if (request instanceof DiscoveryManagerRequest) {
+                            stopDiscoveryManagerRequest(request, clientId, id, clientInfo);
                             clientInfo.onStopResolutionSucceeded(clientId);
                         } else {
-                            clientInfo.onStopResolutionFailed(
-                                    clientId, NsdManager.FAILURE_OPERATION_NOT_RUNNING);
+                            removeRequestMap(clientId, id, clientInfo);
+                            if (stopResolveService(id)) {
+                                clientInfo.onStopResolutionSucceeded(clientId);
+                            } else {
+                                clientInfo.onStopResolutionFailed(
+                                        clientId, NsdManager.FAILURE_OPERATION_NOT_RUNNING);
+                            }
+                            clientInfo.mResolvedService = null;
                         }
-                        clientInfo.mResolvedService = null;
-                        // TODO: Implement the stop resolution with MdnsDiscoveryManager.
                         break;
                     }
                     case NsdManager.REGISTER_SERVICE_CALLBACK:
@@ -1145,17 +1162,27 @@
                                 Log.e(TAG, "Invalid attribute", e);
                             }
                         }
-                        try {
-                            if (serviceInfo.getIpv4Address() != null) {
-                                info.setHost(InetAddresses.parseNumericAddress(
-                                        serviceInfo.getIpv4Address()));
-                            } else {
-                                info.setHost(InetAddresses.parseNumericAddress(
-                                        serviceInfo.getIpv6Address()));
+                        final List<InetAddress> addresses = new ArrayList<>();
+                        for (String ipv4Address : serviceInfo.getIpv4Addresses()) {
+                            try {
+                                addresses.add(InetAddresses.parseNumericAddress(ipv4Address));
+                            } catch (IllegalArgumentException e) {
+                                Log.wtf(TAG, "Invalid ipv4 address", e);
                             }
+                        }
+                        for (String ipv6Address : serviceInfo.getIpv6Addresses()) {
+                            try {
+                                addresses.add(InetAddresses.parseNumericAddress(ipv6Address));
+                            } catch (IllegalArgumentException e) {
+                                Log.wtf(TAG, "Invalid ipv6 address", e);
+                            }
+                        }
+
+                        if (addresses.size() != 0) {
+                            info.setHostAddresses(addresses);
                             clientInfo.onResolveServiceSucceeded(clientId, info);
-                        } catch (IllegalArgumentException e) {
-                            Log.wtf(TAG, "Invalid address in RESOLVE_SERVICE_SUCCEEDED", e);
+                        } else {
+                            // No address. Notify resolution failure.
                             clientInfo.onResolveServiceFailed(
                                     clientId, NsdManager.FAILURE_INTERNAL_ERROR);
                         }
@@ -1165,10 +1192,7 @@
                             Log.wtf(TAG, "non-DiscoveryManager request in DiscoveryManager event");
                             break;
                         }
-                        final MdnsListener listener = ((DiscoveryManagerRequest) request).mListener;
-                        mMdnsDiscoveryManager.unregisterListener(
-                                listener.getListenedServiceType(), listener);
-                        removeRequestMap(clientId, transactionId, clientInfo);
+                        stopDiscoveryManagerRequest(request, clientId, transactionId, clientInfo);
                         break;
                     }
                     default:
@@ -1228,6 +1252,34 @@
         return sb.toString();
     }
 
+    /**
+     * Check the given service type is valid and construct it to a service type
+     * which can use for discovery / resolution service.
+     *
+     * <p> The valid service type should be 2 labels, or 3 labels if the query is for a
+     * subtype (see RFC6763 7.1). Each label is up to 63 characters and must start with an
+     * underscore; they are alphanumerical characters or dashes or underscore, except the
+     * last one that is just alphanumerical. The last label must be _tcp or _udp.
+     *
+     * @param serviceType the request service type for discovery / resolution service
+     * @return constructed service type or null if the given service type is invalid.
+     */
+    @Nullable
+    public static String constructServiceType(String serviceType) {
+        if (TextUtils.isEmpty(serviceType)) return null;
+
+        final Pattern serviceTypePattern = Pattern.compile(
+                "^(_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]\\.)?"
+                        + "(_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]\\._(?:tcp|udp))"
+                        // Drop '.' at the end of service type that is compatible with old backend.
+                        + "\\.?$");
+        final Matcher matcher = serviceTypePattern.matcher(serviceType);
+        if (!matcher.matches()) return null;
+        return matcher.group(1) == null
+                ? matcher.group(2)
+                : matcher.group(1) + "_sub." + matcher.group(2);
+    }
+
     @VisibleForTesting
     NsdService(Context ctx, Handler handler, long cleanupDelayMs) {
         this(ctx, handler, cleanupDelayMs, new Dependencies());
@@ -1281,6 +1333,24 @@
         }
 
         /**
+         * Get the type allowlist flag value.
+         * @see #MDNS_TYPE_ALLOWLIST_FLAGS
+         */
+        @Nullable
+        public String getTypeAllowlistFlags() {
+            return DeviceConfigUtils.getDeviceConfigProperty(NAMESPACE_TETHERING,
+                    MDNS_TYPE_ALLOWLIST_FLAGS, null);
+        }
+
+        /**
+         * @see DeviceConfigUtils#isFeatureEnabled(Context, String, String, String, boolean)
+         */
+        public boolean isFeatureEnabled(Context context, String feature) {
+            return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_TETHERING,
+                    feature, DeviceConfigUtils.TETHERING_MODULE_NAME, false /* defaultEnabled */);
+        }
+
+        /**
          * @see MdnsDiscoveryManager
          */
         public MdnsDiscoveryManager makeMdnsDiscoveryManager(
@@ -1305,6 +1375,41 @@
         }
     }
 
+    /**
+     * Return whether a type is allowlisted to use the Java backend.
+     * @param type The service type
+     * @param flagPrefix One of {@link #MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX} or
+     *                   {@link #MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX}.
+     */
+    private boolean isTypeAllowlistedForJavaBackend(@Nullable String type,
+            @NonNull String flagPrefix) {
+        if (type == null) return false;
+        final String typesConfig = mDeps.getTypeAllowlistFlags();
+        if (TextUtils.isEmpty(typesConfig)) return false;
+
+        final String mappingPrefix = type + ":";
+        String mappedFlag = null;
+        for (String mapping : TextUtils.split(typesConfig, ",")) {
+            if (mapping.startsWith(mappingPrefix)) {
+                mappedFlag = mapping.substring(mappingPrefix.length());
+                break;
+            }
+        }
+
+        if (mappedFlag == null) return false;
+
+        return mDeps.isFeatureEnabled(mContext,
+                flagPrefix + mappedFlag + MDNS_ALLOWLIST_FLAG_SUFFIX);
+    }
+
+    private boolean useDiscoveryManagerForType(@Nullable String type) {
+        return isTypeAllowlistedForJavaBackend(type, MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX);
+    }
+
+    private boolean useAdvertiserForType(@Nullable String type) {
+        return isTypeAllowlistedForJavaBackend(type, MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX);
+    }
+
     public static NsdService create(Context context) {
         HandlerThread thread = new HandlerThread(TAG);
         thread.start();
@@ -1695,6 +1800,13 @@
             mIsPreSClient = true;
         }
 
+        private void unregisterMdnsListenerFromRequest(ClientRequest request) {
+            final MdnsListener listener =
+                    ((DiscoveryManagerRequest) request).mListener;
+            mMdnsDiscoveryManager.unregisterListener(
+                    listener.getListenedServiceType(), listener);
+        }
+
         // Remove any pending requests from the global map when we get rid of a client,
         // and send cancellations to the daemon.
         private void expungeAllRequests() {
@@ -1710,10 +1822,7 @@
                 }
 
                 if (request instanceof DiscoveryManagerRequest) {
-                    final MdnsListener listener =
-                            ((DiscoveryManagerRequest) request).mListener;
-                    mMdnsDiscoveryManager.unregisterListener(
-                            listener.getListenedServiceType(), listener);
+                    unregisterMdnsListenerFromRequest(request);
                     continue;
                 }
 
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
index a6e3762..be2555b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
@@ -25,6 +25,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -36,8 +37,8 @@
     private final List<MdnsPointerRecord> pointerRecords;
     private MdnsServiceRecord serviceRecord;
     private MdnsTextRecord textRecord;
-    private MdnsInetAddressRecord inet4AddressRecord;
-    private MdnsInetAddressRecord inet6AddressRecord;
+    @NonNull private List<MdnsInetAddressRecord> inet4AddressRecords;
+    @NonNull private List<MdnsInetAddressRecord> inet6AddressRecords;
     private long lastUpdateTime;
     private final int interfaceIndex;
     @Nullable private final Network network;
@@ -49,6 +50,8 @@
         lastUpdateTime = now;
         records = new LinkedList<>();
         pointerRecords = new LinkedList<>();
+        inet4AddressRecords = new ArrayList<>();
+        inet6AddressRecords = new ArrayList<>();
         this.interfaceIndex = interfaceIndex;
         this.network = network;
         this.serviceName = serviceName;
@@ -59,8 +62,8 @@
         pointerRecords = new ArrayList<>(base.pointerRecords);
         serviceRecord = base.serviceRecord;
         textRecord = base.textRecord;
-        inet4AddressRecord = base.inet4AddressRecord;
-        inet6AddressRecord = base.inet6AddressRecord;
+        inet4AddressRecords = new ArrayList<>(base.inet4AddressRecords);
+        inet6AddressRecords = new ArrayList<>(base.inet6AddressRecords);
         lastUpdateTime = base.lastUpdateTime;
         serviceName = base.serviceName;
         interfaceIndex = base.interfaceIndex;
@@ -94,8 +97,8 @@
             if (recordsAreSame(pointerRecord, pointerRecords.get(existing))) {
                 return false;
             }
-            pointerRecords.remove(existing);
-            records.remove(existing);
+            final MdnsRecord record = pointerRecords.remove(existing);
+            records.remove(record);
         }
         pointerRecords.add(pointerRecord);
         records.add(pointerRecord);
@@ -201,46 +204,60 @@
         return textRecord != null;
     }
 
-    /** Sets the IPv4 address record. */
-    public synchronized boolean setInet4AddressRecord(
-            @Nullable MdnsInetAddressRecord newInet4AddressRecord) {
-        if (recordsAreSame(this.inet4AddressRecord, newInet4AddressRecord)) {
-            return false;
+    /** Add the IPv4 address record. */
+    public synchronized boolean addInet4AddressRecord(
+            @NonNull MdnsInetAddressRecord newInet4AddressRecord) {
+        final int existing = inet4AddressRecords.indexOf(newInet4AddressRecord);
+        if (existing >= 0) {
+            if (recordsAreSame(newInet4AddressRecord, inet4AddressRecords.get(existing))) {
+                return false;
+            }
+            final MdnsRecord record = inet4AddressRecords.remove(existing);
+            records.remove(record);
         }
-        if (this.inet4AddressRecord != null) {
-            records.remove(this.inet4AddressRecord);
-            this.inet4AddressRecord = null;
-        }
-        if (newInet4AddressRecord != null && newInet4AddressRecord.getInet4Address() != null) {
-            this.inet4AddressRecord = newInet4AddressRecord;
-            records.add(this.inet4AddressRecord);
-        }
+        inet4AddressRecords.add(newInet4AddressRecord);
+        records.add(newInet4AddressRecord);
         return true;
     }
 
-    /** Gets the IPv4 address record. */
+    /** Gets the IPv4 address records. */
+    @NonNull
+    public synchronized List<MdnsInetAddressRecord> getInet4AddressRecords() {
+        return Collections.unmodifiableList(inet4AddressRecords);
+    }
+
+    /** Return the first IPv4 address record or null if no record. */
+    @Nullable
     public synchronized MdnsInetAddressRecord getInet4AddressRecord() {
-        return inet4AddressRecord;
+        return inet4AddressRecords.isEmpty() ? null : inet4AddressRecords.get(0);
     }
 
+    /** Check whether response has IPv4 address record */
     public synchronized boolean hasInet4AddressRecord() {
-        return inet4AddressRecord != null;
+        return !inet4AddressRecords.isEmpty();
     }
 
-    /** Sets the IPv6 address record. */
-    public synchronized boolean setInet6AddressRecord(
-            @Nullable MdnsInetAddressRecord newInet6AddressRecord) {
-        if (recordsAreSame(this.inet6AddressRecord, newInet6AddressRecord)) {
-            return false;
+    /** Clear all IPv4 address records */
+    synchronized void clearInet4AddressRecords() {
+        for (MdnsInetAddressRecord record : inet4AddressRecords) {
+            records.remove(record);
         }
-        if (this.inet6AddressRecord != null) {
-            records.remove(this.inet6AddressRecord);
-            this.inet6AddressRecord = null;
+        inet4AddressRecords.clear();
+    }
+
+    /** Sets the IPv6 address records. */
+    public synchronized boolean addInet6AddressRecord(
+            @NonNull MdnsInetAddressRecord newInet6AddressRecord) {
+        final int existing = inet6AddressRecords.indexOf(newInet6AddressRecord);
+        if (existing >= 0) {
+            if (recordsAreSame(newInet6AddressRecord, inet6AddressRecords.get(existing))) {
+                return false;
+            }
+            final MdnsRecord record = inet6AddressRecords.remove(existing);
+            records.remove(record);
         }
-        if (newInet6AddressRecord != null && newInet6AddressRecord.getInet6Address() != null) {
-            this.inet6AddressRecord = newInet6AddressRecord;
-            records.add(this.inet6AddressRecord);
-        }
+        inet6AddressRecords.add(newInet6AddressRecord);
+        records.add(newInet6AddressRecord);
         return true;
     }
 
@@ -260,13 +277,28 @@
         return network;
     }
 
-    /** Gets the IPv6 address record. */
-    public synchronized MdnsInetAddressRecord getInet6AddressRecord() {
-        return inet6AddressRecord;
+    /** Gets all IPv6 address records. */
+    public synchronized List<MdnsInetAddressRecord> getInet6AddressRecords() {
+        return Collections.unmodifiableList(inet6AddressRecords);
     }
 
+    /** Return the first IPv6 address record or null if no record. */
+    @Nullable
+    public synchronized MdnsInetAddressRecord getInet6AddressRecord() {
+        return inet6AddressRecords.isEmpty() ? null : inet6AddressRecords.get(0);
+    }
+
+    /** Check whether response has IPv6 address record */
     public synchronized boolean hasInet6AddressRecord() {
-        return inet6AddressRecord != null;
+        return !inet6AddressRecords.isEmpty();
+    }
+
+    /** Clear all IPv6 address records */
+    synchronized void clearInet6AddressRecords() {
+        for (MdnsInetAddressRecord record : inet6AddressRecords) {
+            records.remove(record);
+        }
+        inet6AddressRecords.clear();
     }
 
     /** Gets all of the records. */
@@ -283,22 +315,22 @@
         if (this.serviceRecord == null) return false;
         boolean dropAddressRecords = false;
 
-        if (this.inet4AddressRecord != null) {
+        for (MdnsInetAddressRecord inetAddressRecord : getInet4AddressRecords()) {
             if (!Arrays.equals(
-                    this.serviceRecord.getServiceHost(), this.inet4AddressRecord.getName())) {
+                    this.serviceRecord.getServiceHost(), inetAddressRecord.getName())) {
                 dropAddressRecords = true;
             }
         }
-        if (this.inet6AddressRecord != null) {
+        for (MdnsInetAddressRecord inetAddressRecord : getInet6AddressRecords()) {
             if (!Arrays.equals(
-                    this.serviceRecord.getServiceHost(), this.inet6AddressRecord.getName())) {
+                    this.serviceRecord.getServiceHost(), inetAddressRecord.getName())) {
                 dropAddressRecords = true;
             }
         }
 
         if (dropAddressRecords) {
-            setInet4AddressRecord(null);
-            setInet6AddressRecord(null);
+            clearInet4AddressRecords();
+            clearInet6AddressRecords();
             return true;
         }
         return false;
@@ -312,7 +344,7 @@
     public synchronized boolean isComplete() {
         return (serviceRecord != null)
                 && (textRecord != null)
-                && (inet4AddressRecord != null || inet6AddressRecord != null);
+                && (!inet4AddressRecords.isEmpty() || !inet6AddressRecords.isEmpty());
     }
 
     /**
@@ -378,13 +410,13 @@
             ++count;
         }
 
-        if (inet4AddressRecord != null) {
-            inet4AddressRecord.write(writer, now);
+        for (MdnsInetAddressRecord inetAddressRecord : inet4AddressRecords) {
+            inetAddressRecord.write(writer, now);
             ++count;
         }
 
-        if (inet6AddressRecord != null) {
-            inet6AddressRecord.write(writer, now);
+        for (MdnsInetAddressRecord inetAddressRecord : inet6AddressRecords) {
+            inetAddressRecord.write(writer, now);
             ++count;
         }
 
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 7878b0b..0151202 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
@@ -205,39 +205,79 @@
             }
         }
 
-        // Loop 3: find A and AAAA records, which reference the host name in the SRV record.
+        // Loop 3-1: find A and AAAA records and clear addresses if the cache-flush bit set, which
+        //           reference the host name in the SRV record.
+        final List<MdnsInetAddressRecord> inetRecords = new ArrayList<>();
         for (MdnsRecord record : records) {
             if (record instanceof MdnsInetAddressRecord) {
                 MdnsInetAddressRecord inetRecord = (MdnsInetAddressRecord) record;
+                inetRecords.add(inetRecord);
                 if (allowMultipleSrvRecordsPerHost) {
                     List<MdnsResponse> matchingResponses =
                             findResponsesWithHostName(responses, inetRecord.getName());
                     for (MdnsResponse response : matchingResponses) {
-                        if (assignInetRecord(response, inetRecord)) {
-                            modified.add(response);
+                        // Per RFC6762 10.2, clear all address records if the cache-flush bit set.
+                        // This bit, the cache-flush bit, tells neighboring hosts
+                        // that this is not a shared record type.  Instead of merging this new
+                        // record additively into the cache in addition to any previous records with
+                        // the same name, rrtype, and rrclass, all old records with that name,
+                        // rrtype, and rrclass that were received more than one second ago are
+                        // declared invalid, and marked to expire from the cache in one second.
+                        if (inetRecord.getCacheFlush()) {
+                            response.clearInet4AddressRecords();
+                            response.clearInet6AddressRecords();
                         }
                     }
                 } else {
                     MdnsResponse response =
                             findResponseWithHostName(responses, inetRecord.getName());
                     if (response != null) {
-                        if (assignInetRecord(response, inetRecord)) {
-                            modified.add(response);
+                        // Per RFC6762 10.2, clear all address records if the cache-flush bit set.
+                        // This bit, the cache-flush bit, tells neighboring hosts
+                        // that this is not a shared record type.  Instead of merging this new
+                        // record additively into the cache in addition to any previous records with
+                        // the same name, rrtype, and rrclass, all old records with that name,
+                        // rrtype, and rrclass that were received more than one second ago are
+                        // declared invalid, and marked to expire from the cache in one second.
+                        if (inetRecord.getCacheFlush()) {
+                            response.clearInet4AddressRecords();
+                            response.clearInet6AddressRecords();
                         }
                     }
                 }
             }
         }
 
+        // Loop 3-2: Assign addresses, which reference the host name in the SRV record.
+        for (MdnsInetAddressRecord inetRecord : inetRecords) {
+            if (allowMultipleSrvRecordsPerHost) {
+                List<MdnsResponse> matchingResponses =
+                        findResponsesWithHostName(responses, inetRecord.getName());
+                for (MdnsResponse response : matchingResponses) {
+                    if (assignInetRecord(response, inetRecord)) {
+                        modified.add(response);
+                    }
+                }
+            } else {
+                MdnsResponse response =
+                        findResponseWithHostName(responses, inetRecord.getName());
+                if (response != null) {
+                    if (assignInetRecord(response, inetRecord)) {
+                        modified.add(response);
+                    }
+                }
+            }
+        }
+
         return modified;
     }
 
     private static boolean assignInetRecord(
             MdnsResponse response, MdnsInetAddressRecord inetRecord) {
         if (inetRecord.getInet4Address() != null) {
-            return response.setInet4AddressRecord(inetRecord);
+            return response.addInet4AddressRecord(inetRecord);
         } else if (inetRecord.getInet6Address() != null) {
-            return response.setInet6AddressRecord(inetRecord);
+            return response.addInet6AddressRecord(inetRecord);
         }
         return false;
     }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
index 64df46e..78df6df 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
@@ -57,8 +57,8 @@
                             source.createStringArrayList(),
                             source.createStringArray(),
                             source.readInt(),
-                            source.readString(),
-                            source.readString(),
+                            source.createStringArrayList(),
+                            source.createStringArrayList(),
                             source.createStringArrayList(),
                             source.createTypedArrayList(TextEntry.CREATOR),
                             source.readInt(),
@@ -76,10 +76,10 @@
     private final List<String> subtypes;
     private final String[] hostName;
     private final int port;
-    @Nullable
-    private final String ipv4Address;
-    @Nullable
-    private final String ipv6Address;
+    @NonNull
+    private final List<String> ipv4Addresses;
+    @NonNull
+    private final List<String> ipv6Addresses;
     final List<String> textStrings;
     @Nullable
     final List<TextEntry> textEntries;
@@ -105,8 +105,8 @@
                 subtypes,
                 hostName,
                 port,
-                ipv4Address,
-                ipv6Address,
+                List.of(ipv4Address),
+                List.of(ipv6Address),
                 textStrings,
                 /* textEntries= */ null,
                 /* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED,
@@ -130,8 +130,8 @@
                 subtypes,
                 hostName,
                 port,
-                ipv4Address,
-                ipv6Address,
+                List.of(ipv4Address),
+                List.of(ipv6Address),
                 textStrings,
                 textEntries,
                 /* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED,
@@ -160,8 +160,8 @@
                 subtypes,
                 hostName,
                 port,
-                ipv4Address,
-                ipv6Address,
+                List.of(ipv4Address),
+                List.of(ipv6Address),
                 textStrings,
                 textEntries,
                 interfaceIndex,
@@ -179,8 +179,8 @@
             @Nullable List<String> subtypes,
             String[] hostName,
             int port,
-            @Nullable String ipv4Address,
-            @Nullable String ipv6Address,
+            @NonNull List<String> ipv4Addresses,
+            @NonNull List<String> ipv6Addresses,
             @Nullable List<String> textStrings,
             @Nullable List<TextEntry> textEntries,
             int interfaceIndex,
@@ -193,8 +193,8 @@
         }
         this.hostName = hostName;
         this.port = port;
-        this.ipv4Address = ipv4Address;
-        this.ipv6Address = ipv6Address;
+        this.ipv4Addresses = new ArrayList<>(ipv4Addresses);
+        this.ipv6Addresses = new ArrayList<>(ipv6Addresses);
         this.textStrings = new ArrayList<>();
         if (textStrings != null) {
             this.textStrings.addAll(textStrings);
@@ -260,16 +260,41 @@
         return port;
     }
 
-    /** Returns the IPV4 address of this service instance. */
+    /** Returns the IPV4 addresses of this service instance. */
+    @NonNull
+    public List<String> getIpv4Addresses() {
+        return Collections.unmodifiableList(ipv4Addresses);
+    }
+
+    /**
+     * Returns the first IPV4 address of this service instance.
+     *
+     * @deprecated Use {@link #getIpv4Addresses()} to get the entire list of IPV4
+     * addresses for
+     * the host.
+     */
     @Nullable
+    @Deprecated
     public String getIpv4Address() {
-        return ipv4Address;
+        return ipv4Addresses.isEmpty() ? null : ipv4Addresses.get(0);
     }
 
     /** Returns the IPV6 address of this service instance. */
+    @NonNull
+    public List<String> getIpv6Addresses() {
+        return Collections.unmodifiableList(ipv6Addresses);
+    }
+
+    /**
+     * Returns the first IPV6 address of this service instance.
+     *
+     * @deprecated Use {@link #getIpv6Addresses()} to get the entire list of IPV6 addresses for
+     * the host.
+     */
     @Nullable
+    @Deprecated
     public String getIpv6Address() {
-        return ipv6Address;
+        return ipv6Addresses.isEmpty() ? null : ipv6Addresses.get(0);
     }
 
     /**
@@ -333,8 +358,8 @@
         out.writeStringList(subtypes);
         out.writeStringArray(hostName);
         out.writeInt(port);
-        out.writeString(ipv4Address);
-        out.writeString(ipv6Address);
+        out.writeStringList(ipv4Addresses);
+        out.writeStringList(ipv6Addresses);
         out.writeStringList(textStrings);
         out.writeTypedList(textEntries);
         out.writeInt(interfaceIndex);
@@ -343,13 +368,16 @@
 
     @Override
     public String toString() {
-        return String.format(
-                Locale.ROOT,
-                "Name: %s, subtypes: %s, ip: %s, port: %d",
-                serviceInstanceName,
-                TextUtils.join(",", subtypes),
-                ipv4Address,
-                port);
+        return "Name: " + serviceInstanceName
+                + ", type: " + TextUtils.join(".", serviceType)
+                + ", subtypes: " + TextUtils.join(",", subtypes)
+                + ", ip: " + ipv4Addresses
+                + ", ipv6: " + ipv6Addresses
+                + ", port: " + port
+                + ", interfaceIndex: " + interfaceIndex
+                + ", network: " + network
+                + ", textStrings: " + textStrings
+                + ", textEntries: " + textEntries;
     }
 
 
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 f886948..df270bb 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -115,15 +115,19 @@
             port = response.getServiceRecord().getServicePort();
         }
 
-        String ipv4Address = null;
-        String ipv6Address = null;
+        final List<String> ipv4Addresses = new ArrayList<>();
+        final List<String> ipv6Addresses = new ArrayList<>();
         if (response.hasInet4AddressRecord()) {
-            Inet4Address inet4Address = response.getInet4AddressRecord().getInet4Address();
-            ipv4Address = (inet4Address == null) ? null : inet4Address.getHostAddress();
+            for (MdnsInetAddressRecord inetAddressRecord : response.getInet4AddressRecords()) {
+                final Inet4Address inet4Address = inetAddressRecord.getInet4Address();
+                ipv4Addresses.add((inet4Address == null) ? null : inet4Address.getHostAddress());
+            }
         }
         if (response.hasInet6AddressRecord()) {
-            Inet6Address inet6Address = response.getInet6AddressRecord().getInet6Address();
-            ipv6Address = (inet6Address == null) ? null : inet6Address.getHostAddress();
+            for (MdnsInetAddressRecord inetAddressRecord : response.getInet6AddressRecords()) {
+                final Inet6Address inet6Address = inetAddressRecord.getInet6Address();
+                ipv6Addresses.add((inet6Address == null) ? null : inet6Address.getHostAddress());
+            }
         }
         String serviceInstanceName = response.getServiceInstanceName();
         if (serviceInstanceName == null) {
@@ -143,8 +147,8 @@
                 response.getSubtypes(),
                 hostName,
                 port,
-                ipv4Address,
-                ipv6Address,
+                ipv4Addresses,
+                ipv6Addresses,
                 textStrings,
                 textEntries,
                 response.getInterfaceIndex(),
diff --git a/tests/common/java/android/net/netstats/NetworkTemplateTest.kt b/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
index c2eacbc..fb6759e 100644
--- a/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
+++ b/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
@@ -165,23 +165,6 @@
     }
 
     @Test
-    fun testUnsupportedAppUsageConstructor() {
-        val templateMobile = NetworkTemplate(MATCH_MOBILE, null /* subscriberId */,
-                null /* wifiNetworkKey */)
-        val templateMobileWildcard = NetworkTemplate(6 /* MATCH_MOBILE_WILDCARD */,
-                null /* subscriberId */, null /* wifiNetworkKey */)
-        val templateWifiWildcard = NetworkTemplate(7 /* MATCH_WIFI_WILDCARD */,
-                null /* subscriberId */,
-                null /* wifiNetworkKey */)
-
-        assertEquals(NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build(),
-                templateMobile)
-        assertEquals(NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build(),
-                templateMobileWildcard)
-        assertEquals(NetworkTemplate.Builder(MATCH_WIFI).build(), templateWifiWildcard)
-    }
-
-    @Test
     fun testBuilderWifiNetworkKeys() {
         // Verify template builder which generates same template with the given different
         // sequence keys.
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 053212b..3dc5647 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -21,6 +21,7 @@
 import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
 import static android.net.nsd.NsdManager.FAILURE_OPERATION_NOT_RUNNING;
 
+import static com.android.server.NsdService.constructServiceType;
 import static com.android.testutils.ContextUtils.mockService;
 
 import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
@@ -895,8 +896,8 @@
                 List.of(), /* subtypes */
                 new String[] {"android", "local"}, /* hostName */
                 12345, /* port */
-                IPV4_ADDRESS,
-                IPV6_ADDRESS,
+                List.of(IPV4_ADDRESS),
+                List.of(IPV6_ADDRESS),
                 List.of(), /* textStrings */
                 List.of(), /* textEntries */
                 1234, /* interfaceIndex */
@@ -915,8 +916,8 @@
                 null, /* subtypes */
                 null, /* hostName */
                 0, /* port */
-                null, /* ipv4Address */
-                null, /* ipv6Address */
+                List.of(), /* ipv4Address */
+                List.of(), /* ipv6Address */
                 null, /* textStrings */
                 null, /* textEntries */
                 1234, /* interfaceIndex */
@@ -992,8 +993,8 @@
                 List.of(), /* subtypes */
                 new String[]{"android", "local"}, /* hostName */
                 PORT,
-                IPV4_ADDRESS,
-                IPV6_ADDRESS,
+                List.of(IPV4_ADDRESS),
+                List.of("2001:db8::1", "2001:db8::2"),
                 List.of() /* textStrings */,
                 List.of(MdnsServiceInfo.TextEntry.fromBytes(new byte[]{
                         'k', 'e', 'y', '=', (byte) 0xFF, (byte) 0xFE})) /* textEntries */,
@@ -1013,6 +1014,11 @@
         assertEquals(1, info.getAttributes().size());
         assertArrayEquals(new byte[]{(byte) 0xFF, (byte) 0xFE}, info.getAttributes().get("key"));
         assertEquals(parseNumericAddress(IPV4_ADDRESS), info.getHost());
+        assertEquals(3, info.getHostAddresses().size());
+        assertTrue(info.getHostAddresses().stream().anyMatch(
+                address -> address.equals(parseNumericAddress("2001:db8::1"))));
+        assertTrue(info.getHostAddresses().stream().anyMatch(
+                address -> address.equals(parseNumericAddress("2001:db8::2"))));
         assertEquals(network, info.getNetwork());
 
         // Verify the listener has been unregistered.
@@ -1057,6 +1063,54 @@
     }
 
     @Test
+    public void testTypeSpecificFeatureFlagging() {
+        doReturn("_type1._tcp:flag1,_type2._tcp:flag2").when(mDeps).getTypeAllowlistFlags();
+        doReturn(true).when(mDeps).isFeatureEnabled(any(),
+                eq("mdns_discovery_manager_allowlist_flag1_version"));
+        doReturn(true).when(mDeps).isFeatureEnabled(any(),
+                eq("mdns_advertiser_allowlist_flag2_version"));
+
+        final NsdManager client = connectClient(mService);
+        final NsdServiceInfo service1 = new NsdServiceInfo(SERVICE_NAME, "_type1._tcp");
+        service1.setHostAddresses(List.of(parseNumericAddress("2001:db8::123")));
+        service1.setPort(1234);
+        final NsdServiceInfo service2 = new NsdServiceInfo(SERVICE_NAME, "_type2._tcp");
+        service2.setHostAddresses(List.of(parseNumericAddress("2001:db8::123")));
+        service2.setPort(1234);
+
+        client.discoverServices(service1.getServiceType(),
+                NsdManager.PROTOCOL_DNS_SD, mock(DiscoveryListener.class));
+        client.discoverServices(service2.getServiceType(),
+                NsdManager.PROTOCOL_DNS_SD, mock(DiscoveryListener.class));
+        waitForIdle();
+
+        // The DiscoveryManager is enabled for _type1 but not _type2
+        verify(mDiscoveryManager).registerListener(eq("_type1._tcp.local"), any(), any());
+        verify(mDiscoveryManager, never()).registerListener(
+                eq("_type2._tcp.local"), any(), any());
+
+        client.resolveService(service1, mock(ResolveListener.class));
+        client.resolveService(service2, mock(ResolveListener.class));
+        waitForIdle();
+
+        // Same behavior for resolve
+        verify(mDiscoveryManager, times(2)).registerListener(
+                eq("_type1._tcp.local"), any(), any());
+        verify(mDiscoveryManager, never()).registerListener(
+                eq("_type2._tcp.local"), any(), any());
+
+        client.registerService(service1, NsdManager.PROTOCOL_DNS_SD,
+                mock(RegistrationListener.class));
+        client.registerService(service2, NsdManager.PROTOCOL_DNS_SD,
+                mock(RegistrationListener.class));
+        waitForIdle();
+
+        // The advertiser is enabled for _type2 but not _type1
+        verify(mAdvertiser, never()).addService(anyInt(), argThat(info -> matches(info, service1)));
+        verify(mAdvertiser).addService(anyInt(), argThat(info -> matches(info, service2)));
+    }
+
+    @Test
     public void testAdvertiseWithMdnsAdvertiser() {
         setMdnsAdvertiserEnabled();
 
@@ -1152,6 +1206,50 @@
                 argThat(info -> matches(info, new NsdServiceInfo(regInfo.getServiceName(), null))));
     }
 
+    @Test
+    public void testStopServiceResolutionWithMdnsDiscoveryManager() {
+        setMdnsDiscoveryManagerEnabled();
+
+        final NsdManager client = connectClient(mService);
+        final ResolveListener resolveListener = mock(ResolveListener.class);
+        final Network network = new Network(999);
+        final String serviceType = "_nsd._service._tcp";
+        final String constructedServiceType = "_nsd._sub._service._tcp.local";
+        final ArgumentCaptor<MdnsServiceBrowserListener> listenerCaptor =
+                ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, serviceType);
+        request.setNetwork(network);
+        client.resolveService(request, resolveListener);
+        waitForIdle();
+        verify(mSocketProvider).startMonitoringSockets();
+        verify(mDiscoveryManager).registerListener(eq(constructedServiceType),
+                listenerCaptor.capture(), argThat(options -> network.equals(options.getNetwork())));
+
+        client.stopServiceResolution(resolveListener);
+        waitForIdle();
+
+        // Verify the listener has been unregistered.
+        verify(mDiscoveryManager, timeout(TIMEOUT_MS))
+                .unregisterListener(eq(constructedServiceType), eq(listenerCaptor.getValue()));
+        verify(resolveListener, timeout(TIMEOUT_MS)).onResolutionStopped(argThat(ns ->
+                request.getServiceName().equals(ns.getServiceName())
+                        && request.getServiceType().equals(ns.getServiceType())));
+        verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
+    }
+
+    @Test
+    public void testConstructServiceType() {
+        final String serviceType1 = "test._tcp";
+        final String serviceType2 = "_test._quic";
+        final String serviceType3 = "_123._udp.";
+        final String serviceType4 = "_TEST._999._tcp.";
+
+        assertEquals(null, constructServiceType(serviceType1));
+        assertEquals(null, constructServiceType(serviceType2));
+        assertEquals("_123._udp", constructServiceType(serviceType3));
+        assertEquals("_TEST._sub._999._tcp", constructServiceType(serviceType4));
+    }
+
     private void waitForIdle() {
         HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
     }
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 d83c0dd..a80c078 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
@@ -158,16 +158,16 @@
 
     // MDNS record for name "testhost1" with an IPv4 address of 10.1.2.3
     private static final byte[] DATAIN_IPV4_1 = HexDump.hexStringToByteArray(
-            "0974657374686f73743100000100010000007800040a010203");
+            "0974657374686f73743100000180010000007800040a010203");
     // MDNS record for name "testhost1" with an IPv4 address of 10.1.2.4
     private static final byte[] DATAIN_IPV4_2 = HexDump.hexStringToByteArray(
-            "0974657374686f73743100000100010000007800040a010204");
+            "0974657374686f73743100000180010000007800040a010204");
     // MDNS record w/name "testhost1" & IPv6 address of aabb:ccdd:1122:3344:a0b0:c0d0:1020:3040
     private static final byte[] DATAIN_IPV6_1 = HexDump.hexStringToByteArray(
-            "0974657374686f73743100001c0001000000780010aabbccdd11223344a0b0c0d010203040");
+            "0974657374686f73743100001c8001000000780010aabbccdd11223344a0b0c0d010203040");
     // MDNS record w/name "testhost1" & IPv6 address of aabb:ccdd:1122:3344:a0b0:c0d0:1020:3030
     private static final byte[] DATAIN_IPV6_2 = HexDump.hexStringToByteArray(
-            "0974657374686f73743100001c0001000000780010aabbccdd11223344a0b0c0d010203030");
+            "0974657374686f73743100001c8001000000780010aabbccdd11223344a0b0c0d010203030");
     // MDNS record w/name "test" & PTR to foo.bar.quxx
     private static final byte[] DATAIN_PTR_1 = HexDump.hexStringToByteArray(
             "047465737400000C000100001194000E03666F6F03626172047175787800");
@@ -295,15 +295,15 @@
         assertTrue(response.isComplete());
 
         response = new MdnsResponse(responses.valueAt(0));
-        response.setInet4AddressRecord(null);
+        response.clearInet4AddressRecords();
         assertFalse(response.isComplete());
 
-        response.setInet6AddressRecord(new MdnsInetAddressRecord(new String[] { "testhostname" },
+        response.addInet6AddressRecord(new MdnsInetAddressRecord(new String[] { "testhostname" },
                 0L /* receiptTimeMillis */, false /* cacheFlush */, 1234L /* ttlMillis */,
                 parseNumericAddress("2008:db1::123")));
         assertTrue(response.isComplete());
 
-        response.setInet6AddressRecord(null);
+        response.clearInet6AddressRecords();
         assertFalse(response.isComplete());
 
         response = new MdnsResponse(responses.valueAt(0));
@@ -356,10 +356,12 @@
         assertTrue(response2.isComplete());
 
         // And should both have the same IPv6 address:
-        assertEquals(parseNumericAddress("2605:a601:a846:5700:3e61:5ff:fe0c:89f8"),
-                response1.getInet6AddressRecord().getInet6Address());
-        assertEquals(parseNumericAddress("2605:a601:a846:5700:3e61:5ff:fe0c:89f8"),
-                response2.getInet6AddressRecord().getInet6Address());
+        assertTrue(response1.getInet6AddressRecords().stream().anyMatch(
+                record -> record.getInet6Address().equals(
+                        parseNumericAddress("2605:a601:a846:5700:3e61:5ff:fe0c:89f8"))));
+        assertTrue(response2.getInet6AddressRecords().stream().anyMatch(
+                record -> record.getInet6Address().equals(
+                        parseNumericAddress("2605:a601:a846:5700:3e61:5ff:fe0c:89f8"))));
     }
 
     @Test
@@ -514,9 +516,9 @@
             reader.skip(2); // skip record type indication.
             // Apply the right kind of record to the response.
             if (responseData.recordClass == MdnsInet4AddressRecord.class) {
-                response.setInet4AddressRecord(new MdnsInet4AddressRecord(name, reader));
+                response.addInet4AddressRecord(new MdnsInet4AddressRecord(name, reader));
             } else if (responseData.recordClass == MdnsInet6AddressRecord.class) {
-                response.setInet6AddressRecord(new MdnsInet6AddressRecord(name, reader));
+                response.addInet6AddressRecord(new MdnsInet6AddressRecord(name, reader));
             } else if (responseData.recordClass == MdnsPointerRecord.class) {
                 response.addPointerRecord(new MdnsPointerRecord(name, reader));
             } else if (responseData.recordClass == MdnsServiceRecord.class) {
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java
index 132a822..3f5e7a1 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java
@@ -108,10 +108,10 @@
                 0 /* serviceWeight */, 0 /* servicePort */, hostname));
         response.setTextRecord(new MdnsTextRecord(serviceName, 0L /* receiptTimeMillis */,
                 true /* cacheFlush */, recordsTtlMillis, emptyList() /* entries */));
-        response.setInet4AddressRecord(new MdnsInetAddressRecord(
+        response.addInet4AddressRecord(new MdnsInetAddressRecord(
                 hostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
                 recordsTtlMillis, parseNumericAddress("192.0.2.123")));
-        response.setInet6AddressRecord(new MdnsInetAddressRecord(
+        response.addInet6AddressRecord(new MdnsInetAddressRecord(
                 hostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
                 recordsTtlMillis, parseNumericAddress("2001:db8::123")));
         return response;
@@ -126,7 +126,7 @@
         MdnsInetAddressRecord record = new MdnsInetAddressRecord(name, MdnsRecord.TYPE_A, reader);
         MdnsResponse response = new MdnsResponse(0, TEST_SERVICE_NAME, INTERFACE_INDEX, mNetwork);
         assertFalse(response.hasInet4AddressRecord());
-        assertTrue(response.setInet4AddressRecord(record));
+        assertTrue(response.addInet4AddressRecord(record));
         assertEquals(response.getInet4AddressRecord(), record);
     }
 
@@ -140,7 +140,7 @@
                 new MdnsInetAddressRecord(name, MdnsRecord.TYPE_AAAA, reader);
         MdnsResponse response = new MdnsResponse(0, TEST_SERVICE_NAME, INTERFACE_INDEX, mNetwork);
         assertFalse(response.hasInet6AddressRecord());
-        assertTrue(response.setInet6AddressRecord(record));
+        assertTrue(response.addInet6AddressRecord(record));
         assertEquals(response.getInet6AddressRecord(), record);
     }
 
@@ -228,8 +228,8 @@
         final MdnsResponse response = makeCompleteResponse(TEST_TTL_MS);
 
         assertFalse(response.addPointerRecord(response.getPointerRecords().get(0)));
-        assertFalse(response.setInet6AddressRecord(response.getInet6AddressRecord()));
-        assertFalse(response.setInet4AddressRecord(response.getInet4AddressRecord()));
+        assertFalse(response.addInet6AddressRecord(response.getInet6AddressRecord()));
+        assertFalse(response.addInet4AddressRecord(response.getInet4AddressRecord()));
         assertFalse(response.setServiceRecord(response.getServiceRecord()));
         assertFalse(response.setTextRecord(response.getTextRecord()));
     }
@@ -245,12 +245,14 @@
         assertTrue(response.getRecords().stream().anyMatch(r ->
                 r == response.getPointerRecords().get(0)));
 
-        assertTrue(response.setInet6AddressRecord(ttlZeroResponse.getInet6AddressRecord()));
+        assertTrue(response.addInet6AddressRecord(ttlZeroResponse.getInet6AddressRecord()));
+        assertEquals(1, response.getInet6AddressRecords().size());
         assertEquals(0, response.getInet6AddressRecord().getTtl());
         assertTrue(response.getRecords().stream().anyMatch(r ->
                 r == response.getInet6AddressRecord()));
 
-        assertTrue(response.setInet4AddressRecord(ttlZeroResponse.getInet4AddressRecord()));
+        assertTrue(response.addInet4AddressRecord(ttlZeroResponse.getInet4AddressRecord()));
+        assertEquals(1, response.getInet4AddressRecords().size());
         assertEquals(0, response.getInet4AddressRecord().getTtl());
         assertTrue(response.getRecords().stream().anyMatch(r ->
                 r == response.getInet4AddressRecord()));
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
index 4db0f1a..e7d7a98 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
@@ -197,8 +197,8 @@
                         List.of(),
                         new String[] {"my-host", "local"},
                         12345,
-                        "192.168.1.1",
-                        "2001::1",
+                        List.of("192.168.1.1"),
+                        List.of("2001::1"),
                         List.of(),
                         /* textEntries= */ null,
                         /* interfaceIndex= */ 20,
@@ -217,8 +217,8 @@
                         List.of(),
                         new String[] {"my-host", "local"},
                         12345,
-                        "192.168.1.1",
-                        "2001::1",
+                        List.of("192.168.1.1", "192.168.1.2"),
+                        List.of("2001::1", "2001::2"),
                         List.of("vn=Alphabet Inc.", "mn=Google Nest Hub Max", "id=12345"),
                         List.of(
                                 MdnsServiceInfo.TextEntry.fromString("vn=Google Inc."),
@@ -237,7 +237,9 @@
         assertArrayEquals(beforeParcel.getHostName(), afterParcel.getHostName());
         assertEquals(beforeParcel.getPort(), afterParcel.getPort());
         assertEquals(beforeParcel.getIpv4Address(), afterParcel.getIpv4Address());
+        assertEquals(beforeParcel.getIpv4Addresses(), afterParcel.getIpv4Addresses());
         assertEquals(beforeParcel.getIpv6Address(), afterParcel.getIpv6Address());
+        assertEquals(beforeParcel.getIpv6Addresses(), afterParcel.getIpv6Addresses());
         assertEquals(beforeParcel.getAttributes(), afterParcel.getAttributes());
         assertEquals(beforeParcel.getInterfaceIndex(), afterParcel.getInterfaceIndex());
         assertEquals(beforeParcel.getNetwork(), afterParcel.getNetwork());
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 a5c6123..d9fa10f 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -420,13 +420,13 @@
     }
 
     private static void verifyServiceInfo(MdnsServiceInfo serviceInfo, String serviceName,
-            String[] serviceType, String ipv4Address, String ipv6Address, int port,
+            String[] serviceType, List<String> ipv4Addresses, List<String> ipv6Addresses, int port,
             List<String> subTypes, Map<String, String> attributes, int interfaceIndex,
             Network network) {
         assertEquals(serviceName, serviceInfo.getServiceInstanceName());
         assertArrayEquals(serviceType, serviceInfo.getServiceType());
-        assertEquals(ipv4Address, serviceInfo.getIpv4Address());
-        assertEquals(ipv6Address, serviceInfo.getIpv6Address());
+        assertEquals(ipv4Addresses, serviceInfo.getIpv4Addresses());
+        assertEquals(ipv6Addresses, serviceInfo.getIpv6Addresses());
         assertEquals(port, serviceInfo.getPort());
         assertEquals(subTypes, serviceInfo.getSubtypes());
         for (String key : attributes.keySet()) {
@@ -449,8 +449,8 @@
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
                 "service-instance-1",
                 SERVICE_TYPE_LABELS,
-                /* ipv4Address= */ null,
-                /* ipv6Address= */ null,
+                /* ipv4Address= */ List.of(),
+                /* ipv6Address= */ List.of(),
                 /* port= */ 0,
                 /* subTypes= */ List.of(),
                 Collections.emptyMap(),
@@ -484,8 +484,8 @@
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
                 "service-instance-1",
                 SERVICE_TYPE_LABELS,
-                ipV4Address /* ipv4Address */,
-                null /* ipv6Address */,
+                List.of(ipV4Address) /* ipv4Address */,
+                List.of() /* ipv6Address */,
                 5353 /* port */,
                 Collections.singletonList("ABCDE") /* subTypes */,
                 Collections.singletonMap("key", null) /* attributes */,
@@ -539,8 +539,8 @@
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
                 "service-instance-1",
                 SERVICE_TYPE_LABELS,
-                null /* ipv4Address */,
-                ipV6Address /* ipv6Address */,
+                List.of() /* ipv4Address */,
+                List.of(ipV6Address) /* ipv6Address */,
                 5353 /* port */,
                 Collections.singletonList("ABCDE") /* subTypes */,
                 Collections.singletonMap("key", null) /* attributes */,
@@ -638,8 +638,8 @@
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
                 "service-instance-1",
                 SERVICE_TYPE_LABELS,
-                "192.168.1.1" /* ipv4Address */,
-                null /* ipv6Address */,
+                List.of("192.168.1.1") /* ipv4Address */,
+                List.of() /* ipv6Address */,
                 5353 /* port */,
                 Collections.singletonList("ABCDE") /* subTypes */,
                 Collections.singletonMap("key", null) /* attributes */,
@@ -834,8 +834,8 @@
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
                 serviceName,
                 SERVICE_TYPE_LABELS,
-                null /* ipv4Address */,
-                null /* ipv6Address */,
+                List.of() /* ipv4Address */,
+                List.of() /* ipv6Address */,
                 5353 /* port */,
                 Collections.singletonList(subtype) /* subTypes */,
                 Collections.singletonMap("key", null) /* attributes */,
@@ -847,8 +847,8 @@
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(1),
                 serviceName,
                 SERVICE_TYPE_LABELS,
-                ipV4Address /* ipv4Address */,
-                null /* ipv6Address */,
+                List.of(ipV4Address) /* ipv4Address */,
+                List.of() /* ipv6Address */,
                 5353 /* port */,
                 Collections.singletonList(subtype) /* subTypes */,
                 Collections.singletonMap("key", null) /* attributes */,
@@ -860,8 +860,8 @@
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(2),
                 serviceName,
                 SERVICE_TYPE_LABELS,
-                ipV4Address /* ipv4Address */,
-                ipV6Address /* ipv6Address */,
+                List.of(ipV4Address) /* ipv4Address */,
+                List.of(ipV6Address) /* ipv6Address */,
                 5354 /* port */,
                 Collections.singletonList(subtype) /* subTypes */,
                 Collections.singletonMap("key", "value") /* attributes */,
@@ -873,8 +873,8 @@
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(3),
                 serviceName,
                 SERVICE_TYPE_LABELS,
-                ipV4Address /* ipv4Address */,
-                ipV6Address /* ipv6Address */,
+                List.of(ipV4Address) /* ipv4Address */,
+                List.of(ipV6Address) /* ipv6Address */,
                 5354 /* port */,
                 Collections.singletonList("ABCDE") /* subTypes */,
                 Collections.singletonMap("key", "value") /* attributes */,
@@ -886,8 +886,8 @@
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(4),
                 serviceName,
                 SERVICE_TYPE_LABELS,
-                ipV4Address /* ipv4Address */,
-                ipV6Address /* ipv6Address */,
+                List.of(ipV4Address) /* ipv4Address */,
+                List.of(ipV6Address) /* ipv6Address */,
                 5354 /* port */,
                 Collections.singletonList("ABCDE") /* subTypes */,
                 Collections.singletonMap("key", "value") /* attributes */,
@@ -982,8 +982,8 @@
         verifyServiceInfo(serviceInfoCaptor.getValue(),
                 instanceName,
                 SERVICE_TYPE_LABELS,
-                ipV4Address,
-                ipV6Address,
+                List.of(ipV4Address),
+                List.of(ipV6Address),
                 1234 /* port */,
                 Collections.emptyList() /* subTypes */,
                 Collections.emptyMap() /* attributes */,