Merge "Remove unnecessary public API getActiveNetworkForUid" into sc-dev
diff --git a/framework/Android.bp b/framework/Android.bp
index 74cecdd..5f5ebf4 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -23,26 +23,6 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-java_library {
-    name: "framework-connectivity-protos",
-    sdk_version: "module_current",
-    min_sdk_version: "30",
-    proto: {
-        type: "nano",
-    },
-    srcs: [
-        // TODO: consider moving relevant .proto files directly to the module directory
-        ":framework-javastream-protos",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-    jarjar_rules: "jarjar-rules-proto.txt",
-    visibility: [
-        "//visibility:private",
-    ],
-}
-
 filegroup {
     name: "framework-connectivity-internal-sources",
     srcs: [
@@ -109,9 +89,6 @@
     libs: [
         "unsupportedappusage",
     ],
-    static_libs: [
-        "framework-connectivity-protos",
-    ],
     jarjar_rules: "jarjar-rules.txt",
     permitted_packages: ["android.net"],
     impl_library_visibility: [
@@ -121,15 +98,26 @@
         "//packages/modules/Connectivity/service",
         "//frameworks/base/packages/Connectivity/service",
         "//frameworks/base",
-        "//packages/modules/Connectivity/Tethering/tests/unit",
+
+        // Tests using hidden APIs
+        "//external/sl4a:__subpackages__",
+        "//frameworks/base/tests/net:__subpackages__",
+        "//frameworks/libs/net/common/testutils",
+        "//frameworks/libs/net/common/tests:__subpackages__",
+        "//frameworks/opt/telephony/tests/telephonytests",
+        "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
+        "//packages/modules/Connectivity/tests:__subpackages__",
+        "//packages/modules/NetworkStack/tests:__subpackages__",
+        "//packages/modules/Wifi/service/tests/wifitests",
     ],
     apex_available: [
         "com.android.tethering",
     ],
 }
 
-cc_defaults {
-    name: "libframework-connectivity-defaults",
+cc_library_shared {
+    name: "libframework-connectivity-jni",
+    min_sdk_version: "30",
     cflags: [
         "-Wall",
         "-Werror",
@@ -140,38 +128,19 @@
         "-Wno-unguarded-availability",
         "-Wthread-safety",
     ],
+    srcs: [
+        "jni/android_net_NetworkUtils.cpp",
+        "jni/onload.cpp",
+    ],
     shared_libs: [
+        "libandroid",
         "liblog",
         "libnativehelper",
     ],
     header_libs: [
         "dnsproxyd_protocol_headers",
     ],
-}
-
-cc_library_static {
-    name: "libconnectivityframeworkutils",
-    defaults: ["libframework-connectivity-defaults"],
-    srcs: [
-        "jni/android_net_NetworkUtils.cpp",
-    ],
-    shared_libs: ["libandroid_net"],
-    apex_available: [
-        "//apex_available:platform",
-        "com.android.tethering",
-    ],
-}
-
-cc_library_shared {
-    name: "libframework-connectivity-jni",
-    min_sdk_version: "30",
-    defaults: ["libframework-connectivity-defaults"],
-    srcs: [
-        "jni/android_net_NetworkUtils.cpp",
-        "jni/onload.cpp",
-    ],
-    shared_libs: ["libandroid"],
-    stl: "libc++_static",
+    stl: "none",
     apex_available: [
         "com.android.tethering",
     ],
diff --git a/framework/aidl-export/android/net/IpPrefix.aidl b/framework/aidl-export/android/net/IpPrefix.aidl
index 0d70f2a..3495efc 100644
--- a/framework/aidl-export/android/net/IpPrefix.aidl
+++ b/framework/aidl-export/android/net/IpPrefix.aidl
@@ -18,5 +18,5 @@
 package android.net;
 
 // @JavaOnlyStableParcelable only affects the parcelable when built as stable aidl (aidl_interface
-// build rule). IpPrefix is also used in cpp but only as non-stable aidl.
-@JavaOnlyStableParcelable parcelable IpPrefix cpp_header "binder/IpPrefix.h";
+// build rule).
+@JavaOnlyStableParcelable parcelable IpPrefix;
diff --git a/framework/api/current.txt b/framework/api/current.txt
index 0dbac8c..ac5cb70 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -297,7 +297,6 @@
     method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
     method public int getOwnerUid();
     method public int getSignalStrength();
-    method @NonNull public java.util.Set<java.lang.Integer> getSubIds();
     method @Nullable public android.net.TransportInfo getTransportInfo();
     method public boolean hasCapability(int);
     method public boolean hasTransport(int);
@@ -310,6 +309,7 @@
     field public static final int NET_CAPABILITY_ENTERPRISE = 29; // 0x1d
     field public static final int NET_CAPABILITY_FOREGROUND = 19; // 0x13
     field public static final int NET_CAPABILITY_FOTA = 3; // 0x3
+    field public static final int NET_CAPABILITY_HEAD_UNIT = 32; // 0x20
     field public static final int NET_CAPABILITY_IA = 7; // 0x7
     field public static final int NET_CAPABILITY_IMS = 4; // 0x4
     field public static final int NET_CAPABILITY_INTERNET = 12; // 0xc
@@ -333,6 +333,7 @@
     field public static final int TRANSPORT_CELLULAR = 0; // 0x0
     field public static final int TRANSPORT_ETHERNET = 3; // 0x3
     field public static final int TRANSPORT_LOWPAN = 6; // 0x6
+    field public static final int TRANSPORT_USB = 8; // 0x8
     field public static final int TRANSPORT_VPN = 4; // 0x4
     field public static final int TRANSPORT_WIFI = 1; // 0x1
     field public static final int TRANSPORT_WIFI_AWARE = 5; // 0x5
@@ -407,7 +408,6 @@
     method public android.net.NetworkRequest.Builder removeTransportType(int);
     method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
     method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier);
-    method @NonNull public android.net.NetworkRequest.Builder setSubIds(@NonNull java.util.Set<java.lang.Integer>);
   }
 
   public class ParseException extends java.lang.RuntimeException {
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 970a236..513b630 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -107,7 +107,6 @@
   }
 
   public final class NetworkCapabilities implements android.os.Parcelable {
-    ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, long);
     method @Nullable public java.util.Set<android.util.Range<java.lang.Integer>> getUids();
     method public boolean hasUnwantedCapability(int);
     field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL
@@ -179,10 +178,12 @@
   }
 
   public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
-    ctor public VpnTransportInfo(int);
+    ctor public VpnTransportInfo(int, @Nullable String);
     method public int describeContents();
+    method @NonNull public android.net.VpnTransportInfo makeCopy(long);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.VpnTransportInfo> CREATOR;
+    field @Nullable public final String sessionId;
     field public final int type;
   }
 
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index 95df8b8..847bcbc 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -219,7 +219,7 @@
     method public void onAutomaticReconnectDisabled();
     method public void onBandwidthUpdateRequested();
     method public void onNetworkCreated();
-    method public void onNetworkDisconnected();
+    method public void onNetworkDestroyed();
     method public void onNetworkUnwanted();
     method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter);
     method public void onQosCallbackUnregistered(int);
@@ -232,12 +232,14 @@
     method @NonNull public android.net.Network register();
     method public final void sendLinkProperties(@NonNull android.net.LinkProperties);
     method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
+    method public final void sendNetworkScore(@NonNull android.net.NetworkScore);
     method public final void sendNetworkScore(@IntRange(from=0, to=99) int);
     method public final void sendQosCallbackError(int, int);
     method public final void sendQosSessionAvailable(int, int, @NonNull android.net.QosSessionAttributes);
     method public final void sendQosSessionLost(int, int, int);
     method public final void sendSocketKeepaliveEvent(int, int);
     method @Deprecated public void setLegacySubtype(int, @NonNull String);
+    method public void setTeardownDelayMs(@IntRange(from=0, to=0x1388) int);
     method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
     method public void unregister();
     field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
@@ -274,6 +276,7 @@
     method @NonNull public int[] getAdministratorUids();
     method @Nullable public static String getCapabilityCarrierName(int);
     method @Nullable public String getSsid();
+    method @NonNull public java.util.Set<java.lang.Integer> getSubIds();
     method @NonNull public int[] getTransportTypes();
     method public boolean isPrivateDnsBroken();
     method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
@@ -334,12 +337,15 @@
 
   public static class NetworkRequest.Builder {
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
+    method @NonNull public android.net.NetworkRequest.Builder setSubIds(@NonNull java.util.Set<java.lang.Integer>);
   }
 
   public final class NetworkScore implements android.os.Parcelable {
     method public int describeContents();
     method public int getKeepConnectedReason();
     method public int getLegacyInt();
+    method public boolean isExiting();
+    method public boolean isTransportPrimary();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkScore> CREATOR;
     field public static final int KEEP_CONNECTED_FOR_HANDOVER = 1; // 0x1
@@ -349,8 +355,10 @@
   public static final class NetworkScore.Builder {
     ctor public NetworkScore.Builder();
     method @NonNull public android.net.NetworkScore build();
+    method @NonNull public android.net.NetworkScore.Builder setExiting(boolean);
     method @NonNull public android.net.NetworkScore.Builder setKeepConnectedReason(int);
     method @NonNull public android.net.NetworkScore.Builder setLegacyInt(int);
+    method @NonNull public android.net.NetworkScore.Builder setTransportPrimary(boolean);
   }
 
   public final class OemNetworkPreferences implements android.os.Parcelable {
diff --git a/framework/jarjar-rules-proto.txt b/framework/jarjar-rules-proto.txt
deleted file mode 100644
index 37b4dec..0000000
--- a/framework/jarjar-rules-proto.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-keep android.net.NetworkCapabilitiesProto
-keep android.net.NetworkProto
-keep android.net.NetworkRequestProto
diff --git a/framework/jarjar-rules.txt b/framework/jarjar-rules.txt
index 7474c24..2e5848c 100644
--- a/framework/jarjar-rules.txt
+++ b/framework/jarjar-rules.txt
@@ -1,11 +1,2 @@
 rule com.android.net.module.util.** android.net.connectivity.framework.util.@1
 rule android.net.NetworkFactory* android.net.connectivity.framework.NetworkFactory@1
-
-# TODO (b/149403767): remove the annotations from net-utils-device-common instead of here
-zap android.annotation.**
-zap com.android.net.module.annotation.**
-zap com.android.internal.annotations.**
-
-rule android.net.NetworkCapabilitiesProto* android.net.connectivity.proto.NetworkCapabilitiesProto@1
-rule android.net.NetworkProto* android.net.connectivity.proto.NetworkProto@1
-rule android.net.NetworkRequestProto* android.net.connectivity.proto.NetworkRequestProto@1
diff --git a/framework/jni/android_net_NetworkUtils.cpp b/framework/jni/android_net_NetworkUtils.cpp
index e8bb42d..9bf910b 100644
--- a/framework/jni/android_net_NetworkUtils.cpp
+++ b/framework/jni/android_net_NetworkUtils.cpp
@@ -16,34 +16,19 @@
 
 #define LOG_TAG "NetworkUtils"
 
-#include <vector>
-
 #include <android/file_descriptor_jni.h>
 #include <android/multinetwork.h>
-#include <arpa/inet.h>
 #include <linux/filter.h>
-#include <linux/if_arp.h>
 #include <linux/tcp.h>
-#include <net/if.h>
-#include <netinet/ether.h>
-#include <netinet/ip.h>
-#include <netinet/udp.h>
+#include <netinet/in.h>
+#include <string.h>
 
 #include <DnsProxydProtocol.h> // NETID_USE_LOCAL_NAMESERVERS
-#include <cutils/properties.h>
-#include <nativehelper/JNIHelp.h>
 #include <nativehelper/JNIPlatformHelp.h>
-#include <nativehelper/ScopedLocalRef.h>
 #include <utils/Log.h>
-#include <utils/misc.h>
 
 #include "jni.h"
 
-extern "C" {
-int ifc_enable(const char *ifname);
-int ifc_disable(const char *ifname);
-}
-
 #define NETUTILS_PKG_NAME "android/net/NetworkUtils"
 
 namespace android {
@@ -52,6 +37,9 @@
 // FrameworkListener limits the size of commands to 4096 bytes.
 constexpr int MAXCMDSIZE = 4096;
 
+static volatile jclass class_Network = 0;
+static volatile jmethodID method_fromNetworkHandle = 0;
+
 static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) {
     jclass clazz = env->FindClass(class_name);
     LOG_ALWAYS_FATAL_IF(clazz == NULL, "Unable to find class %s", class_name);
@@ -138,11 +126,11 @@
 
     // Only allow dname which could be simply formatted to UTF8.
     // In native layer, res_mkquery would re-format the input char array to packet.
-    std::vector<char> queryname(byteCountUTF8 + 1, 0);
+    char queryname[byteCountUTF8 + 1];
+    memset(queryname, 0, (byteCountUTF8 + 1) * sizeof(char));
 
-    env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data());
-
-    int fd = android_res_nquery(netHandle, queryname.data(), ns_class, ns_type, flags);
+    env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname);
+    int fd = android_res_nquery(netHandle, queryname, ns_class, ns_type, flags);
 
     if (fd < 0) {
         jniThrowErrnoException(env, "resNetworkQuery", -fd);
@@ -170,9 +158,9 @@
 static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) {
     int fd = AFileDescriptor_getFD(env, javaFd);
     int rcode;
-    std::vector<uint8_t> buf(MAXPACKETSIZE, 0);
+    uint8_t buf[MAXPACKETSIZE] = {0};
 
-    int res = android_res_nresult(fd, &rcode, buf.data(), MAXPACKETSIZE);
+    int res = android_res_nresult(fd, &rcode, buf, MAXPACKETSIZE);
     jniSetFileDescriptorOfFD(env, javaFd, -1);
     if (res < 0) {
         jniThrowErrnoException(env, "resNetworkResult", -res);
@@ -184,8 +172,7 @@
         jniThrowErrnoException(env, "resNetworkResult", ENOMEM);
         return nullptr;
     } else {
-        env->SetByteArrayRegion(answer, 0, res,
-                reinterpret_cast<jbyte*>(buf.data()));
+        env->SetByteArrayRegion(answer, 0, res, reinterpret_cast<jbyte*>(buf));
     }
 
     jclass class_DnsResponse = env->FindClass("android/net/DnsResolver$DnsResponse");
@@ -207,11 +194,14 @@
         return nullptr;
     }
 
-    static jclass class_Network = MakeGlobalRefOrDie(
-            env, FindClassOrDie(env, "android/net/Network"));
-    static jmethodID method = env->GetStaticMethodID(class_Network, "fromNetworkHandle",
-            "(J)Landroid/net/Network;");
-    return env->CallStaticObjectMethod(class_Network, method, static_cast<jlong>(dnsNetHandle));
+    if (method_fromNetworkHandle == 0) {
+        // This may be called multiple times concurrently but that is fine
+        class_Network = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/net/Network"));
+        method_fromNetworkHandle = env->GetStaticMethodID(class_Network, "fromNetworkHandle",
+                "(J)Landroid/net/Network;");
+    }
+    return env->CallStaticObjectMethod(class_Network, method_fromNetworkHandle,
+            static_cast<jlong>(dnsNetHandle));
 }
 
 static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) {
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 3488679..2f6c2a2 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -924,6 +924,7 @@
             BLOCKED_REASON_DOZE,
             BLOCKED_REASON_APP_STANDBY,
             BLOCKED_REASON_RESTRICTED_MODE,
+            BLOCKED_REASON_LOCKDOWN_VPN,
             BLOCKED_METERED_REASON_DATA_SAVER,
             BLOCKED_METERED_REASON_USER_RESTRICTED,
             BLOCKED_METERED_REASON_ADMIN_DISABLED,
@@ -3143,18 +3144,27 @@
     }
 
     /**
-     * Set a network-independent global http proxy.  This is not normally what you want
-     * for typical HTTP proxies - they are general network dependent.  However if you're
-     * doing something unusual like general internal filtering this may be useful.  On
-     * a private network where the proxy is not accessible, you may break HTTP using this.
+     * Set a network-independent global HTTP proxy.
      *
-     * @param p A {@link ProxyInfo} object defining the new global
-     *        HTTP proxy.  A {@code null} value will clear the global HTTP proxy.
+     * This sets an HTTP proxy that applies to all networks and overrides any network-specific
+     * proxy. If set, HTTP libraries that are proxy-aware will use this global proxy when
+     * accessing any network, regardless of what the settings for that network are.
+     *
+     * Note that HTTP proxies are by nature typically network-dependent, and setting a global
+     * proxy is likely to break networking on multiple networks. This method is only meant
+     * for device policy clients looking to do general internal filtering or similar use cases.
+     *
+     * {@see #getGlobalProxy}
+     * {@see LinkProperties#getHttpProxy}
+     *
+     * @param p A {@link ProxyInfo} object defining the new global HTTP proxy. Calling this
+     *          method with a {@code null} value will clear the global HTTP proxy.
      * @hide
      */
+    // Used by Device Policy Manager to set the global proxy.
     @SystemApi(client = MODULE_LIBRARIES)
     @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
-    public void setGlobalProxy(@Nullable ProxyInfo p) {
+    public void setGlobalProxy(@Nullable final ProxyInfo p) {
         try {
             mService.setGlobalProxy(p);
         } catch (RemoteException e) {
@@ -3466,6 +3476,8 @@
          * not include location sensitive info.
          * </p>
          */
+        // Note: Some existing fields which are location sensitive may still be included without
+        // this flag if the app targets SDK < S (to maintain backwards compatibility).
         public static final int FLAG_INCLUDE_LOCATION_INFO = 1 << 0;
 
         /** @hide */
@@ -3716,7 +3728,8 @@
         public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {}
 
         /**
-         * Called when access to the specified network is blocked or unblocked.
+         * Called when access to the specified network is blocked or unblocked, or the reason for
+         * access being blocked changes.
          *
          * If a NetworkCallback object implements this method,
          * {@link #onBlockedStatusChanged(Network, boolean)} will not be called.
diff --git a/framework/src/android/net/DnsResolver.java b/framework/src/android/net/DnsResolver.java
index 3f7660f..dac88ad 100644
--- a/framework/src/android/net/DnsResolver.java
+++ b/framework/src/android/net/DnsResolver.java
@@ -500,7 +500,7 @@
                             try {
                                 resp = resNetworkResult(fd);  // Closes fd, marks it invalid.
                             } catch (ErrnoException e) {
-                                Log.e(TAG, "resNetworkResult:" + e.toString());
+                                Log.w(TAG, "resNetworkResult:" + e.toString());
                                 exception = e;
                             }
                         }
diff --git a/framework/src/android/net/INetworkAgent.aidl b/framework/src/android/net/INetworkAgent.aidl
index f9d3994..d941d4b 100644
--- a/framework/src/android/net/INetworkAgent.aidl
+++ b/framework/src/android/net/INetworkAgent.aidl
@@ -47,5 +47,5 @@
     void onQosFilterCallbackRegistered(int qosCallbackId, in QosFilterParcelable filterParcel);
     void onQosCallbackUnregistered(int qosCallbackId);
     void onNetworkCreated();
-    void onNetworkDisconnected();
+    void onNetworkDestroyed();
 }
diff --git a/framework/src/android/net/INetworkAgentRegistry.aidl b/framework/src/android/net/INetworkAgentRegistry.aidl
index cbd6193..26cb1ed 100644
--- a/framework/src/android/net/INetworkAgentRegistry.aidl
+++ b/framework/src/android/net/INetworkAgentRegistry.aidl
@@ -41,4 +41,5 @@
     void sendNrQosSessionAvailable(int callbackId, in QosSession session, in NrQosSessionAttributes attributes);
     void sendQosSessionLost(int qosCallbackId, in QosSession session);
     void sendQosCallbackError(int qosCallbackId, int exceptionType);
+    void sendTeardownDelayMs(int teardownDelayMs);
 }
diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index 6b55bb7..3622c1c 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -185,6 +185,20 @@
     public static final int EVENT_UNDERLYING_NETWORKS_CHANGED = BASE + 5;
 
     /**
+     * Sent by the NetworkAgent to ConnectivityService to pass the current value of the teardown
+     * delay.
+     * arg1 = teardown delay in milliseconds
+     * @hide
+     */
+    public static final int EVENT_TEARDOWN_DELAY_CHANGED = BASE + 6;
+
+    /**
+     * The maximum value for the teardown delay, in milliseconds.
+     * @hide
+     */
+    public static final int MAX_TEARDOWN_DELAY_MS = 5000;
+
+    /**
      * Sent by ConnectivityService to the NetworkAgent to inform the agent of the
      * networks status - whether we could use the network or could not, due to
      * either a bad network configuration (no internet link) or captive portal.
@@ -197,7 +211,6 @@
      */
     public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7;
 
-
     /**
      * Network validation suceeded.
      * Corresponds to {@link NetworkCapabilities.NET_CAPABILITY_VALIDATED}.
@@ -376,7 +389,7 @@
      *
      * @hide
      */
-    public static final int CMD_NETWORK_DISCONNECTED = BASE + 23;
+    public static final int CMD_NETWORK_DESTROYED = BASE + 23;
 
     private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
         final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
@@ -581,8 +594,8 @@
                     onNetworkCreated();
                     break;
                 }
-                case CMD_NETWORK_DISCONNECTED: {
-                    onNetworkDisconnected();
+                case CMD_NETWORK_DESTROYED: {
+                    onNetworkDestroyed();
                     break;
                 }
             }
@@ -732,8 +745,8 @@
         }
 
         @Override
-        public void onNetworkDisconnected() {
-            mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_DISCONNECTED));
+        public void onNetworkDestroyed() {
+            mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_DESTROYED));
         }
     }
 
@@ -851,6 +864,29 @@
     }
 
     /**
+     * Sets the value of the teardown delay.
+     *
+     * The teardown delay is the time between when the network disconnects and when the native
+     * network corresponding to this {@code NetworkAgent} is destroyed. By default, the native
+     * network is destroyed immediately. If {@code teardownDelayMs} is non-zero, then when this
+     * network disconnects, the system will instead immediately mark the network as restricted
+     * and unavailable to unprivileged apps, but will defer destroying the native network until the
+     * teardown delay timer expires.
+     *
+     * The interfaces in use by this network will remain in use until the native network is
+     * destroyed and cannot be reused until {@link #onNetworkDestroyed()} is called.
+     *
+     * This method may be called at any time while the network is connected. It has no effect if
+     * the network is already disconnected and the teardown delay timer is running.
+     *
+     * @param teardownDelayMs the teardown delay to set, or 0 to disable teardown delay.
+     */
+    public void setTeardownDelayMs(
+            @IntRange(from = 0, to = MAX_TEARDOWN_DELAY_MS) int teardownDelayMs) {
+        queueOrSendMessage(reg -> reg.sendTeardownDelayMs(teardownDelayMs));
+    }
+
+    /**
      * Change the legacy subtype of this network agent.
      *
      * This is only for backward compatibility and should not be used by non-legacy network agents,
@@ -920,7 +956,6 @@
      * Must be called by the agent to update the score of this network.
      *
      * @param score the new score.
-     * @hide TODO : unhide when impl is complete
      */
     public final void sendNetworkScore(@NonNull NetworkScore score) {
         Objects.requireNonNull(score);
@@ -1053,7 +1088,7 @@
     /**
      * Called when ConnectivityService has successfully destroy this NetworkAgent's native network.
      */
-    public void onNetworkDisconnected() {}
+    public void onNetworkDestroyed() {}
 
     /**
      * Requests that the network hardware send the specified packet at the specified interval.
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index ca69f16..c4277c3 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -162,7 +162,6 @@
      *                   {@link NetworkCapabilities}.
      * @hide
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public NetworkCapabilities(@Nullable NetworkCapabilities nc, @RedactionType long redactions) {
         mRedactions = redactions;
         if (nc != null) {
@@ -275,6 +274,7 @@
             NET_CAPABILITY_ENTERPRISE,
             NET_CAPABILITY_VSIM,
             NET_CAPABILITY_BIP,
+            NET_CAPABILITY_HEAD_UNIT,
     })
     public @interface NetCapability { }
 
@@ -508,8 +508,13 @@
     @SystemApi
     public static final int NET_CAPABILITY_BIP = 31;
 
+    /**
+     * Indicates that this network is connected to an automotive head unit.
+     */
+    public static final int NET_CAPABILITY_HEAD_UNIT = 32;
+
     private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
-    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_BIP;
+    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_HEAD_UNIT;
 
     /**
      * Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -527,7 +532,10 @@
             | (1 << NET_CAPABILITY_NOT_SUSPENDED)
             | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY)
             | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED)
-            | (1 << NET_CAPABILITY_NOT_VCN_MANAGED);
+            | (1 << NET_CAPABILITY_NOT_VCN_MANAGED)
+            // The value of NET_CAPABILITY_HEAD_UNIT is 32, which cannot use int to do bit shift,
+            // otherwise there will be an overflow. Use long to do bit shift instead.
+            | (1L << NET_CAPABILITY_HEAD_UNIT);
 
     /**
      * Network capabilities that are not allowed in NetworkRequests. This exists because the
@@ -592,8 +600,9 @@
         // TODO: Consider adding unwanted capabilities to the public API and mention this
         // in the documentation.
         checkValidCapability(capability);
-        mNetworkCapabilities |= 1 << capability;
-        mUnwantedNetworkCapabilities &= ~(1 << capability);  // remove from unwanted capability list
+        mNetworkCapabilities |= 1L << capability;
+        // remove from unwanted capability list
+        mUnwantedNetworkCapabilities &= ~(1L << capability);
         return this;
     }
 
@@ -612,8 +621,8 @@
      */
     public void addUnwantedCapability(@NetCapability int capability) {
         checkValidCapability(capability);
-        mUnwantedNetworkCapabilities |= 1 << capability;
-        mNetworkCapabilities &= ~(1 << capability);  // remove from requested capabilities
+        mUnwantedNetworkCapabilities |= 1L << capability;
+        mNetworkCapabilities &= ~(1L << capability);  // remove from requested capabilities
     }
 
     /**
@@ -626,7 +635,7 @@
      */
     public @NonNull NetworkCapabilities removeCapability(@NetCapability int capability) {
         checkValidCapability(capability);
-        final long mask = ~(1 << capability);
+        final long mask = ~(1L << capability);
         mNetworkCapabilities &= mask;
         return this;
     }
@@ -641,7 +650,7 @@
      */
     public @NonNull NetworkCapabilities removeUnwantedCapability(@NetCapability int capability) {
         checkValidCapability(capability);
-        mUnwantedNetworkCapabilities &= ~(1 << capability);
+        mUnwantedNetworkCapabilities &= ~(1L << capability);
         return this;
     }
 
@@ -709,14 +718,14 @@
      */
     public boolean hasCapability(@NetCapability int capability) {
         return isValidCapability(capability)
-                && ((mNetworkCapabilities & (1 << capability)) != 0);
+                && ((mNetworkCapabilities & (1L << capability)) != 0);
     }
 
     /** @hide */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public boolean hasUnwantedCapability(@NetCapability int capability) {
         return isValidCapability(capability)
-                && ((mUnwantedNetworkCapabilities & (1 << capability)) != 0);
+                && ((mUnwantedNetworkCapabilities & (1L << capability)) != 0);
     }
 
     /**
@@ -867,6 +876,7 @@
             TRANSPORT_WIFI_AWARE,
             TRANSPORT_LOWPAN,
             TRANSPORT_TEST,
+            TRANSPORT_USB,
     })
     public @interface Transport { }
 
@@ -913,10 +923,15 @@
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int TRANSPORT_TEST = 7;
 
+    /**
+     * Indicates this network uses a USB transport.
+     */
+    public static final int TRANSPORT_USB = 8;
+
     /** @hide */
     public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
     /** @hide */
-    public static final int MAX_TRANSPORT = TRANSPORT_TEST;
+    public static final int MAX_TRANSPORT = TRANSPORT_USB;
 
     /** @hide */
     public static boolean isValidTransport(@Transport int transportType) {
@@ -931,7 +946,8 @@
         "VPN",
         "WIFI_AWARE",
         "LOWPAN",
-        "TEST"
+        "TEST",
+        "USB"
     };
 
     /**
@@ -1111,7 +1127,9 @@
      * app needs to hold {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission. If the
      * app targets SDK version greater than or equal to {@link Build.VERSION_CODES#S}, then they
      * also need to use {@link NetworkCallback#FLAG_INCLUDE_LOCATION_INFO} to get the info in their
-     * callback. The app will be blamed for location access if this field is included.
+     * callback. If the apps targets SDK version equal to {{@link Build.VERSION_CODES#R}, this field
+     * will always be included. The app will be blamed for location access if this field is
+     * included.
      * </p>
      */
     public int getOwnerUid() {
@@ -2107,6 +2125,7 @@
             case NET_CAPABILITY_ENTERPRISE:           return "ENTERPRISE";
             case NET_CAPABILITY_VSIM:                 return "VSIM";
             case NET_CAPABILITY_BIP:                  return "BIP";
+            case NET_CAPABILITY_HEAD_UNIT:            return "HEAD_UNIT";
             default:                                  return Integer.toString(capability);
         }
     }
@@ -2334,9 +2353,15 @@
 
     /**
      * Gets the subscription ID set that associated to this network or request.
+     *
+     * <p>Instances of NetworkCapabilities will only have this field populated by the system if the
+     * receiver holds the NETWORK_FACTORY permission. In all other cases, it will be the empty set.
+     *
      * @return
+     * @hide
      */
     @NonNull
+    @SystemApi
     public Set<Integer> getSubIds() {
         return new ArraySet<>(mSubIds);
     }
@@ -2701,10 +2726,17 @@
         /**
          * Set the subscription ID set.
          *
+         * <p>SubIds are populated in NetworkCapability instances from the system only for callers
+         * that hold the NETWORK_FACTORY permission. Similarly, the system will reject any
+         * NetworkRequests filed with a non-empty set of subIds unless the caller holds the
+         * NETWORK_FACTORY permission.
+         *
          * @param subIds a set that represent the subscription IDs. Empty if clean up.
          * @return this builder.
+         * @hide
          */
         @NonNull
+        @SystemApi
         public Builder setSubIds(@NonNull final Set<Integer> subIds) {
             mCaps.setSubIds(subIds);
             return this;
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index 5313f08..78e1011 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -500,9 +500,14 @@
          * A network will satisfy this request only if it matches one of the subIds in this set.
          * An empty set matches all networks, including those without a subId.
          *
+         * <p>Registering a NetworkRequest with a non-empty set of subIds requires the
+         * NETWORK_FACTORY permission.
+         *
          * @param subIds A {@code Set} that represents subscription IDs.
+         * @hide
          */
         @NonNull
+        @SystemApi
         public Builder setSubIds(@NonNull Set<Integer> subIds) {
             mNetworkCapabilities.setSubIds(subIds);
             return this;
@@ -655,25 +660,6 @@
                 ", " + networkCapabilities.toString() + " ]";
     }
 
-    private int typeToProtoEnum(Type t) {
-        switch (t) {
-            case NONE:
-                return NetworkRequestProto.TYPE_NONE;
-            case LISTEN:
-                return NetworkRequestProto.TYPE_LISTEN;
-            case TRACK_DEFAULT:
-                return NetworkRequestProto.TYPE_TRACK_DEFAULT;
-            case REQUEST:
-                return NetworkRequestProto.TYPE_REQUEST;
-            case BACKGROUND_REQUEST:
-                return NetworkRequestProto.TYPE_BACKGROUND_REQUEST;
-            case TRACK_SYSTEM_DEFAULT:
-                return NetworkRequestProto.TYPE_TRACK_SYSTEM_DEFAULT;
-            default:
-                return NetworkRequestProto.TYPE_UNKNOWN;
-        }
-    }
-
     public boolean equals(@Nullable Object obj) {
         if (obj instanceof NetworkRequest == false) return false;
         NetworkRequest that = (NetworkRequest)obj;
diff --git a/framework/src/android/net/NetworkScore.java b/framework/src/android/net/NetworkScore.java
index 9786b09..0dee225 100644
--- a/framework/src/android/net/NetworkScore.java
+++ b/framework/src/android/net/NetworkScore.java
@@ -52,11 +52,27 @@
     public static final int KEEP_CONNECTED_FOR_HANDOVER = 1;
 
     // Agent-managed policies
-    // TODO : add them here, starting from 1
+    // This network should lose to a wifi that has ever been validated
+    // NOTE : temporarily this policy is managed by ConnectivityService, because of legacy. The
+    // legacy design has this bit global to the system and tacked on WiFi which means it will affect
+    // networks from carriers who don't want it and non-carrier networks, which is bad for users.
+    // The S design has this on mobile networks only, so this can be fixed eventually ; as CS
+    // doesn't know what carriers need this bit, the initial S implementation will continue to
+    // affect other carriers but will at least leave non-mobile networks alone. Eventually Telephony
+    // should set this on networks from carriers that require it.
     /** @hide */
-    public static final int MIN_AGENT_MANAGED_POLICY = 0;
+    public static final int POLICY_YIELD_TO_BAD_WIFI = 1;
+    // This network is primary for this transport.
     /** @hide */
-    public static final int MAX_AGENT_MANAGED_POLICY = -1;
+    public static final int POLICY_TRANSPORT_PRIMARY = 2;
+    // This network is exiting : it will likely disconnect in a few seconds.
+    /** @hide */
+    public static final int POLICY_EXITING = 3;
+
+    /** @hide */
+    public static final int MIN_AGENT_MANAGED_POLICY = POLICY_YIELD_TO_BAD_WIFI;
+    /** @hide */
+    public static final int MAX_AGENT_MANAGED_POLICY = POLICY_EXITING;
 
     // Bitmask of all the policies applied to this score.
     private final long mPolicies;
@@ -98,9 +114,63 @@
         return 0 != (mPolicies & (1L << policy));
     }
 
+    /**
+     * To the exclusive usage of FullScore
+     * @hide
+     */
+    public long getPolicies() {
+        return mPolicies;
+    }
+
+    /**
+     * Whether this network should yield to a previously validated wifi gone bad.
+     *
+     * If this policy is set, other things being equal, the device will prefer a previously
+     * validated WiFi even if this network is validated and the WiFi is not.
+     * If this policy is not set, the device prefers the validated network.
+     *
+     * @hide
+     */
+    // TODO : Unhide this for telephony and have telephony call it on the relevant carriers.
+    // In the mean time this is handled by Connectivity in a backward-compatible manner.
+    public boolean shouldYieldToBadWifi() {
+        return hasPolicy(POLICY_YIELD_TO_BAD_WIFI);
+    }
+
+    /**
+     * Whether this network is primary for this transport.
+     *
+     * When multiple networks of the same transport are active, the device prefers the ones that
+     * are primary. This is meant in particular for DS-DA devices with a user setting to choose the
+     * default SIM card, or for WiFi STA+STA and make-before-break cases.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isTransportPrimary() {
+        return hasPolicy(POLICY_TRANSPORT_PRIMARY);
+    }
+
+    /**
+     * Whether this network is exiting.
+     *
+     * If this policy is set, the device will expect this network to disconnect within seconds.
+     * It will try to migrate to some other network if any is available, policy permitting, to
+     * avoid service disruption.
+     * This is useful in particular when a good cellular network is available and WiFi is getting
+     * weak and risks disconnecting soon. The WiFi network should be marked as exiting so that
+     * the device will prefer the reliable mobile network over this soon-to-be-lost WiFi.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isExiting() {
+        return hasPolicy(POLICY_EXITING);
+    }
+
     @Override
     public String toString() {
-        return "Score(" + mLegacyInt + ")";
+        return "Score(" + mLegacyInt + " ; Policies : " + mPolicies + ")";
     }
 
     @Override
@@ -137,6 +207,7 @@
         private static final int INVALID_LEGACY_INT = Integer.MIN_VALUE;
         private int mLegacyInt = INVALID_LEGACY_INT;
         private int mKeepConnectedReason = KEEP_CONNECTED_NONE;
+        private int mPolicies = 0;
 
         /**
          * Sets the legacy int for this score.
@@ -152,6 +223,75 @@
             return this;
         }
 
+
+        /**
+         * Set for a network that should never be preferred to a wifi that has ever been validated
+         *
+         * If this policy is set, other things being equal, the device will prefer a previously
+         * validated WiFi even if this network is validated and the WiFi is not.
+         * If this policy is not set, the device prefers the validated network.
+         *
+         * @return this builder
+         * @hide
+         */
+        // TODO : Unhide this for telephony and have telephony call it on the relevant carriers.
+        // In the mean time this is handled by Connectivity in a backward-compatible manner.
+        @NonNull
+        public Builder setShouldYieldToBadWifi(final boolean val) {
+            if (val) {
+                mPolicies |= (1L << POLICY_YIELD_TO_BAD_WIFI);
+            } else {
+                mPolicies &= ~(1L << POLICY_YIELD_TO_BAD_WIFI);
+            }
+            return this;
+        }
+
+        /**
+         * Set for a network that is primary for this transport.
+         *
+         * When multiple networks of the same transport are active, the device prefers the ones that
+         * are primary. This is meant in particular for DS-DA devices with a user setting to choose
+         * the default SIM card, or for WiFi STA+STA and make-before-break cases.
+         *
+         * @return this builder
+         * @hide
+         */
+        @SystemApi
+        @NonNull
+        public Builder setTransportPrimary(final boolean val) {
+            if (val) {
+                mPolicies |= (1L << POLICY_TRANSPORT_PRIMARY);
+            } else {
+                mPolicies &= ~(1L << POLICY_TRANSPORT_PRIMARY);
+            }
+            return this;
+        }
+
+        /**
+         * Set for a network that will likely disconnect in a few seconds.
+         *
+         * If this policy is set, the device will expect this network to disconnect within seconds.
+         * It will try to migrate to some other network if any is available, policy permitting, to
+         * avoid service disruption.
+         * This is useful in particular when a good cellular network is available and WiFi is
+         * getting weak and risks disconnecting soon. The WiFi network should be marked as exiting
+         * so that the device will prefer the reliable mobile network over this soon-to-be-lost
+         * WiFi.
+         *
+         * @return this builder
+         * @hide
+         */
+        @SystemApi
+        @NonNull
+        public Builder setExiting(final boolean val) {
+            if (val) {
+                mPolicies |= (1L << POLICY_EXITING);
+            } else {
+                mPolicies &= ~(1L << POLICY_EXITING);
+            }
+            return this;
+        }
+
         /**
          * Set the keep-connected reason.
          *
@@ -169,7 +309,7 @@
          */
         @NonNull
         public NetworkScore build() {
-            return new NetworkScore(mLegacyInt, POLICY_NONE, mKeepConnectedReason);
+            return new NetworkScore(mLegacyInt, mPolicies, mKeepConnectedReason);
         }
     }
 }
diff --git a/framework/src/android/net/VpnTransportInfo.java b/framework/src/android/net/VpnTransportInfo.java
index cd8f4c0..ba83a44 100644
--- a/framework/src/android/net/VpnTransportInfo.java
+++ b/framework/src/android/net/VpnTransportInfo.java
@@ -17,11 +17,14 @@
 package android.net;
 
 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 
 import java.util.Objects;
 
@@ -38,8 +41,26 @@
     /** Type of this VPN. */
     public final int type;
 
-    public VpnTransportInfo(int type) {
+    @Nullable
+    public final String sessionId;
+
+    @Override
+    public long getApplicableRedactions() {
+        return REDACT_FOR_NETWORK_SETTINGS;
+    }
+
+    /**
+     * Create a copy of a {@link VpnTransportInfo} with the sessionId redacted if necessary.
+     */
+    @NonNull
+    public VpnTransportInfo makeCopy(long redactions) {
+        return new VpnTransportInfo(type,
+            ((redactions & REDACT_FOR_NETWORK_SETTINGS) != 0) ? null : sessionId);
+    }
+
+    public VpnTransportInfo(int type, @Nullable String sessionId) {
         this.type = type;
+        this.sessionId = sessionId;
     }
 
     @Override
@@ -47,17 +68,17 @@
         if (!(o instanceof VpnTransportInfo)) return false;
 
         VpnTransportInfo that = (VpnTransportInfo) o;
-        return this.type == that.type;
+        return (this.type == that.type) && TextUtils.equals(this.sessionId, that.sessionId);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(type);
+        return Objects.hash(type, sessionId);
     }
 
     @Override
     public String toString() {
-        return String.format("VpnTransportInfo{type=%d}", type);
+        return String.format("VpnTransportInfo{type=%d, sessionId=%s}", type, sessionId);
     }
 
     @Override
@@ -68,12 +89,13 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(type);
+        dest.writeString(sessionId);
     }
 
     public static final @NonNull Creator<VpnTransportInfo> CREATOR =
             new Creator<VpnTransportInfo>() {
         public VpnTransportInfo createFromParcel(Parcel in) {
-            return new VpnTransportInfo(in.readInt());
+            return new VpnTransportInfo(in.readInt(), in.readString());
         }
         public VpnTransportInfo[] newArray(int size) {
             return new VpnTransportInfo[size];
diff --git a/service/Android.bp b/service/Android.bp
index b44128b..518f198 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -72,7 +72,7 @@
         "ServiceConnectivityResources",
     ],
     static_libs: [
-        "dnsresolver_aidl_interface-V7-java",
+        "dnsresolver_aidl_interface-V8-java",
         "modules-utils-os",
         "net-utils-device-common",
         "net-utils-framework-common",
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index bbe59c9..922b21a 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -320,6 +320,9 @@
     // The maximum number of network request allowed per uid before an exception is thrown.
     private static final int MAX_NETWORK_REQUESTS_PER_UID = 100;
 
+    // The maximum number of network request allowed for system UIDs before an exception is thrown.
+    private static final int MAX_NETWORK_REQUESTS_PER_SYSTEM_UID = 250;
+
     @VisibleForTesting
     protected int mLingerDelayMs;  // Can't be final, or test subclass constructors can't change it.
     @VisibleForTesting
@@ -335,6 +338,7 @@
     protected final PermissionMonitor mPermissionMonitor;
 
     private final PerUidCounter mNetworkRequestCounter;
+    private final PerUidCounter mSystemNetworkRequestCounter;
 
     private volatile boolean mLockdownEnabled;
 
@@ -1215,6 +1219,7 @@
         mContext = Objects.requireNonNull(context, "missing Context");
         mResources = deps.getResources(mContext);
         mNetworkRequestCounter = new PerUidCounter(MAX_NETWORK_REQUESTS_PER_UID);
+        mSystemNetworkRequestCounter = new PerUidCounter(MAX_NETWORK_REQUESTS_PER_SYSTEM_UID);
 
         mMetricsLog = logger;
         mNetworkRanker = new NetworkRanker();
@@ -1289,14 +1294,22 @@
 
         mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
 
+        mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
         // Listen for user add/removes to inform PermissionMonitor.
         // Should run on mHandler to avoid any races.
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(Intent.ACTION_USER_ADDED);
-        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+        final IntentFilter userIntentFilter = new IntentFilter();
+        userIntentFilter.addAction(Intent.ACTION_USER_ADDED);
+        userIntentFilter.addAction(Intent.ACTION_USER_REMOVED);
+        mUserAllContext.registerReceiver(mUserIntentReceiver, userIntentFilter,
+                null /* broadcastPermission */, mHandler);
 
-        mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
-        mUserAllContext.registerReceiver(mIntentReceiver, intentFilter,
+        // Listen to package add/removes for netd
+        final IntentFilter packageIntentFilter = new IntentFilter();
+        packageIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        packageIntentFilter.addDataScheme("package");
+        mUserAllContext.registerReceiver(mPackageIntentReceiver, packageIntentFilter,
                 null /* broadcastPermission */, mHandler);
 
         mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mHandler, mNetd);
@@ -1324,7 +1337,7 @@
         mLingerMonitor = new LingerMonitor(mContext, mNotifier, dailyLimit, rateLimit);
 
         mMultinetworkPolicyTracker = mDeps.makeMultinetworkPolicyTracker(
-                mContext, mHandler, () -> rematchForAvoidBadWifiUpdate());
+                mContext, mHandler, () -> updateAvoidBadWifi());
         mMultinetworkPolicyTracker.start();
 
         mDnsManager = new DnsManager(mContext, mDnsResolver);
@@ -1570,16 +1583,16 @@
         mNetworkInfoBlockingLogs.log(action + " " + uid);
     }
 
-    private void maybeLogBlockedStatusChanged(NetworkRequestInfo nri, Network net,
-            boolean blocked) {
+    private void maybeLogBlockedStatusChanged(NetworkRequestInfo nri, Network net, int blocked) {
         if (nri == null || net == null || !LOGD_BLOCKED_NETWORKINFO) {
             return;
         }
-        final String action = blocked ? "BLOCKED" : "UNBLOCKED";
+        final String action = (blocked != 0) ? "BLOCKED" : "UNBLOCKED";
         final int requestId = nri.getActiveRequest() != null
                 ? nri.getActiveRequest().requestId : nri.mRequests.get(0).requestId;
         mNetworkInfoBlockingLogs.log(String.format(
-                "%s %d(%d) on netId %d", action, nri.mAsUid, requestId, net.getNetId()));
+                "%s %d(%d) on netId %d: %s", action, nri.mAsUid, requestId, net.getNetId(),
+                blockedReasonsToString(blocked)));
     }
 
     /**
@@ -1909,6 +1922,10 @@
             newNc.setNetworkSpecifier(newNc.getNetworkSpecifier().redact());
         }
         newNc.setAdministratorUids(new int[0]);
+        if (!checkAnyPermissionOf(
+                callerPid, callerUid, android.Manifest.permission.NETWORK_FACTORY)) {
+            newNc.setSubIds(Collections.emptySet());
+        }
 
         return newNc;
     }
@@ -3137,6 +3154,13 @@
                     }
                     break;
                 }
+                case NetworkAgent.EVENT_TEARDOWN_DELAY_CHANGED: {
+                    if (msg.arg1 >= 0 && msg.arg1 <= NetworkAgent.MAX_TEARDOWN_DELAY_MS) {
+                        nai.teardownDelayMs = msg.arg1;
+                    } else {
+                        logwtf(nai.toShortString() + " set invalid teardown delay " + msg.arg1);
+                    }
+                }
             }
         }
 
@@ -3708,6 +3732,23 @@
         mLegacyTypeTracker.remove(nai, wasDefault);
         rematchAllNetworksAndRequests();
         mLingerMonitor.noteDisconnect(nai);
+
+        // Immediate teardown.
+        if (nai.teardownDelayMs == 0) {
+            destroyNetwork(nai);
+            return;
+        }
+
+        // Delayed teardown.
+        try {
+            mNetd.networkSetPermissionForNetwork(nai.network.netId, INetd.PERMISSION_SYSTEM);
+        } catch (RemoteException e) {
+            Log.d(TAG, "Error marking network restricted during teardown: " + e);
+        }
+        mHandler.postDelayed(() -> destroyNetwork(nai), nai.teardownDelayMs);
+    }
+
+    private void destroyNetwork(NetworkAgentInfo nai) {
         if (nai.created) {
             // Tell netd to clean up the configuration for this network
             // (routing rules, DNS, etc).
@@ -3720,7 +3761,7 @@
             mDnsManager.removeNetwork(nai.network);
         }
         mNetIdManager.releaseNetId(nai.network.getNetId());
-        nai.onNetworkDisconnected();
+        nai.onNetworkDestroyed();
     }
 
     private boolean createNativeNetwork(@NonNull NetworkAgentInfo networkAgent) {
@@ -3748,6 +3789,10 @@
     private void destroyNativeNetwork(@NonNull NetworkAgentInfo networkAgent) {
         try {
             mNetd.networkDestroy(networkAgent.network.getNetId());
+        } catch (RemoteException | ServiceSpecificException e) {
+            loge("Exception destroying network(networkDestroy): " + e);
+        }
+        try {
             mDnsResolver.destroyNetworkCache(networkAgent.network.getNetId());
         } catch (RemoteException | ServiceSpecificException e) {
             loge("Exception destroying network: " + e);
@@ -4023,7 +4068,7 @@
                 }
             }
         }
-        mNetworkRequestCounter.decrementCount(nri.mUid);
+        nri.decrementRequestCount();
         mNetworkRequestInfoLogs.log("RELEASE " + nri);
 
         if (null != nri.getActiveRequest()) {
@@ -4128,6 +4173,12 @@
         }
     }
 
+    private PerUidCounter getRequestCounter(NetworkRequestInfo nri) {
+        return checkAnyPermissionOf(
+                nri.mPid, nri.mUid, NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+                ? mSystemNetworkRequestCounter : mNetworkRequestCounter;
+    }
+
     @Override
     public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
         enforceNetworkStackSettingsOrSetup();
@@ -4346,8 +4397,10 @@
         return avoidBadWifi();
     }
 
-    // TODO : this function is now useless.
-    private void rematchForAvoidBadWifiUpdate() {
+    private void updateAvoidBadWifi() {
+        for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+            nai.updateScoreForNetworkAgentConfigUpdate();
+        }
         rematchAllNetworksAndRequests();
     }
 
@@ -4936,7 +4989,7 @@
     }
 
     @Override
-    public void setGlobalProxy(final ProxyInfo proxyProperties) {
+    public void setGlobalProxy(@Nullable final ProxyInfo proxyProperties) {
         PermissionUtils.enforceNetworkStackPermission(mContext);
         mProxyTracker.setGlobalProxy(proxyProperties);
     }
@@ -5281,14 +5334,14 @@
         }
     }
 
-    private void onUserAdded(UserHandle user) {
+    private void onUserAdded(@NonNull final UserHandle user) {
         mPermissionMonitor.onUserAdded(user);
         if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) {
             handleSetOemNetworkPreference(mOemNetworkPreferences, null);
         }
     }
 
-    private void onUserRemoved(UserHandle user) {
+    private void onUserRemoved(@NonNull final UserHandle user) {
         mPermissionMonitor.onUserRemoved(user);
         // If there was a network preference for this user, remove it.
         handleSetProfileNetworkPreference(new ProfileNetworkPreferences.Preference(user, null),
@@ -5298,7 +5351,18 @@
         }
     }
 
-    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+    private void onPackageChanged(@NonNull final String packageName) {
+        // This is necessary in case a package is added or removed, but also when it's replaced to
+        // run as a new UID by its manifest rules. Also, if a separate package shares the same UID
+        // as one in the preferences, then it should follow the same routing as that other package,
+        // which means updating the rules is never to be needed in this case (whether it joins or
+        // leaves a UID with a preference).
+        if (isMappedInOemNetworkPreference(packageName)) {
+            handleSetOemNetworkPreference(mOemNetworkPreferences, null);
+        }
+    }
+
+    private final BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             ensureRunningOnConnectivityServiceThread();
@@ -5321,6 +5385,22 @@
         }
     };
 
+    private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            ensureRunningOnConnectivityServiceThread();
+            switch (intent.getAction()) {
+                case Intent.ACTION_PACKAGE_ADDED:
+                case Intent.ACTION_PACKAGE_REMOVED:
+                case Intent.ACTION_PACKAGE_REPLACED:
+                    onPackageChanged(intent.getData().getSchemeSpecificPart());
+                    break;
+                default:
+                    Log.wtf(TAG, "received unexpected intent: " + intent.getAction());
+            }
+        }
+    };
+
     private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>();
     private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>();
 
@@ -5413,6 +5493,9 @@
         @Nullable
         final String mCallingAttributionTag;
 
+        // Counter keeping track of this NRI.
+        final PerUidCounter mPerUidCounter;
+
         // Effective UID of this request. This is different from mUid when a privileged process
         // files a request on behalf of another UID. This UID is used to determine blocked status,
         // UID matching, and so on. mUid above is used for permission checks and to enforce the
@@ -5463,7 +5546,8 @@
             mPid = getCallingPid();
             mUid = mDeps.getCallingUid();
             mAsUid = asUid;
-            mNetworkRequestCounter.incrementCountOrThrow(mUid);
+            mPerUidCounter = getRequestCounter(this);
+            mPerUidCounter.incrementCountOrThrow(mUid);
             /**
              * Location sensitive data not included in pending intent. Only included in
              * {@link NetworkCallback}.
@@ -5495,15 +5579,11 @@
             mUid = mDeps.getCallingUid();
             mAsUid = asUid;
             mPendingIntent = null;
-            mNetworkRequestCounter.incrementCountOrThrow(mUid);
+            mPerUidCounter = getRequestCounter(this);
+            mPerUidCounter.incrementCountOrThrow(mUid);
             mCallbackFlags = callbackFlags;
             mCallingAttributionTag = callingAttributionTag;
-
-            try {
-                mBinder.linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                binderDied();
-            }
+            linkDeathRecipient();
         }
 
         NetworkRequestInfo(@NonNull final NetworkRequestInfo nri,
@@ -5538,9 +5618,11 @@
             mUid = nri.mUid;
             mAsUid = nri.mAsUid;
             mPendingIntent = nri.mPendingIntent;
-            mNetworkRequestCounter.incrementCountOrThrow(mUid);
+            mPerUidCounter = getRequestCounter(this);
+            mPerUidCounter.incrementCountOrThrow(mUid);
             mCallbackFlags = nri.mCallbackFlags;
             mCallingAttributionTag = nri.mCallingAttributionTag;
+            linkDeathRecipient();
         }
 
         NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r) {
@@ -5569,8 +5651,22 @@
             return Collections.unmodifiableList(tempRequests);
         }
 
+        void decrementRequestCount() {
+            mPerUidCounter.decrementCount(mUid);
+        }
+
+        void linkDeathRecipient() {
+            if (null != mBinder) {
+                try {
+                    mBinder.linkToDeath(this, 0);
+                } catch (RemoteException e) {
+                    binderDied();
+                }
+            }
+        }
+
         void unlinkDeathRecipient() {
-            if (mBinder != null) {
+            if (null != mBinder) {
                 mBinder.unlinkToDeath(this, 0);
             }
         }
@@ -5591,7 +5687,7 @@
                     + mNetworkRequestForCallback.requestId
                     + " " + mRequests
                     + (mPendingIntent == null ? "" : " to trigger " + mPendingIntent)
-                    + "callback flags: " + mCallbackFlags;
+                    + " callback flags: " + mCallbackFlags;
         }
     }
 
@@ -5616,6 +5712,10 @@
                     "Insufficient permissions to request a specific signal strength");
         }
         mAppOpsManager.checkPackage(callerUid, callerPackageName);
+
+        if (!nc.getSubIds().isEmpty()) {
+            enforceNetworkFactoryPermission();
+        }
     }
 
     private int[] getSignalStrengthThresholds(@NonNull final NetworkAgentInfo nai) {
@@ -6137,6 +6237,15 @@
     @NonNull
     private ProfileNetworkPreferences mProfileNetworkPreferences = new ProfileNetworkPreferences();
 
+    /**
+     * Determine whether a given package has a mapping in the current OemNetworkPreferences.
+     * @param packageName the package name to check existence of a mapping for.
+     * @return true if a mapping exists, false otherwise
+     */
+    private boolean isMappedInOemNetworkPreference(@NonNull final String packageName) {
+        return mOemNetworkPreferences.getNetworkPreferences().containsKey(packageName);
+    }
+
     // The always-on request for an Internet-capable network that apps without a specific default
     // fall back to.
     @VisibleForTesting
@@ -6157,7 +6266,7 @@
      * @return the NetworkRequestInfo tracking the given uid.
      */
     @NonNull
-    private NetworkRequestInfo getDefaultRequestTrackingUid(@NonNull final int uid) {
+    private NetworkRequestInfo getDefaultRequestTrackingUid(final int uid) {
         for (final NetworkRequestInfo nri : mDefaultNetworkRequests) {
             if (nri == mDefaultRequest) {
                 continue;
@@ -6283,8 +6392,8 @@
     // Request used to optionally keep vehicle internal network always active
     private final NetworkRequest mDefaultVehicleRequest;
 
-    // TODO replace with INetd.DUMMY_NET_ID when available.
-    private static final int NO_SERVICE_NET_ID = 51;
+    // TODO replace with INetd.UNREACHABLE_NET_ID when available.
+    private static final int NO_SERVICE_NET_ID = 52;
     // Sentinel NAI used to direct apps with default networks that should have no connectivity to a
     // network with no service. This NAI should never be matched against, nor should any public API
     // ever return the associated network. For this reason, this NAI is not in the list of available
@@ -6675,7 +6784,7 @@
                     mDeps.reportNetworkInterfaceForTransports(mContext, iface,
                             caps.getTransportTypes());
                 } catch (Exception e) {
-                    loge("Exception adding interface: " + e);
+                    logw("Exception adding interface: " + e);
                 }
             }
         }
@@ -7354,7 +7463,7 @@
                 break;
             }
             case ConnectivityManager.CALLBACK_BLK_CHANGED: {
-                maybeLogBlockedStatusChanged(nri, networkAgent.network, arg1 != 0);
+                maybeLogBlockedStatusChanged(nri, networkAgent.network, arg1);
                 msg.arg1 = arg1;
                 break;
             }
@@ -7697,7 +7806,7 @@
             NetworkAgentInfo bestNetwork = null;
             NetworkRequest bestRequest = null;
             for (final NetworkRequest req : nri.mRequests) {
-                bestNetwork = mNetworkRanker.getBestNetwork(req, nais);
+                bestNetwork = mNetworkRanker.getBestNetwork(req, nais, nri.getSatisfier());
                 // Stop evaluating as the highest possible priority request is satisfied.
                 if (null != bestNetwork) {
                     bestRequest = req;
@@ -7950,7 +8059,6 @@
             @NonNull final NetworkOffer offer, @NonNull final NetworkRanker networkRanker) {
         final NetworkRequest activeRequest = nri.isBeingSatisfied() ? nri.getActiveRequest() : null;
         final NetworkAgentInfo satisfier = null != activeRequest ? nri.getSatisfier() : null;
-        final FullScore satisfierScore = null != satisfier ? satisfier.getScore() : null;
 
         // Multi-layer requests have a currently active request, the one being satisfied.
         // Since the system will try to bring up a better network than is currently satisfying
@@ -7989,7 +8097,7 @@
                     && satisfier.factorySerialNumber == offer.providerId;
             final boolean newNeeded = (currentlyServing
                     || (activeRequest.canBeSatisfiedBy(offer.caps)
-                            && networkRanker.mightBeat(activeRequest, satisfierScore, offer)));
+                            && networkRanker.mightBeat(activeRequest, satisfier, offer)));
             if (newNeeded != oldNeeded) {
                 if (newNeeded) {
                     offer.onNetworkNeeded(activeRequest);
@@ -8825,7 +8933,7 @@
             // Decrement the reference count for this NetworkRequestInfo. The reference count is
             // incremented when the NetworkRequestInfo is created as part of
             // enforceRequestCountLimit().
-            mNetworkRequestCounter.decrementCount(nri.mUid);
+            nri.decrementRequestCount();
             return;
         }
 
@@ -8891,7 +8999,7 @@
         // Decrement the reference count for this NetworkRequestInfo. The reference count is
         // incremented when the NetworkRequestInfo is created as part of
         // enforceRequestCountLimit().
-        mNetworkRequestCounter.decrementCount(nri.mUid);
+        nri.decrementRequestCount();
 
         iCb.unlinkToDeath(cbInfo, 0);
     }
@@ -9467,14 +9575,8 @@
             throw new IllegalArgumentException("Must explicitly specify a user handle ("
                     + "UserHandle.CURRENT not supported)");
         }
-        final UserManager um;
-        try {
-            um = mContext.createContextAsUser(profile, 0 /* flags */)
-                    .getSystemService(UserManager.class);
-        } catch (IllegalStateException e) {
-            throw new IllegalArgumentException("Profile does not exist");
-        }
-        if (!um.isManagedProfile()) {
+        final UserManager um = mContext.getSystemService(UserManager.class);
+        if (!um.isManagedProfile(profile.getIdentifier())) {
             throw new IllegalArgumentException("Profile must be a managed profile");
         }
         // Strictly speaking, mOemNetworkPreferences should only be touched on the
@@ -9641,7 +9743,6 @@
                 new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference);
         replaceDefaultNetworkRequestsForPreference(nris);
         mOemNetworkPreferences = preference;
-        // TODO http://b/176496396 persist data to shared preferences.
 
         if (null != listener) {
             try {
@@ -9798,7 +9899,6 @@
                     // packages are sent on a network preference as the system will watch for
                     // package installations associated with this network preference and update
                     // accordingly. This is done so as to minimize race conditions on app install.
-                    // TODO b/177092163 add app install watching.
                     continue;
                 }
             }
diff --git a/services/core/java/com/android/server/connectivity/FullScore.java b/services/core/java/com/android/server/connectivity/FullScore.java
index 375d005..a8a83fc 100644
--- a/services/core/java/com/android/server/connectivity/FullScore.java
+++ b/services/core/java/com/android/server/connectivity/FullScore.java
@@ -17,9 +17,13 @@
 package com.android.server.connectivity;
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkScore.KEEP_CONNECTED_NONE;
+import static android.net.NetworkScore.POLICY_EXITING;
+import static android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY;
+import static android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -52,7 +56,8 @@
             POLICY_IS_VALIDATED,
             POLICY_IS_VPN,
             POLICY_EVER_USER_SELECTED,
-            POLICY_ACCEPT_UNVALIDATED
+            POLICY_ACCEPT_UNVALIDATED,
+            POLICY_IS_UNMETERED
     })
     public @interface Policy {
     }
@@ -77,12 +82,27 @@
     /** @hide */
     public static final int POLICY_ACCEPT_UNVALIDATED = 60;
 
+    // This network is unmetered. {@see NetworkCapabilities.NET_CAPABILITY_NOT_METERED}.
+    /** @hide */
+    public static final int POLICY_IS_UNMETERED = 59;
+
+    // This network is invincible. This is useful for offers until there is an API to listen
+    // to requests.
+    /** @hide */
+    public static final int POLICY_IS_INVINCIBLE = 58;
+
     // To help iterate when printing
     @VisibleForTesting
-    static final int MIN_CS_MANAGED_POLICY = POLICY_ACCEPT_UNVALIDATED;
+    static final int MIN_CS_MANAGED_POLICY = POLICY_IS_INVINCIBLE;
     @VisibleForTesting
     static final int MAX_CS_MANAGED_POLICY = POLICY_IS_VALIDATED;
 
+    // Mask for policies in NetworkScore. This should have all bits managed by NetworkScore set
+    // and all bits managed by FullScore unset. As bits are handled from 0 up in NetworkScore and
+    // from 63 down in FullScore, cut at the 32rd bit for simplicity, but change this if some day
+    // there are more than 32 bits handled on either side.
+    private static final int EXTERNAL_POLICIES_MASK = 0x0000FFFF;
+
     @VisibleForTesting
     static @NonNull String policyNameOf(final int policy) {
         switch (policy) {
@@ -90,6 +110,11 @@
             case POLICY_IS_VPN: return "IS_VPN";
             case POLICY_EVER_USER_SELECTED: return "EVER_USER_SELECTED";
             case POLICY_ACCEPT_UNVALIDATED: return "ACCEPT_UNVALIDATED";
+            case POLICY_IS_UNMETERED: return "IS_UNMETERED";
+            case POLICY_YIELD_TO_BAD_WIFI: return "YIELD_TO_BAD_WIFI";
+            case POLICY_TRANSPORT_PRIMARY: return "TRANSPORT_PRIMARY";
+            case POLICY_EXITING: return "EXITING";
+            case POLICY_IS_INVINCIBLE: return "INVINCIBLE";
         }
         throw new IllegalArgumentException("Unknown policy : " + policy);
     }
@@ -112,15 +137,24 @@
      * @param score the score supplied by the agent
      * @param caps the NetworkCapabilities of the network
      * @param config the NetworkAgentConfig of the network
-     * @return an FullScore that is appropriate to use for ranking.
+     * @param yieldToBadWiFi whether this network yields to a previously validated wifi gone bad
+     * @return a FullScore that is appropriate to use for ranking.
      */
+    // TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the
+    // telephony factory, so that it depends on the carrier. For now this is handled by
+    // connectivity for backward compatibility.
     public static FullScore fromNetworkScore(@NonNull final NetworkScore score,
-            @NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config) {
-        return withPolicies(score.getLegacyInt(), score.getKeepConnectedReason(),
+            @NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config,
+            final boolean yieldToBadWiFi) {
+        return withPolicies(score.getLegacyInt(), score.getPolicies(),
+                score.getKeepConnectedReason(),
                 caps.hasCapability(NET_CAPABILITY_VALIDATED),
                 caps.hasTransport(TRANSPORT_VPN),
+                caps.hasCapability(NET_CAPABILITY_NOT_METERED),
                 config.explicitlySelected,
-                config.acceptUnvalidated);
+                config.acceptUnvalidated,
+                yieldToBadWiFi,
+                false /* invincible */); // only prospective scores can be invincible
     }
 
     /**
@@ -142,12 +176,21 @@
         final boolean mayValidate = caps.hasCapability(NET_CAPABILITY_INTERNET);
         // VPN transports are known in advance.
         final boolean vpn = caps.hasTransport(TRANSPORT_VPN);
+        // Prospective scores are always unmetered, because unmetered networks are stronger
+        // than metered networks, and it's not known in advance whether the network is metered.
+        final boolean unmetered = true;
         // The network hasn't been chosen by the user (yet, at least).
         final boolean everUserSelected = false;
         // Don't assume the user will accept unvalidated connectivity.
         final boolean acceptUnvalidated = false;
-        return withPolicies(score.getLegacyInt(), KEEP_CONNECTED_NONE,
-                mayValidate, vpn, everUserSelected, acceptUnvalidated);
+        // Don't assume clinging to bad wifi
+        final boolean yieldToBadWiFi = false;
+        // A prospective score is invincible if the legacy int in the filter is over the maximum
+        // score.
+        final boolean invincible = score.getLegacyInt() > NetworkRanker.LEGACY_INT_MAX;
+        return withPolicies(score.getLegacyInt(), score.getPolicies(), KEEP_CONNECTED_NONE,
+                mayValidate, vpn, unmetered, everUserSelected, acceptUnvalidated, yieldToBadWiFi,
+                invincible);
     }
 
     /**
@@ -157,26 +200,42 @@
      * @param config the NetworkAgentConfig of the network
      * @return a score with the policies from the arguments reset
      */
+    // TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the
+    // telephony factory, so that it depends on the carrier. For now this is handled by
+    // connectivity for backward compatibility.
     public FullScore mixInScore(@NonNull final NetworkCapabilities caps,
-            @NonNull final NetworkAgentConfig config) {
-        return withPolicies(mLegacyInt, mKeepConnectedReason,
+            @NonNull final NetworkAgentConfig config, final boolean yieldToBadWifi) {
+        return withPolicies(mLegacyInt, mPolicies, mKeepConnectedReason,
                 caps.hasCapability(NET_CAPABILITY_VALIDATED),
                 caps.hasTransport(TRANSPORT_VPN),
+                caps.hasCapability(NET_CAPABILITY_NOT_METERED),
                 config.explicitlySelected,
-                config.acceptUnvalidated);
+                config.acceptUnvalidated,
+                yieldToBadWifi,
+                false /* invincible */); // only prospective scores can be invincible
     }
 
+    // TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the
+    // telephony factory, so that it depends on the carrier. For now this is handled by
+    // connectivity for backward compatibility.
     private static FullScore withPolicies(@NonNull final int legacyInt,
+            final long externalPolicies,
             @KeepConnectedReason final int keepConnectedReason,
             final boolean isValidated,
             final boolean isVpn,
+            final boolean isUnmetered,
             final boolean everUserSelected,
-            final boolean acceptUnvalidated) {
-        return new FullScore(legacyInt,
-                (isValidated         ? 1L << POLICY_IS_VALIDATED : 0)
+            final boolean acceptUnvalidated,
+            final boolean yieldToBadWiFi,
+            final boolean invincible) {
+        return new FullScore(legacyInt, (externalPolicies & EXTERNAL_POLICIES_MASK)
+                | (isValidated       ? 1L << POLICY_IS_VALIDATED : 0)
                 | (isVpn             ? 1L << POLICY_IS_VPN : 0)
+                | (isUnmetered       ? 1L << POLICY_IS_UNMETERED : 0)
                 | (everUserSelected  ? 1L << POLICY_EVER_USER_SELECTED : 0)
-                | (acceptUnvalidated ? 1L << POLICY_ACCEPT_UNVALIDATED : 0),
+                | (acceptUnvalidated ? 1L << POLICY_ACCEPT_UNVALIDATED : 0)
+                | (yieldToBadWiFi    ? 1L << POLICY_YIELD_TO_BAD_WIFI : 0)
+                | (invincible        ? 1L << POLICY_IS_INVINCIBLE : 0),
                 keepConnectedReason);
     }
 
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index b31fa70..5d793fd 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -17,6 +17,7 @@
 package com.android.server.connectivity;
 
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.transportNamesOf;
 
 import android.annotation.NonNull;
@@ -142,7 +143,7 @@
 // the network is no longer considered "lingering". After the linger timer expires, if the network
 // is satisfying one or more background NetworkRequests it is kept up in the background. If it is
 // not, ConnectivityService disconnects the NetworkAgent's AsyncChannel.
-public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
+public class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRanker.Scoreable {
 
     @NonNull public NetworkInfo networkInfo;
     // This Network object should always be used if possible, so as to encourage reuse of the
@@ -201,6 +202,9 @@
     // Set to true when partial connectivity was detected.
     public boolean partialConnectivity;
 
+    // Delay between when the network is disconnected and when the native network is destroyed.
+    public int teardownDelayMs;
+
     // Captive portal info of the network from RFC8908, if any.
     // Obtained by ConnectivityService and merged into NetworkAgent-provided information.
     public CaptivePortalData capportApiData;
@@ -359,9 +363,9 @@
         linkProperties = lp;
         networkCapabilities = nc;
         networkAgentConfig = config;
-        setScore(score); // uses members networkCapabilities and networkAgentConfig
-        clatd = new Nat464Xlat(this, netd, dnsResolver, deps);
         mConnService = connService;
+        setScore(score); // uses members connService, networkCapabilities and networkAgentConfig
+        clatd = new Nat464Xlat(this, netd, dnsResolver, deps);
         mContext = context;
         mHandler = handler;
         this.factorySerialNumber = factorySerialNumber;
@@ -589,13 +593,13 @@
     }
 
     /**
-     * Notify the NetworkAgent that the network is disconnected and destroyed.
+     * Notify the NetworkAgent that the native network has been destroyed.
      */
-    public void onNetworkDisconnected() {
+    public void onNetworkDestroyed() {
         try {
-            networkAgent.onNetworkDisconnected();
+            networkAgent.onNetworkDestroyed();
         } catch (RemoteException e) {
-            Log.e(TAG, "Error sending network disconnected event", e);
+            Log.e(TAG, "Error sending network destroyed event", e);
         }
     }
 
@@ -675,6 +679,12 @@
                 @QosCallbackException.ExceptionType final int exceptionType) {
             mQosCallbackTracker.sendEventQosCallbackError(qosCallbackId, exceptionType);
         }
+
+        @Override
+        public void sendTeardownDelayMs(int teardownDelayMs) {
+            mHandler.obtainMessage(NetworkAgent.EVENT_TEARDOWN_DELAY_CHANGED,
+                    teardownDelayMs, 0, new Pair<>(NetworkAgentInfo.this, null)).sendToTarget();
+        }
     }
 
     /**
@@ -697,7 +707,7 @@
             @NonNull final NetworkCapabilities nc) {
         final NetworkCapabilities oldNc = networkCapabilities;
         networkCapabilities = nc;
-        mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig);
+        mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig, yieldToBadWiFi());
         final NetworkMonitorManager nm = mNetworkMonitor;
         if (nm != null) {
             nm.notifyNetworkCapabilitiesChanged(nc);
@@ -705,6 +715,11 @@
         return oldNc;
     }
 
+    private boolean yieldToBadWiFi() {
+        // Only cellular networks yield to bad wifi
+        return networkCapabilities.hasTransport(TRANSPORT_CELLULAR) && !mConnService.avoidBadWifi();
+    }
+
     public ConnectivityService connService() {
         return mConnService;
     }
@@ -875,16 +890,15 @@
         return isVPN();
     }
 
-    // Return true on devices configured to ignore score penalty for wifi networks
-    // that become unvalidated (b/31075769).
-    private boolean ignoreWifiUnvalidationPenalty() {
-        boolean isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
-                networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
-        boolean avoidBadWifi = mConnService.avoidBadWifi() || avoidUnvalidated;
-        return isWifi && !avoidBadWifi && everValidated;
+    // Caller must not mutate. This method is called frequently and making a defensive copy
+    // would be too expensive. This is used by NetworkRanker.Scoreable, so it can be compared
+    // against other scoreables.
+    @Override public NetworkCapabilities getCaps() {
+        return networkCapabilities;
     }
 
-    public FullScore getScore() {
+    // NetworkRanker.Scoreable
+    @Override public FullScore getScore() {
         return mScore;
     }
 
@@ -904,7 +918,8 @@
      * Mix-in the ConnectivityService-managed bits in the score.
      */
     public void setScore(final NetworkScore score) {
-        mScore = FullScore.fromNetworkScore(score, networkCapabilities, networkAgentConfig);
+        mScore = FullScore.fromNetworkScore(score, networkCapabilities, networkAgentConfig,
+                yieldToBadWiFi());
     }
 
     /**
@@ -913,7 +928,7 @@
      * Call this after updating the network agent config.
      */
     public void updateScoreForNetworkAgentConfigUpdate() {
-        mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig);
+        mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig, yieldToBadWiFi());
     }
 
     /**
@@ -1076,7 +1091,7 @@
         return "NetworkAgentInfo{"
                 + "network{" + network + "}  handle{" + network.getNetworkHandle() + "}  ni{"
                 + networkInfo.toShortString() + "} "
-                + "  Score{" + getCurrentScore() + "} "
+                + mScore + " "
                 + (isNascent() ? " nascent" : (isLingering() ? " lingering" : ""))
                 + (everValidated ? " everValidated" : "")
                 + (lastValidated ? " lastValidated" : "")
diff --git a/services/core/java/com/android/server/connectivity/NetworkOffer.java b/services/core/java/com/android/server/connectivity/NetworkOffer.java
index a0d3924..5336593 100644
--- a/services/core/java/com/android/server/connectivity/NetworkOffer.java
+++ b/services/core/java/com/android/server/connectivity/NetworkOffer.java
@@ -40,7 +40,7 @@
  *
  * @hide
  */
-public class NetworkOffer {
+public class NetworkOffer implements NetworkRanker.Scoreable {
     @NonNull public final FullScore score;
     @NonNull public final NetworkCapabilities caps;
     @NonNull public final INetworkOfferCallback callback;
@@ -67,6 +67,20 @@
     }
 
     /**
+     * Get the score filter of this offer
+     */
+    @Override @NonNull public FullScore getScore() {
+        return score;
+    }
+
+    /**
+     * Get the capabilities filter of this offer
+     */
+    @Override @NonNull public NetworkCapabilities getCaps() {
+        return caps;
+    }
+
+    /**
      * Tell the provider for this offer that the network is needed for a request.
      * @param request the request for which the offer is needed
      */
diff --git a/services/core/java/com/android/server/connectivity/NetworkRanker.java b/services/core/java/com/android/server/connectivity/NetworkRanker.java
index 0e4dda2..3aaff59 100644
--- a/services/core/java/com/android/server/connectivity/NetworkRanker.java
+++ b/services/core/java/com/android/server/connectivity/NetworkRanker.java
@@ -16,31 +16,233 @@
 
 package com.android.server.connectivity;
 
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkScore.POLICY_EXITING;
+import static android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY;
+import static android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI;
+
+import static com.android.server.connectivity.FullScore.POLICY_ACCEPT_UNVALIDATED;
+import static com.android.server.connectivity.FullScore.POLICY_EVER_USER_SELECTED;
+import static com.android.server.connectivity.FullScore.POLICY_IS_INVINCIBLE;
+import static com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED;
+import static com.android.server.connectivity.FullScore.POLICY_IS_VPN;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 
+import com.android.net.module.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
+import java.util.function.Predicate;
 
 /**
  * A class that knows how to find the best network matching a request out of a list of networks.
  */
 public class NetworkRanker {
+    // Historically the legacy ints have been 0~100 in principle (though the highest score in
+    // AOSP has always been 90). This is relied on by VPNs that send a legacy score of 101.
+    public static final int LEGACY_INT_MAX = 100;
+
+    /**
+     * A class that can be scored against other scoreables.
+     */
+    public interface Scoreable {
+        /** Get score of this scoreable */
+        FullScore getScore();
+        /** Get capabilities of this scoreable */
+        NetworkCapabilities getCaps();
+    }
+
+    private static final boolean USE_POLICY_RANKING = false;
+
     public NetworkRanker() { }
 
+    // TODO : move to module utils CollectionUtils.
+    @NonNull private static <T> ArrayList<T> filter(@NonNull final Collection<T> source,
+            @NonNull final Predicate<T> test) {
+        final ArrayList<T> matches = new ArrayList<>();
+        for (final T e : source) {
+            if (test.test(e)) {
+                matches.add(e);
+            }
+        }
+        return matches;
+    }
+
     /**
      * Find the best network satisfying this request among the list of passed networks.
      */
-    // Almost equivalent to Collections.max(nais), but allows returning null if no network
-    // satisfies the request.
     @Nullable
     public NetworkAgentInfo getBestNetwork(@NonNull final NetworkRequest request,
+            @NonNull final Collection<NetworkAgentInfo> nais,
+            @Nullable final NetworkAgentInfo currentSatisfier) {
+        final ArrayList<NetworkAgentInfo> candidates = filter(nais, nai -> nai.satisfies(request));
+        if (candidates.size() == 1) return candidates.get(0); // Only one potential satisfier
+        if (candidates.size() <= 0) return null; // No network can satisfy this request
+        if (USE_POLICY_RANKING) {
+            return getBestNetworkByPolicy(candidates, currentSatisfier);
+        } else {
+            return getBestNetworkByLegacyInt(candidates);
+        }
+    }
+
+    // Transport preference order, if it comes down to that.
+    private static final int[] PREFERRED_TRANSPORTS_ORDER = { TRANSPORT_ETHERNET, TRANSPORT_WIFI,
+            TRANSPORT_BLUETOOTH, TRANSPORT_CELLULAR };
+
+    // Function used to partition a list into two working areas depending on whether they
+    // satisfy a predicate. All items satisfying the predicate will be put in |positive|, all
+    // items that don't will be put in |negative|.
+    // This is useful in this file because many of the ranking checks will retain only networks that
+    // satisfy a predicate if any of them do, but keep them all if all of them do. Having working
+    // areas is uncustomary in Java, but this function is called in a fairly intensive manner
+    // and doing allocation quite that often might affect performance quite badly.
+    private static <T> void partitionInto(@NonNull final List<T> source, @NonNull Predicate<T> test,
+            @NonNull final List<T> positive, @NonNull final List<T> negative) {
+        positive.clear();
+        negative.clear();
+        for (final T item : source) {
+            if (test.test(item)) {
+                positive.add(item);
+            } else {
+                negative.add(item);
+            }
+        }
+    }
+
+    @Nullable private <T extends Scoreable> T getBestNetworkByPolicy(
+            @NonNull List<T> candidates,
+            @Nullable final T currentSatisfier) {
+        // Used as working areas.
+        final ArrayList<T> accepted =
+                new ArrayList<>(candidates.size() /* initialCapacity */);
+        final ArrayList<T> rejected =
+                new ArrayList<>(candidates.size() /* initialCapacity */);
+
+        // The following tests will search for a network matching a given criterion. They all
+        // function the same way : if any network matches the criterion, drop from consideration
+        // all networks that don't. To achieve this, the tests below :
+        // 1. partition the list of remaining candidates into accepted and rejected networks.
+        // 2. if only one candidate remains, that's the winner : if accepted.size == 1 return [0]
+        // 3. if multiple remain, keep only the accepted networks and go on to the next criterion.
+        //    Because the working areas will be wiped, a copy of the accepted networks needs to be
+        //    made.
+        // 4. if none remain, the criterion did not help discriminate so keep them all. As an
+        //    optimization, skip creating a new array and go on to the next criterion.
+
+        // If a network is invincible, use it.
+        partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_INVINCIBLE),
+                accepted, rejected);
+        if (accepted.size() == 1) return accepted.get(0);
+        if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
+        // If there is a connected VPN, use it.
+        partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_VPN),
+                accepted, rejected);
+        if (accepted.size() == 1) return accepted.get(0);
+        if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
+        // Selected & Accept-unvalidated policy : if any network has both of these, then don't
+        // choose one that doesn't.
+        partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_EVER_USER_SELECTED)
+                        && nai.getScore().hasPolicy(POLICY_ACCEPT_UNVALIDATED),
+                accepted, rejected);
+        if (accepted.size() == 1) return accepted.get(0);
+        if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
+        // Yield to bad wifi policy : if any wifi has ever been validated, keep only networks
+        // that don't yield to such a wifi network.
+        final boolean anyWiFiEverValidated = CollectionUtils.any(candidates,
+                nai -> nai.getScore().hasPolicy(POLICY_EVER_USER_SELECTED)
+                        && nai.getCaps().hasTransport(TRANSPORT_WIFI));
+        if (anyWiFiEverValidated) {
+            partitionInto(candidates, nai -> !nai.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI),
+                    accepted, rejected);
+            if (accepted.size() == 1) return accepted.get(0);
+            if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+        }
+
+        // If any network is validated (or should be accepted even if it's not validated), then
+        // don't choose one that isn't.
+        partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_VALIDATED)
+                        || nai.getScore().hasPolicy(POLICY_ACCEPT_UNVALIDATED),
+                accepted, rejected);
+        if (accepted.size() == 1) return accepted.get(0);
+        if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
+        // If any network is not exiting, don't choose one that is.
+        partitionInto(candidates, nai -> !nai.getScore().hasPolicy(POLICY_EXITING),
+                accepted, rejected);
+        if (accepted.size() == 1) return accepted.get(0);
+        if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
+        // TODO : If any network is unmetered, don't choose a metered network.
+        // This can't be implemented immediately because prospective networks are always
+        // considered unmetered because factories don't know if the network will be metered.
+        // Saying an unmetered network always beats a metered one would mean that when metered wifi
+        // is connected, the offer for telephony would beat WiFi but the actual metered network
+        // would lose, so we'd have an infinite loop where telephony would continually bring up
+        // a network that is immediately torn down.
+        // Fix this by getting the agent to tell connectivity whether the network they will
+        // bring up is metered. Cell knows that in advance, while WiFi has a good estimate and
+        // can revise it if the network later turns out to be metered.
+        // partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_UNMETERED),
+        //         accepted, rejected);
+        // if (accepted.size() == 1) return accepted.get(0);
+        // if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
+        // If any network is for the default subscription, don't choose a network for another
+        // subscription with the same transport.
+        partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY),
+                accepted, rejected);
+        for (final Scoreable defaultSubNai : accepted) {
+            // Remove all networks without the DEFAULT_SUBSCRIPTION policy and the same transports
+            // as a network that has it.
+            final int[] transports = defaultSubNai.getCaps().getTransportTypes();
+            candidates.removeIf(nai -> !nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY)
+                    && Arrays.equals(transports, nai.getCaps().getTransportTypes()));
+        }
+        if (1 == candidates.size()) return candidates.get(0);
+        // It's guaranteed candidates.size() > 0 because there is at least one with DEFAULT_SUB
+        // policy and only those without it were removed.
+
+        // If some of the networks have a better transport than others, keep only the ones with
+        // the best transports.
+        for (final int transport : PREFERRED_TRANSPORTS_ORDER) {
+            partitionInto(candidates, nai -> nai.getCaps().hasTransport(transport),
+                    accepted, rejected);
+            if (accepted.size() == 1) return accepted.get(0);
+            if (accepted.size() > 0 && rejected.size() > 0) {
+                candidates = new ArrayList<>(accepted);
+                break;
+            }
+        }
+
+        // At this point there are still multiple networks passing all the tests above. If any
+        // of them is the previous satisfier, keep it.
+        if (candidates.contains(currentSatisfier)) return currentSatisfier;
+
+        // If there are still multiple options at this point but none of them is any of the
+        // transports above, it doesn't matter which is returned. They are all the same.
+        return candidates.get(0);
+    }
+
+    // TODO : switch to the policy implementation and remove
+    // Almost equivalent to Collections.max(nais), but allows returning null if no network
+    // satisfies the request.
+    private NetworkAgentInfo getBestNetworkByLegacyInt(
             @NonNull final Collection<NetworkAgentInfo> nais) {
         NetworkAgentInfo bestNetwork = null;
         int bestScore = Integer.MIN_VALUE;
         for (final NetworkAgentInfo nai : nais) {
-            if (!nai.satisfies(request)) continue;
             if (nai.getCurrentScore() > bestScore) {
                 bestNetwork = nai;
                 bestScore = nai.getCurrentScore();
@@ -63,19 +265,37 @@
      * beat a current champion.
      *
      * @param request The request to evaluate against.
-     * @param championScore The currently best network for this request.
+     * @param champion The currently best network for this request.
      * @param offer The offer.
      * @return Whether the offer stands a chance to beat the champion.
      */
     public boolean mightBeat(@NonNull final NetworkRequest request,
-            @Nullable final FullScore championScore,
+            @Nullable final NetworkAgentInfo champion,
             @NonNull final NetworkOffer offer) {
         // If this network can't even satisfy the request then it can't beat anything, not
         // even an absence of network. It can't satisfy it anyway.
         if (!request.canBeSatisfiedBy(offer.caps)) return false;
         // If there is no satisfying network, then this network can beat, because some network
         // is always better than no network.
-        if (null == championScore) return true;
+        if (null == champion) return true;
+        if (USE_POLICY_RANKING) {
+            // If there is no champion, the offer can always beat.
+            // Otherwise rank them.
+            final ArrayList<Scoreable> candidates = new ArrayList<>();
+            candidates.add(champion);
+            candidates.add(offer);
+            return offer == getBestNetworkByPolicy(candidates, champion);
+        } else {
+            return mightBeatByLegacyInt(request, champion.getScore(), offer);
+        }
+    }
+
+    /**
+     * Returns whether an offer might beat a champion according to the legacy int.
+     */
+    public boolean mightBeatByLegacyInt(@NonNull final NetworkRequest request,
+            @Nullable final FullScore championScore,
+            @NonNull final NetworkOffer offer) {
         final int offerIntScore;
         if (offer.caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
             // If the offer might have Internet access, then it might validate.
diff --git a/test/Android.bp b/test/Android.bp
new file mode 100644
index 0000000..a6cad2e
--- /dev/null
+++ b/test/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+// defaults for tests that need to build against framework-connectivity's @hide APIs
+// Only usable from targets that have visibility on framework-connectivity.impl.
+// Instead of using this, consider avoiding to depend on hidden connectivity APIs in
+// tests.
+java_defaults {
+    name: "framework-connectivity-test-defaults",
+    sdk_version: "core_platform", // tests can use @CorePlatformApi's
+    libs: [
+        // order matters: classes in framework-connectivity are resolved before framework,
+        // meaning @hide APIs in framework-connectivity are resolved before @SystemApi
+        // stubs in framework
+        "framework-connectivity.impl",
+        "framework",
+
+        // if sdk_version="" this gets automatically included, but here we need to add manually.
+        "framework-res",
+    ],
+}
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index e1a424f..6f503ac 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -51,12 +51,14 @@
 
 android_test {
     name: "FrameworksNetTests",
-    defaults: ["FrameworksNetTests-jni-defaults"],
+    defaults: [
+        "framework-connectivity-test-defaults",
+        "FrameworksNetTests-jni-defaults",
+    ],
     srcs: [
         "java/**/*.java",
         "java/**/*.kt",
     ],
-    platform_apis: true,
     test_suites: ["device-tests"],
     certificate: "platform",
     jarjar_rules: "jarjar-rules.txt",
diff --git a/tests/net/common/Android.bp b/tests/net/common/Android.bp
index babb81c..439665b 100644
--- a/tests/net/common/Android.bp
+++ b/tests/net/common/Android.bp
@@ -27,7 +27,11 @@
 
 java_library {
     name: "FrameworksNetCommonTests",
-    srcs: ["java/**/*.java", "java/**/*.kt"],
+    defaults: ["framework-connectivity-test-defaults"],
+    srcs: [
+        "java/**/*.java",
+        "java/**/*.kt",
+    ],
     static_libs: [
         "androidx.core_core",
         "androidx.test.rules",
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index e7718b5..6d852bf 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -48,6 +48,7 @@
 
 import static com.android.modules.utils.build.SdkLevel.isAtLeastR;
 import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
+import static com.android.net.module.util.NetworkCapabilitiesUtils.TRANSPORT_USB;
 import static com.android.testutils.MiscAsserts.assertEmpty;
 import static com.android.testutils.MiscAsserts.assertThrows;
 import static com.android.testutils.ParcelUtils.assertParcelSane;
@@ -959,6 +960,11 @@
         assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps());
     }
 
+    private int getMaxTransport() {
+        if (!isAtLeastS() && MAX_TRANSPORT == TRANSPORT_USB) return MAX_TRANSPORT - 1;
+        return MAX_TRANSPORT;
+    }
+
     @Test
     public void testSignalStrength() {
         final NetworkCapabilities nc = new NetworkCapabilities();
@@ -970,7 +976,7 @@
     }
 
     private void assertNoTransport(NetworkCapabilities nc) {
-        for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+        for (int i = MIN_TRANSPORT; i <= getMaxTransport(); i++) {
             assertFalse(nc.hasTransport(i));
         }
     }
@@ -987,7 +993,7 @@
                 assertFalse(nc.hasTransport(i));
             }
         }
-        for (int i = MAX_TRANSPORT; i > maxTransportType; i--) {
+        for (int i = getMaxTransport(); i > maxTransportType; i--) {
             if (positiveSequence) {
                 assertFalse(nc.hasTransport(i));
             } else {
@@ -1001,12 +1007,12 @@
         final NetworkCapabilities nc = new NetworkCapabilities();
         assertNoTransport(nc);
         // Test adding multiple transport types.
-        for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+        for (int i = MIN_TRANSPORT; i <= getMaxTransport(); i++) {
             nc.addTransportType(i);
             checkCurrentTransportTypes(nc, i, true /* positiveSequence */);
         }
         // Test removing multiple transport types.
-        for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+        for (int i = MIN_TRANSPORT; i <= getMaxTransport(); i++) {
             nc.removeTransportType(i);
             checkCurrentTransportTypes(nc, i, false /* positiveSequence */);
         }
diff --git a/tests/net/integration/AndroidManifest.xml b/tests/net/integration/AndroidManifest.xml
index f5a4234..db18500 100644
--- a/tests/net/integration/AndroidManifest.xml
+++ b/tests/net/integration/AndroidManifest.xml
@@ -37,6 +37,7 @@
     <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
     <!-- Reading DeviceConfig flags -->
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
+    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner"/>
 
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index 6245e85..40d068d 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -52,7 +52,6 @@
 import android.util.Range;
 
 import com.android.net.module.util.ArrayTrackRecord;
-import com.android.server.connectivity.ConnectivityConstants;
 import com.android.testutils.HandlerUtils;
 import com.android.testutils.TestableNetworkCallback;
 
@@ -70,7 +69,7 @@
     private final ConditionVariable mDisconnected = new ConditionVariable();
     private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
     private final AtomicBoolean mConnected = new AtomicBoolean(false);
-    private int mScore;
+    private NetworkScore mScore;
     private NetworkAgent mNetworkAgent;
     private int mStartKeepaliveError = SocketKeepalive.ERROR_UNSUPPORTED;
     private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE;
@@ -91,23 +90,23 @@
         mNetworkCapabilities.addTransportType(transport);
         switch (transport) {
             case TRANSPORT_ETHERNET:
-                mScore = 70;
+                mScore = new NetworkScore.Builder().setLegacyInt(70).build();
                 break;
             case TRANSPORT_WIFI:
-                mScore = 60;
+                mScore = new NetworkScore.Builder().setLegacyInt(60).build();
                 break;
             case TRANSPORT_CELLULAR:
-                mScore = 50;
+                mScore = new NetworkScore.Builder().setLegacyInt(50).build();
                 break;
             case TRANSPORT_WIFI_AWARE:
-                mScore = 20;
+                mScore = new NetworkScore.Builder().setLegacyInt(20).build();
                 break;
             case TRANSPORT_VPN:
                 mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN);
                 // VPNs deduce the SUSPENDED capability from their underlying networks and there
                 // is no public API to let VPN services set it.
                 mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
-                mScore = ConnectivityConstants.VPN_DEFAULT_SCORE;
+                mScore = new NetworkScore.Builder().setLegacyInt(101).build();
                 break;
             default:
                 throw new UnsupportedOperationException("unimplemented network type");
@@ -201,16 +200,22 @@
     }
 
     public void setScore(@NonNull final NetworkScore score) {
-        mScore = score.getLegacyInt();
+        mScore = score;
         mNetworkAgent.sendNetworkScore(score);
     }
 
     public void adjustScore(int change) {
-        mScore += change;
+        final int newLegacyScore = mScore.getLegacyInt() + change;
+        final NetworkScore.Builder builder = new NetworkScore.Builder()
+                .setLegacyInt(newLegacyScore);
+        if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI) && newLegacyScore < 50) {
+            builder.setExiting(true);
+        }
+        mScore = builder.build();
         mNetworkAgent.sendNetworkScore(mScore);
     }
 
-    public int getScore() {
+    public NetworkScore getScore() {
         return mScore;
     }
 
diff --git a/tests/net/java/android/net/VpnTransportInfoTest.java b/tests/net/java/android/net/VpnTransportInfoTest.java
index b7a42ec..fee65f0 100644
--- a/tests/net/java/android/net/VpnTransportInfoTest.java
+++ b/tests/net/java/android/net/VpnTransportInfoTest.java
@@ -16,6 +16,9 @@
 
 package android.net;
 
+import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
+import static android.net.NetworkCapabilities.REDACT_NONE;
+
 import static com.android.testutils.ParcelUtils.assertParcelSane;
 
 import static org.junit.Assert.assertEquals;
@@ -33,23 +36,33 @@
 
     @Test
     public void testParceling() {
-        VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
-        assertParcelSane(v, 1 /* fieldCount */);
+        VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, "12345");
+        assertParcelSane(v, 2 /* fieldCount */);
     }
 
     @Test
     public void testEqualsAndHashCode() {
-        VpnTransportInfo v1 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
-        VpnTransportInfo v2 = new VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE);
-        VpnTransportInfo v3 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
-        VpnTransportInfo v4 = new VpnTransportInfo(VpnManager.TYPE_VPN_LEGACY);
-        VpnTransportInfo v5 = new VpnTransportInfo(VpnManager.TYPE_VPN_OEM);
+        String session1 = "12345";
+        String session2 = "6789";
+        VpnTransportInfo v11 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, session1);
+        VpnTransportInfo v12 = new VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE, session1);
+        VpnTransportInfo v13 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, session1);
+        VpnTransportInfo v14 = new VpnTransportInfo(VpnManager.TYPE_VPN_LEGACY, session1);
+        VpnTransportInfo v15 = new VpnTransportInfo(VpnManager.TYPE_VPN_OEM, session1);
+        VpnTransportInfo v21 = new VpnTransportInfo(VpnManager.TYPE_VPN_LEGACY, session2);
 
-        assertNotEquals(v1, v2);
-        assertNotEquals(v3, v4);
-        assertNotEquals(v4, v5);
+        VpnTransportInfo v31 = v11.makeCopy(REDACT_FOR_NETWORK_SETTINGS);
+        VpnTransportInfo v32 = v13.makeCopy(REDACT_FOR_NETWORK_SETTINGS);
 
-        assertEquals(v1, v3);
-        assertEquals(v1.hashCode(), v3.hashCode());
+        assertNotEquals(v11, v12);
+        assertNotEquals(v13, v14);
+        assertNotEquals(v14, v15);
+        assertNotEquals(v14, v21);
+
+        assertEquals(v11, v13);
+        assertEquals(v31, v32);
+        assertEquals(v11.hashCode(), v13.hashCode());
+        assertEquals(REDACT_FOR_NETWORK_SETTINGS, v32.getApplicableRedactions());
+        assertEquals(session1, v15.makeCopy(REDACT_NONE).sessionId);
     }
-}
\ No newline at end of file
+}
diff --git a/tests/net/java/android/net/util/KeepaliveUtilsTest.kt b/tests/net/java/android/net/util/KeepaliveUtilsTest.kt
index b62bdbc..5006d53 100644
--- a/tests/net/java/android/net/util/KeepaliveUtilsTest.kt
+++ b/tests/net/java/android/net/util/KeepaliveUtilsTest.kt
@@ -101,7 +101,7 @@
 
         // Check valid customization generates expected array.
         val validRes = arrayOf("0,3", "1,0", "4,4")
-        val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0)
+        val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0, 0)
 
         val mockContext = getMockedContextWithStringArrayRes(
                 R.array.config_networkSupportedKeepaliveCount,
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index e6ff5d8..93a2bb0 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -18,7 +18,12 @@
 
 import static android.Manifest.permission.CHANGE_NETWORK_STATE;
 import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.NETWORK_FACTORY;
+import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.ACTION_PACKAGE_REPLACED;
 import static android.content.Intent.ACTION_USER_ADDED;
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.ACTION_USER_UNLOCKED;
@@ -31,6 +36,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
 import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
@@ -57,6 +63,7 @@
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_BIP;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
@@ -83,6 +90,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VSIM;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
 import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
@@ -288,7 +296,6 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.net.module.util.ArrayTrackRecord;
 import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
-import com.android.server.connectivity.ConnectivityConstants;
 import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.Nat464Xlat;
 import com.android.server.connectivity.NetworkAgentInfo;
@@ -576,6 +583,7 @@
             final UserManager umMock = createContextAsUser(userHandle, 0 /* flags */)
                     .getSystemService(UserManager.class);
             doReturn(value).when(umMock).isManagedProfile();
+            doReturn(value).when(mUserManager).isManagedProfile(eq(userHandle.getIdentifier()));
         }
 
         @Override
@@ -716,6 +724,9 @@
         private int mProbesSucceeded;
         private String mNmValidationRedirectUrl = null;
         private boolean mNmProvNotificationRequested = false;
+        private Runnable mCreatedCallback;
+        private Runnable mUnwantedCallback;
+        private Runnable mDisconnectedCallback;
 
         private final ConditionVariable mNetworkStatusReceived = new ConditionVariable();
         // Contains the redirectUrl from networkStatus(). Before reading, wait for
@@ -770,6 +781,24 @@
                     mRedirectUrl = redirectUrl;
                     mNetworkStatusReceived.open();
                 }
+
+                @Override
+                public void onNetworkCreated() {
+                    super.onNetworkCreated();
+                    if (mCreatedCallback != null) mCreatedCallback.run();
+                }
+
+                @Override
+                public void onNetworkUnwanted() {
+                    super.onNetworkUnwanted();
+                    if (mUnwantedCallback != null) mUnwantedCallback.run();
+                }
+
+                @Override
+                public void onNetworkDestroyed() {
+                    super.onNetworkDestroyed();
+                    if (mDisconnectedCallback != null) mDisconnectedCallback.run();
+                }
             };
 
             assertEquals(na.getNetwork().netId, nmNetworkCaptor.getValue().netId);
@@ -971,6 +1000,18 @@
             p.timestampMillis = DATA_STALL_TIMESTAMP;
             mNmCallbacks.notifyDataStallSuspected(p);
         }
+
+        public void setCreatedCallback(Runnable r) {
+            mCreatedCallback = r;
+        }
+
+        public void setUnwantedCallback(Runnable r) {
+            mUnwantedCallback = r;
+        }
+
+        public void setDisconnectedCallback(Runnable r) {
+            mDisconnectedCallback = r;
+        }
     }
 
     /**
@@ -1181,10 +1222,12 @@
             if (mAgentRegistered) throw new IllegalStateException("already registered");
             updateState(NetworkInfo.DetailedState.CONNECTING, "registerAgent");
             mConfig = new VpnConfig();
+            mConfig.session = "MySession12345";
             setUids(uids);
             if (!isAlwaysMetered) mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
             mInterface = VPN_IFNAME;
-            mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType()));
+            mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType(),
+                    mConfig.session));
             mMockNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp,
                     mNetworkCapabilities);
             mMockNetworkAgent.waitForIdle(TIMEOUT_MS);
@@ -1361,10 +1404,21 @@
     }
 
     private void mockUidNetworkingBlocked() {
-        doAnswer(i -> NetworkPolicyManager.isUidBlocked(mBlockedReasons, i.getArgument(1))
+        doAnswer(i -> isUidBlocked(mBlockedReasons, i.getArgument(1))
         ).when(mNetworkPolicyManager).isUidNetworkingBlocked(anyInt(), anyBoolean());
     }
 
+    private boolean isUidBlocked(int blockedReasons, boolean meteredNetwork) {
+        final int blockedOnAllNetworksReason = (blockedReasons & ~BLOCKED_METERED_REASON_MASK);
+        if (blockedOnAllNetworksReason != BLOCKED_REASON_NONE) {
+            return true;
+        }
+        if (meteredNetwork) {
+            return blockedReasons != BLOCKED_REASON_NONE;
+        }
+        return false;
+    }
+
     private void setBlockedReasonChanged(int blockedReasons) {
         mBlockedReasons = blockedReasons;
         mPolicyCallback.onUidBlockedReasonChanged(Process.myUid(), blockedReasons);
@@ -2427,8 +2481,7 @@
     public void networkCallbacksSanitizationTest_Sanitize() throws Exception {
         mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
                 PERMISSION_DENIED);
-        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
-                PERMISSION_DENIED);
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED);
         doNetworkCallbacksSanitizationTest(true /* sanitized */);
     }
 
@@ -2436,7 +2489,7 @@
     public void networkCallbacksSanitizationTest_NoSanitize_NetworkStack() throws Exception {
         mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
                 PERMISSION_GRANTED);
-        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED);
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED);
         doNetworkCallbacksSanitizationTest(false /* sanitized */);
     }
 
@@ -2444,7 +2497,7 @@
     public void networkCallbacksSanitizationTest_NoSanitize_Settings() throws Exception {
         mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
                 PERMISSION_DENIED);
-        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
         doNetworkCallbacksSanitizationTest(false /* sanitized */);
     }
 
@@ -2735,10 +2788,14 @@
     }
 
     private void grantUsingBackgroundNetworksPermissionForUid(final int uid) throws Exception {
-        final String myPackageName = mContext.getPackageName();
-        when(mPackageManager.getPackageInfo(eq(myPackageName), eq(GET_PERMISSIONS)))
+        grantUsingBackgroundNetworksPermissionForUid(uid, mContext.getPackageName());
+    }
+
+    private void grantUsingBackgroundNetworksPermissionForUid(
+            final int uid, final String packageName) throws Exception {
+        when(mPackageManager.getPackageInfo(eq(packageName), eq(GET_PERMISSIONS)))
                 .thenReturn(buildPackageInfo(true, uid));
-        mService.mPermissionMonitor.onPackageAdded(myPackageName, uid);
+        mService.mPermissionMonitor.onPackageAdded(packageName, uid);
     }
 
     @Test
@@ -2788,6 +2845,94 @@
     }
 
     @Test
+    public void testNetworkAgentCallbacks() throws Exception {
+        // Keeps track of the order of events that happen in this test.
+        final LinkedBlockingQueue<String> eventOrder = new LinkedBlockingQueue<>();
+
+        final NetworkRequest request = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_WIFI).build();
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        final AtomicReference<Network> wifiNetwork = new AtomicReference<>();
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+
+        // Expectations for state when various callbacks fire. These expectations run on the handler
+        // thread and not on the test thread because they need to prevent the handler thread from
+        // advancing while they examine state.
+
+        // 1. When onCreated fires, netd has been told to create the network.
+        mWiFiNetworkAgent.setCreatedCallback(() -> {
+            eventOrder.offer("onNetworkCreated");
+            wifiNetwork.set(mWiFiNetworkAgent.getNetwork());
+            assertNotNull(wifiNetwork.get());
+            try {
+                verify(mMockNetd).networkCreatePhysical(wifiNetwork.get().getNetId(),
+                        INetd.PERMISSION_NONE);
+            } catch (RemoteException impossible) {
+                fail();
+            }
+        });
+
+        // 2. onNetworkUnwanted isn't precisely ordered with respect to any particular events. Just
+        //    check that it is fired at some point after disconnect.
+        mWiFiNetworkAgent.setUnwantedCallback(() -> eventOrder.offer("onNetworkUnwanted"));
+
+        // 3. While the teardown timer is running, connectivity APIs report the network is gone, but
+        //    netd has not yet been told to destroy it.
+        final Runnable duringTeardown = () -> {
+            eventOrder.offer("timePasses");
+            assertNull(mCm.getLinkProperties(wifiNetwork.get()));
+            try {
+                verify(mMockNetd, never()).networkDestroy(wifiNetwork.get().getNetId());
+            } catch (RemoteException impossible) {
+                fail();
+            }
+        };
+
+        // 4. After onNetworkDisconnected is called, connectivity APIs report the network is gone,
+        // and netd has been told to destroy it.
+        mWiFiNetworkAgent.setDisconnectedCallback(() -> {
+            eventOrder.offer("onNetworkDisconnected");
+            assertNull(mCm.getLinkProperties(wifiNetwork.get()));
+            try {
+                verify(mMockNetd).networkDestroy(wifiNetwork.get().getNetId());
+            } catch (RemoteException impossible) {
+                fail();
+            }
+        });
+
+        // Connect a network, and file a request for it after it has come up, to ensure the nascent
+        // timer is cleared and the test does not have to wait for it. Filing the request after the
+        // network has come up is necessary because ConnectivityService does not appear to clear the
+        // nascent timer if the first request satisfied by the network was filed before the network
+        // connected.
+        // TODO: fix this bug, file the request before connecting, and remove the waitForIdle.
+        mWiFiNetworkAgent.connectWithoutInternet();
+        waitForIdle();
+        mCm.requestNetwork(request, callback);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+
+        // Set teardown delay and make sure CS has processed it.
+        mWiFiNetworkAgent.getNetworkAgent().setTeardownDelayMs(300);
+        waitForIdle();
+
+        // Post the duringTeardown lambda to the handler so it fires while teardown is in progress.
+        // The delay must be long enough it will run after the unregisterNetworkCallback has torn
+        // down the network and started the teardown timer, and short enough that the lambda is
+        // scheduled to run before the teardown timer.
+        final Handler h = new Handler(mCsHandlerThread.getLooper());
+        h.postDelayed(duringTeardown, 150);
+
+        // Disconnect the network and check that events happened in the right order.
+        mCm.unregisterNetworkCallback(callback);
+        assertEquals("onNetworkCreated", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals("onNetworkUnwanted", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals("timePasses", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals("onNetworkDisconnected", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        mCm.unregisterNetworkCallback(callback);
+    }
+
+    @Test
     public void testExplicitlySelected() throws Exception {
         NetworkRequest request = new NetworkRequest.Builder()
                 .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
@@ -2887,10 +3032,11 @@
         // Verify NOT_RESTRICTED is set appropriately
         final NetworkCapabilities nc = new NetworkRequest.Builder().addCapability(capability)
                 .build().networkCapabilities;
-        if (capability == NET_CAPABILITY_CBS || capability == NET_CAPABILITY_DUN ||
-                capability == NET_CAPABILITY_EIMS || capability == NET_CAPABILITY_FOTA ||
-                capability == NET_CAPABILITY_IA || capability == NET_CAPABILITY_IMS ||
-                capability == NET_CAPABILITY_RCS || capability == NET_CAPABILITY_XCAP
+        if (capability == NET_CAPABILITY_CBS || capability == NET_CAPABILITY_DUN
+                || capability == NET_CAPABILITY_EIMS || capability == NET_CAPABILITY_FOTA
+                || capability == NET_CAPABILITY_IA || capability == NET_CAPABILITY_IMS
+                || capability == NET_CAPABILITY_RCS || capability == NET_CAPABILITY_XCAP
+                || capability == NET_CAPABILITY_VSIM || capability == NET_CAPABILITY_BIP
                 || capability == NET_CAPABILITY_ENTERPRISE) {
             assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
         } else {
@@ -2898,8 +3044,9 @@
         }
 
         NetworkCapabilities filter = new NetworkCapabilities();
+        filter.addTransportType(TRANSPORT_CELLULAR);
         filter.addCapability(capability);
-        // Add NOT_VCN_MANAGED capability into filter unconditionally since some request will add
+        // Add NOT_VCN_MANAGED capability into filter unconditionally since some requests will add
         // NOT_VCN_MANAGED automatically but not for NetworkCapabilities,
         // see {@code NetworkCapabilities#deduceNotVcnManagedCapability} for more details.
         filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
@@ -2928,15 +3075,11 @@
 
         // Now bring in a higher scored network.
         TestNetworkAgentWrapper testAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        // Rather than create a validated network which complicates things by registering it's
-        // own NetworkRequest during startup, just bump up the score to cancel out the
-        // unvalidated penalty.
-        testAgent.adjustScore(40);
-
-        // When testAgent connects, because of its 50 score (50 for cell + 40 adjustment score
-        // - 40 penalty for not being validated), it will beat the testFactory's offer, so
-        // the request will be removed.
-        testAgent.connect(false);
+        // When testAgent connects, because of its score (50 legacy int / cell transport)
+        // it will beat or equal the testFactory's offer, so the request will be removed.
+        // Note the agent as validated only if the capability is INTERNET, as it's the only case
+        // where it makes sense.
+        testAgent.connect(NET_CAPABILITY_INTERNET == capability /* validated */);
         testAgent.addCapability(capability);
         testFactory.expectRequestRemove();
         testFactory.assertRequestCountEquals(0);
@@ -2950,17 +3093,18 @@
         testFactory.assertRequestCountEquals(0);
         assertFalse(testFactory.getMyStartRequested());
 
-        // Make the test agent weak enough to have the exact same score as the
-        // factory (50 for cell + 40 adjustment -40 validation penalty - 5 adjustment). Make sure
-        // the factory doesn't see the request.
+        // If using legacy scores, make the test agent weak enough to have the exact same score as
+        // the factory (50 for cell - 5 adjustment). Make sure the factory doesn't see the request.
+        // If not using legacy score, this is a no-op and the "same score removes request" behavior
+        // has already been tested above.
         testAgent.adjustScore(-5);
         expectNoRequestChanged(testFactory);
         assertFalse(testFactory.getMyStartRequested());
 
-        // Make the test agent weak enough to see the two requests (the one that was just sent,
-        // and either the default one or the one sent at the top of this test if the default
-        // won't be seen).
-        testAgent.adjustScore(-45);
+        // Make the test agent weak enough that the factory will see the two requests (the one that
+        // was just sent, and either the default one or the one sent at the top of this test if
+        // the default won't be seen).
+        testAgent.setScore(new NetworkScore.Builder().setLegacyInt(2).setExiting(true).build());
         testFactory.expectRequestAdds(2);
         testFactory.assertRequestCountEquals(2);
         assertTrue(testFactory.getMyStartRequested());
@@ -2992,7 +3136,7 @@
         assertTrue(testFactory.getMyStartRequested());
 
         // Adjust the agent score up again. Expect the request to be withdrawn.
-        testAgent.adjustScore(50);
+        testAgent.setScore(new NetworkScore.Builder().setLegacyInt(50).build());
         testFactory.expectRequestRemove();
         testFactory.assertRequestCountEquals(0);
         assertFalse(testFactory.getMyStartRequested());
@@ -3027,28 +3171,43 @@
         tryNetworkFactoryRequests(NET_CAPABILITY_INTERNET);
         tryNetworkFactoryRequests(NET_CAPABILITY_TRUSTED);
         tryNetworkFactoryRequests(NET_CAPABILITY_NOT_VPN);
+        tryNetworkFactoryRequests(NET_CAPABILITY_VSIM);
+        tryNetworkFactoryRequests(NET_CAPABILITY_BIP);
         // Skipping VALIDATED and CAPTIVE_PORTAL as they're disallowed.
     }
 
     @Test
     public void testRegisterIgnoringScore() throws Exception {
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.adjustScore(30); // score = 60 (wifi) + 30 = 90
+        mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(90).build());
         mWiFiNetworkAgent.connect(true /* validated */);
 
         // Make sure the factory sees the default network
         final NetworkCapabilities filter = new NetworkCapabilities();
+        filter.addTransportType(TRANSPORT_CELLULAR);
         filter.addCapability(NET_CAPABILITY_INTERNET);
         filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
         final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
         handlerThread.start();
         final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
                 mServiceContext, "testFactory", filter, mCsHandlerThread);
-        testFactory.registerIgnoringScore();
-        testFactory.expectRequestAdd();
+        testFactory.register();
 
-        mWiFiNetworkAgent.adjustScore(20); // exceed the maximum score
-        expectNoRequestChanged(testFactory); // still seeing the request
+        final MockNetworkFactory testFactoryAll = new MockNetworkFactory(handlerThread.getLooper(),
+                mServiceContext, "testFactoryAll", filter, mCsHandlerThread);
+        testFactoryAll.registerIgnoringScore();
+
+        // The regular test factory should not see the request, because WiFi is stronger than cell.
+        expectNoRequestChanged(testFactory);
+        // With ignoringScore though the request is seen.
+        testFactoryAll.expectRequestAdd();
+
+        // The legacy int will be ignored anyway, set the only other knob to true
+        mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(110)
+                .setTransportPrimary(true).build());
+
+        expectNoRequestChanged(testFactory); // still not seeing the request
+        expectNoRequestChanged(testFactoryAll); // still seeing the request
 
         mWiFiNetworkAgent.disconnect();
     }
@@ -3480,8 +3639,7 @@
 
     @Test
     public void testCaptivePortalApi() throws Exception {
-        mServiceContext.setPermission(
-                android.Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
 
         final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
         final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
@@ -3515,8 +3673,7 @@
     private TestNetworkCallback setupNetworkCallbackAndConnectToWifi() throws Exception {
         // Grant NETWORK_SETTINGS permission to be able to receive LinkProperties change callbacks
         // with sensitive (captive portal) data
-        mServiceContext.setPermission(
-                android.Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
 
         final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
         final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
@@ -3950,8 +4107,7 @@
     @Test
     public void testRegisterDefaultNetworkCallback() throws Exception {
         // NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback.
-        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
-                PERMISSION_GRANTED);
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
 
         final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
@@ -4110,8 +4266,7 @@
                 () -> mCm.registerDefaultNetworkCallbackAsUid(APP1_UID, callback, handler));
         callback.assertNoCallback();
 
-        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
-                PERMISSION_GRANTED);
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
         mCm.registerSystemDefaultNetworkCallback(callback, handler);
         callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         mCm.unregisterNetworkCallback(callback);
@@ -5481,10 +5636,11 @@
     }
 
     @Test
-    public void testNetworkCallbackMaximum() {
+    public void testNetworkCallbackMaximum() throws Exception {
         final int MAX_REQUESTS = 100;
         final int CALLBACKS = 89;
         final int INTENTS = 11;
+        final int SYSTEM_ONLY_MAX_REQUESTS = 250;
         assertEquals(MAX_REQUESTS, CALLBACKS + INTENTS);
 
         NetworkRequest networkRequest = new NetworkRequest.Builder().build();
@@ -5533,6 +5689,33 @@
                                 new Intent("d"), FLAG_IMMUTABLE))
         );
 
+        // The system gets another SYSTEM_ONLY_MAX_REQUESTS slots.
+        final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+        withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, () -> {
+            ArrayList<NetworkCallback> systemRegistered = new ArrayList<>();
+            for (int i = 0; i < SYSTEM_ONLY_MAX_REQUESTS - 1; i++) {
+                NetworkCallback cb = new NetworkCallback();
+                if (i % 2 == 0) {
+                    mCm.registerDefaultNetworkCallbackAsUid(1000000 + i, cb, handler);
+                } else {
+                    mCm.registerNetworkCallback(networkRequest, cb);
+                }
+                systemRegistered.add(cb);
+            }
+            waitForIdle();
+
+            assertThrows(TooManyRequestsException.class, () ->
+                    mCm.registerDefaultNetworkCallbackAsUid(1001042, new NetworkCallback(),
+                            handler));
+            assertThrows(TooManyRequestsException.class, () ->
+                    mCm.registerNetworkCallback(networkRequest, new NetworkCallback()));
+
+            for (NetworkCallback callback : systemRegistered) {
+                mCm.unregisterNetworkCallback(callback);
+            }
+            waitForIdle();  // Wait for requests to be unregistered before giving up the permission.
+        });
+
         for (Object o : registered) {
             if (o instanceof NetworkCallback) {
                 mCm.unregisterNetworkCallback((NetworkCallback)o);
@@ -5559,6 +5742,30 @@
         waitForIdle();
 
         for (int i = 0; i < MAX_REQUESTS; i++) {
+            NetworkCallback networkCallback = new NetworkCallback();
+            mCm.registerDefaultNetworkCallback(networkCallback);
+            mCm.unregisterNetworkCallback(networkCallback);
+        }
+        waitForIdle();
+
+        for (int i = 0; i < MAX_REQUESTS; i++) {
+            NetworkCallback networkCallback = new NetworkCallback();
+            mCm.registerDefaultNetworkCallback(networkCallback);
+            mCm.unregisterNetworkCallback(networkCallback);
+        }
+        waitForIdle();
+
+        withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, () -> {
+            for (int i = 0; i < MAX_REQUESTS; i++) {
+                NetworkCallback networkCallback = new NetworkCallback();
+                mCm.registerDefaultNetworkCallbackAsUid(1000000 + i, networkCallback,
+                        new Handler(ConnectivityThread.getInstanceLooper()));
+                mCm.unregisterNetworkCallback(networkCallback);
+            }
+        });
+        waitForIdle();
+
+        for (int i = 0; i < MAX_REQUESTS; i++) {
             final PendingIntent pendingIntent = PendingIntent.getBroadcast(
                     mContext, 0 /* requestCode */, new Intent("e" + i), FLAG_IMMUTABLE);
             mCm.requestNetwork(networkRequest, pendingIntent);
@@ -5864,7 +6071,8 @@
         // called again, it does. For example, connect Ethernet, but with a low score, such that it
         // does not become the default network.
         mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
-        mEthernetNetworkAgent.adjustScore(-40);
+        mEthernetNetworkAgent.setScore(
+                new NetworkScore.Builder().setLegacyInt(30).setExiting(true).build());
         mEthernetNetworkAgent.connect(false);
         waitForIdle();
         verify(mStatsManager).notifyNetworkStatus(any(List.class),
@@ -6551,8 +6759,7 @@
     @Test
     public void testVpnNetworkActive() throws Exception {
         // NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback.
-        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
-                PERMISSION_GRANTED);
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
 
         final int uid = Process.myUid();
 
@@ -6740,8 +6947,6 @@
         callback.expectAvailableCallbacksUnvalidated(mMockVpn);
         callback.assertNoCallback();
 
-        assertTrue(mMockVpn.getAgent().getScore() > mEthernetNetworkAgent.getScore());
-        assertEquals(ConnectivityConstants.VPN_DEFAULT_SCORE, mMockVpn.getAgent().getScore());
         assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
 
         NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
@@ -7044,8 +7249,7 @@
     @Test
     public void testRestrictedProfileAffectsVpnUidRanges() throws Exception {
         // NETWORK_SETTINGS is necessary to see the UID ranges in NetworkCapabilities.
-        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
-                PERMISSION_GRANTED);
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
 
         final NetworkRequest request = new NetworkRequest.Builder()
                 .removeCapability(NET_CAPABILITY_NOT_VPN)
@@ -7131,8 +7335,7 @@
         mServiceContext.setPermission(
                 Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
         // Necessary to see the UID ranges in NetworkCapabilities.
-        mServiceContext.setPermission(
-                Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
 
         final NetworkRequest request = new NetworkRequest.Builder()
                 .removeCapability(NET_CAPABILITY_NOT_VPN)
@@ -7620,8 +7823,7 @@
                 Manifest.permission.CONTROL_ALWAYS_ON_VPN, PERMISSION_GRANTED);
         mServiceContext.setPermission(
                 Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
-        mServiceContext.setPermission(
-                Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
 
         final TestNetworkCallback callback = new TestNetworkCallback();
         final NetworkRequest request = new NetworkRequest.Builder()
@@ -7857,8 +8059,7 @@
         mServiceContext.setPermission(
                 Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
         // For LockdownVpnTracker to call registerSystemDefaultNetworkCallback.
-        mServiceContext.setPermission(
-                Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
 
         final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
         final TestNetworkCallback callback = new TestNetworkCallback();
@@ -8047,12 +8248,12 @@
         assertExtraInfoFromCmPresent(mWiFiNetworkAgent);
 
         b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED);
+        b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
         systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
         b1.expectBroadcast();
         callback.expectCapabilitiesThat(mMockVpn, nc -> !nc.hasTransport(TRANSPORT_WIFI));
-        b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);
         mMockVpn.expectStopVpnRunnerPrivileged();
         callback.expectCallback(CallbackEntry.LOST, mMockVpn);
         b2.expectBroadcast();
@@ -8988,8 +9189,7 @@
     private void denyAllLocationPrivilegedPermissions() {
         mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
                 PERMISSION_DENIED);
-        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
-                PERMISSION_DENIED);
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED);
         mServiceContext.setPermission(Manifest.permission.NETWORK_STACK,
                 PERMISSION_DENIED);
         mServiceContext.setPermission(Manifest.permission.NETWORK_SETUP_WIZARD,
@@ -9056,7 +9256,7 @@
 
         final int expectedOwnerUidWithoutIncludeFlag =
                 shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag
-                        ? Process.myUid() : INVALID_UID;
+                        ? myUid : INVALID_UID;
         assertEquals(expectedOwnerUidWithoutIncludeFlag, getOwnerUidNetCapsPermission(
                 myUid, myUid, false /* includeLocationSensitiveInfo */));
 
@@ -9075,22 +9275,26 @@
 
     }
 
+    private void verifyOwnerUidAndTransportInfoNetCapsPermissionPreS() {
+        verifyOwnerUidAndTransportInfoNetCapsPermission(
+                // Ensure that owner uid is included even if the request asks to remove it (which is
+                // the default) since the app has necessary permissions and targetSdk < S.
+                true, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */
+                true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */
+                // Ensure that location info is removed if the request asks to remove it even if the
+                // app has necessary permissions.
+                false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */
+                true /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */
+        );
+    }
+
     @Test
-    public void testCreateWithLocationInfoSanitizedWithFineLocationAfterQ()
+    public void testCreateWithLocationInfoSanitizedWithFineLocationAfterQPreS()
             throws Exception {
         setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
                 Manifest.permission.ACCESS_FINE_LOCATION);
 
-        verifyOwnerUidAndTransportInfoNetCapsPermission(
-                // Ensure that we include owner uid even if the request asks to remove it since the
-                // app has necessary permissions and targetSdk < S.
-                true, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */
-                true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */
-                false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */
-                // Ensure that we remove location info if the request asks to remove it even if the
-                // app has necessary permissions.
-                true /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */
-        );
+        verifyOwnerUidAndTransportInfoNetCapsPermissionPreS();
     }
 
     @Test
@@ -9099,16 +9303,7 @@
         setupLocationPermissions(Build.VERSION_CODES.R, true, AppOpsManager.OPSTR_FINE_LOCATION,
                 Manifest.permission.ACCESS_FINE_LOCATION);
 
-        verifyOwnerUidAndTransportInfoNetCapsPermission(
-                // Ensure that we include owner uid even if the request asks to remove it since the
-                // app has necessary permissions and targetSdk < S.
-                true, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */
-                true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */
-                false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */
-                // Ensure that we remove location info if the request asks to remove it even if the
-                // app has necessary permissions.
-                true /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */
-        );
+        verifyOwnerUidAndTransportInfoNetCapsPermissionPreS();
     }
 
     @Test
@@ -9119,13 +9314,13 @@
                 Manifest.permission.ACCESS_FINE_LOCATION);
 
         verifyOwnerUidAndTransportInfoNetCapsPermission(
-                // Ensure that we owner UID if the request asks us to remove it even if the app
-                // has necessary permissions since targetSdk >= S.
+                // Ensure that the owner UID is removed if the request asks us to remove it even
+                // if the app has necessary permissions since targetSdk >= S.
                 false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */
                 true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */
-                false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */
-                // Ensure that we remove location info if the request asks to remove it even if the
+                // Ensure that location info is removed if the request asks to remove it even if the
                 // app has necessary permissions.
+                false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */
                 true /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */
         );
     }
@@ -9136,15 +9331,15 @@
         setupLocationPermissions(Build.VERSION_CODES.P, true, AppOpsManager.OPSTR_COARSE_LOCATION,
                 Manifest.permission.ACCESS_COARSE_LOCATION);
 
+        verifyOwnerUidAndTransportInfoNetCapsPermissionPreS();
+    }
+
+    private void verifyOwnerUidAndTransportInfoNetCapsNotIncluded() {
         verifyOwnerUidAndTransportInfoNetCapsPermission(
-                // Ensure that we owner UID if the request asks us to remove it even if the app
-                // has necessary permissions since targetSdk >= S.
-                true, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */
-                true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */
+                false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */
+                false, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */
                 false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */
-                // Ensure that we remove location info if the request asks to remove it even if the
-                // app has necessary permissions.
-                true /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */
+                false /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */
         );
     }
 
@@ -9154,12 +9349,7 @@
         setupLocationPermissions(Build.VERSION_CODES.Q, false, AppOpsManager.OPSTR_FINE_LOCATION,
                 Manifest.permission.ACCESS_FINE_LOCATION);
 
-        verifyOwnerUidAndTransportInfoNetCapsPermission(
-                false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */
-                false, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */
-                false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */
-                false /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */
-        );
+        verifyOwnerUidAndTransportInfoNetCapsNotIncluded();
     }
 
     @Test
@@ -9181,26 +9371,17 @@
         setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_COARSE_LOCATION,
                 Manifest.permission.ACCESS_COARSE_LOCATION);
 
-        verifyOwnerUidAndTransportInfoNetCapsPermission(
-                false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */
-                false, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */
-                false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */
-                false /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */
-        );
+        verifyOwnerUidAndTransportInfoNetCapsNotIncluded();
     }
 
     @Test
-    public void testCreateWithLocationInfoSanitizedWithoutLocationPermission()
+    public void testCreateWithLocationInfoSanitizedWithCoarseLocationAfterS()
             throws Exception {
         // Test that not having fine location permission leads to sanitization.
-        setupLocationPermissions(Build.VERSION_CODES.Q, true, null /* op */, null /* perm */);
+        setupLocationPermissions(Build.VERSION_CODES.S, true, AppOpsManager.OPSTR_COARSE_LOCATION,
+                Manifest.permission.ACCESS_COARSE_LOCATION);
 
-        verifyOwnerUidAndTransportInfoNetCapsPermission(
-                false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */
-                false, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */
-                false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */
-                false /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */
-        );
+        verifyOwnerUidAndTransportInfoNetCapsNotIncluded();
     }
 
     @Test
@@ -9245,7 +9426,7 @@
     @Test
     public void testCreateForCallerWithLocalMacAddressSanitizedWithSettingsPermission()
             throws Exception {
-        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
 
         final TransportInfo transportInfo = mock(TransportInfo.class);
         when(transportInfo.getApplicableRedactions())
@@ -10245,6 +10426,12 @@
                 .thenReturn(applicationInfo);
     }
 
+    private void mockGetApplicationInfoThrowsNameNotFound(@NonNull final String packageName)
+            throws Exception {
+        when(mPackageManager.getApplicationInfo(eq(packageName), anyInt()))
+                .thenThrow(new PackageManager.NameNotFoundException(packageName));
+    }
+
     private void mockHasSystemFeature(@NonNull final String featureName,
             @NonNull final boolean hasFeature) {
         when(mPackageManager.hasSystemFeature(eq(featureName)))
@@ -10641,8 +10828,7 @@
 
     private void registerDefaultNetworkCallbacks() {
         // Using Manifest.permission.NETWORK_SETTINGS for registerSystemDefaultNetworkCallback()
-        mServiceContext.setPermission(
-                Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
         mSystemDefaultNetworkCallback = new TestNetworkCallback();
         mDefaultNetworkCallback = new TestNetworkCallback();
         mProfileDefaultNetworkCallback = new TestNetworkCallback();
@@ -10652,8 +10838,7 @@
         registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallback,
                 TEST_WORK_PROFILE_APP_UID);
         // TODO: test using ConnectivityManager#registerDefaultNetworkCallbackAsUid as well.
-        mServiceContext.setPermission(
-                Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED);
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED);
     }
 
     private void unregisterDefaultNetworkCallbacks() {
@@ -10703,15 +10888,23 @@
             @NonNull final UidRangeParcel[] uidRanges,
             @NonNull final String testPackageName)
             throws Exception {
-        mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true);
-
         // These tests work off a single UID therefore using 'start' is valid.
         mockGetApplicationInfo(testPackageName, uidRanges[0].start);
 
+        setOemNetworkPreference(networkPrefToSetup, testPackageName);
+    }
+
+    private void setOemNetworkPreference(final int networkPrefToSetup,
+            @NonNull final String... testPackageNames)
+            throws Exception {
+        mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true);
+
         // Build OemNetworkPreferences object
-        final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
-                .addNetworkPreference(testPackageName, networkPrefToSetup)
-                .build();
+        final OemNetworkPreferences.Builder builder = new OemNetworkPreferences.Builder();
+        for (final String packageName : testPackageNames) {
+            builder.addNetworkPreference(packageName, networkPrefToSetup);
+        }
+        final OemNetworkPreferences pref = builder.build();
 
         // Act on ConnectivityService.setOemNetworkPreference()
         final TestOemListenerCallback oemPrefListener = new TestOemListenerCallback();
@@ -10808,7 +11001,7 @@
         defaultNetworkCallback.assertNoCallback();
 
         final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback();
-        withPermission(Manifest.permission.NETWORK_SETTINGS, () ->
+        withPermission(NETWORK_SETTINGS, () ->
                 mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback,
                         new Handler(ConnectivityThread.getInstanceLooper())));
 
@@ -10856,7 +11049,7 @@
         defaultNetworkCallback.assertNoCallback();
 
         final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback();
-        withPermission(Manifest.permission.NETWORK_SETTINGS, () ->
+        withPermission(NETWORK_SETTINGS, () ->
                 mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback,
                         new Handler(ConnectivityThread.getInstanceLooper())));
 
@@ -10898,7 +11091,7 @@
         defaultNetworkCallback.assertNoCallback();
 
         final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback();
-        withPermission(Manifest.permission.NETWORK_SETTINGS, () ->
+        withPermission(NETWORK_SETTINGS, () ->
                 mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback,
                         new Handler(ConnectivityThread.getInstanceLooper())));
 
@@ -11310,8 +11503,7 @@
         // Arrange PackageManager mocks
         final int secondUserTestPackageUid = UserHandle.getUid(secondUser, TEST_PACKAGE_UID);
         final UidRangeParcel[] uidRangesSingleUser =
-                toUidRangeStableParcels(
-                        uidRangesForUids(TEST_PACKAGE_UID));
+                toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID));
         final UidRangeParcel[] uidRangesBothUsers =
                 toUidRangeStableParcels(
                         uidRangesForUids(TEST_PACKAGE_UID, secondUserTestPackageUid));
@@ -11358,6 +11550,84 @@
                 false /* shouldDestroyNetwork */);
     }
 
+    @Test
+    public void testMultilayerForPackageChangesEvaluatesCorrectly()
+            throws Exception {
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OEM_NETWORK_PREFERENCE_OEM_PAID;
+        final String packageScheme = "package:";
+
+        // Arrange PackageManager mocks
+        final String packageToInstall = "package.to.install";
+        final int packageToInstallUid = 81387;
+        final UidRangeParcel[] uidRangesSinglePackage =
+                toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID));
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+        mockGetApplicationInfoThrowsNameNotFound(packageToInstall);
+        setOemNetworkPreference(networkPref, TEST_PACKAGE_NAME, packageToInstall);
+        grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid(), packageToInstall);
+
+        // Verify the starting state. No networks should be connected.
+        verifySetOemNetworkPreferenceForPreference(uidRangesSinglePackage,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Test that we correctly add the expected values for installed packages.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
+        verifySetOemNetworkPreferenceForPreference(uidRangesSinglePackage,
+                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Set the system to recognize the package to be installed
+        mockGetApplicationInfo(packageToInstall, packageToInstallUid);
+        final UidRangeParcel[] uidRangesAllPackages =
+                toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID, packageToInstallUid));
+
+        // Send a broadcast indicating a package was installed.
+        final Intent addedIntent = new Intent(ACTION_PACKAGE_ADDED);
+        addedIntent.setData(Uri.parse(packageScheme + packageToInstall));
+        processBroadcast(addedIntent);
+
+        // Test the single package is removed and the combined packages are added.
+        verifySetOemNetworkPreferenceForPreference(uidRangesAllPackages, uidRangesSinglePackage,
+                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Set the system to no longer recognize the package to be installed
+        mockGetApplicationInfoThrowsNameNotFound(packageToInstall);
+
+        // Send a broadcast indicating a package was removed.
+        final Intent removedIntent = new Intent(ACTION_PACKAGE_REMOVED);
+        removedIntent.setData(Uri.parse(packageScheme + packageToInstall));
+        processBroadcast(removedIntent);
+
+        // Test the combined packages are removed and the single package is added.
+        verifySetOemNetworkPreferenceForPreference(uidRangesSinglePackage, uidRangesAllPackages,
+                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Set the system to change the installed package's uid
+        final int replacedTestPackageUid = TEST_PACKAGE_UID + 1;
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, replacedTestPackageUid);
+        final UidRangeParcel[] uidRangesReplacedPackage =
+                toUidRangeStableParcels(uidRangesForUids(replacedTestPackageUid));
+
+        // Send a broadcast indicating a package was replaced.
+        final Intent replacedIntent = new Intent(ACTION_PACKAGE_REPLACED);
+        replacedIntent.setData(Uri.parse(packageScheme + TEST_PACKAGE_NAME));
+        processBroadcast(replacedIntent);
+
+        // Test the original uid is removed and is replaced with the new uid.
+        verifySetOemNetworkPreferenceForPreference(uidRangesReplacedPackage, uidRangesSinglePackage,
+                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                false /* shouldDestroyNetwork */);
+    }
+
     /**
      * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID in the following order:
      * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID -> fallback
@@ -11470,14 +11740,14 @@
                 new LinkProperties(), oemPaidNc);
         oemPaidAgent.connect(true);
 
-        // The oemPaidAgent has score 50 (default for cell) so it beats what the oemPaidFactory can
+        // The oemPaidAgent has score 50/cell transport, so it beats what the oemPaidFactory can
         // provide, therefore it loses the request.
         oemPaidFactory.expectRequestRemove();
         oemPaidFactory.assertRequestCountEquals(0);
         expectNoRequestChanged(internetFactory);
         internetFactory.assertRequestCountEquals(0);
 
-        oemPaidAgent.adjustScore(-30);
+        oemPaidAgent.setScore(new NetworkScore.Builder().setLegacyInt(20).setExiting(true).build());
         // Now the that the agent is weak, the oemPaidFactory can beat the existing network for the
         // OEM_PAID request. The internet factory however can't beat a network that has OEM_PAID
         // for the preference request, so it doesn't see the request.
@@ -11507,7 +11777,8 @@
         // Now WiFi connects and it's unmetered, but it's weaker than cell.
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
-        mWiFiNetworkAgent.adjustScore(-30); // Not the best Internet network, but unmetered
+        mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(30).setExiting(true)
+                .build()); // Not the best Internet network, but unmetered
         mWiFiNetworkAgent.connect(true);
 
         // The OEM_PAID preference prefers an unmetered network to an OEM_PAID network, so
@@ -12289,4 +12560,68 @@
                         mCm.setProfileNetworkPreference(testHandle,
                                 PROFILE_NETWORK_PREFERENCE_ENTERPRISE, null, null));
     }
+
+    @Test
+    public void testSubIdsClearedWithoutNetworkFactoryPermission() throws Exception {
+        mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED);
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        nc.setSubIds(Collections.singleton(Process.myUid()));
+
+        final NetworkCapabilities result =
+                mService.networkCapabilitiesRestrictedForCallerPermissions(
+                        nc, Process.myPid(), Process.myUid());
+        assertTrue(result.getSubIds().isEmpty());
+    }
+
+    @Test
+    public void testSubIdsExistWithNetworkFactoryPermission() throws Exception {
+        mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+
+        final Set<Integer> subIds = Collections.singleton(Process.myUid());
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        nc.setSubIds(subIds);
+
+        final NetworkCapabilities result =
+                mService.networkCapabilitiesRestrictedForCallerPermissions(
+                        nc, Process.myPid(), Process.myUid());
+        assertEquals(subIds, result.getSubIds());
+    }
+
+    private NetworkRequest getRequestWithSubIds() {
+        return new NetworkRequest.Builder()
+                .setSubIds(Collections.singleton(Process.myUid()))
+                .build();
+    }
+
+    @Test
+    public void testNetworkRequestWithSubIdsWithNetworkFactoryPermission() throws Exception {
+        mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
+        final NetworkCallback networkCallback1 = new NetworkCallback();
+        final NetworkCallback networkCallback2 = new NetworkCallback();
+
+        mCm.requestNetwork(getRequestWithSubIds(), networkCallback1);
+        mCm.requestNetwork(getRequestWithSubIds(), pendingIntent);
+        mCm.registerNetworkCallback(getRequestWithSubIds(), networkCallback2);
+
+        mCm.unregisterNetworkCallback(networkCallback1);
+        mCm.releaseNetworkRequest(pendingIntent);
+        mCm.unregisterNetworkCallback(networkCallback2);
+    }
+
+    @Test
+    public void testNetworkRequestWithSubIdsWithoutNetworkFactoryPermission() throws Exception {
+        mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED);
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
+
+        final Class<SecurityException> expected = SecurityException.class;
+        assertThrows(
+                expected, () -> mCm.requestNetwork(getRequestWithSubIds(), new NetworkCallback()));
+        assertThrows(expected, () -> mCm.requestNetwork(getRequestWithSubIds(), pendingIntent));
+        assertThrows(
+                expected,
+                () -> mCm.registerNetworkCallback(getRequestWithSubIds(), new NetworkCallback()));
+    }
 }
diff --git a/tests/net/java/com/android/server/connectivity/FullScoreTest.kt b/tests/net/java/com/android/server/connectivity/FullScoreTest.kt
index 2864bc7..f0d7d86 100644
--- a/tests/net/java/com/android/server/connectivity/FullScoreTest.kt
+++ b/tests/net/java/com/android/server/connectivity/FullScoreTest.kt
@@ -56,7 +56,7 @@
             if (vpn) addTransportType(NetworkCapabilities.TRANSPORT_VPN)
             if (validated) addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
         }.build()
-        return mixInScore(nc, nac)
+        return mixInScore(nc, nac, false /* avoidBadWifi */)
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt
index 86c9116..1348c6a 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt
+++ b/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt
@@ -43,7 +43,7 @@
         val nais = scores.map { makeNai(true, it) }
         val bestNetwork = nais[2] // The one with the top score
         val someRequest = mock(NetworkRequest::class.java)
-        assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais))
+        assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais, bestNetwork))
     }
 
     @Test
@@ -52,20 +52,20 @@
                 makeNai(false, 60), makeNai(true, 23), makeNai(false, 68))
         val bestNetwork = nais[1] // Top score that's satisfying
         val someRequest = mock(NetworkRequest::class.java)
-        assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais))
+        assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais, nais[1]))
     }
 
     @Test
     fun testNoMatch() {
         val nais = listOf(makeNai(false, 20), makeNai(false, 50), makeNai(false, 90))
         val someRequest = mock(NetworkRequest::class.java)
-        assertNull(ranker.getBestNetwork(someRequest, nais))
+        assertNull(ranker.getBestNetwork(someRequest, nais, null))
     }
 
     @Test
     fun testEmpty() {
         val someRequest = mock(NetworkRequest::class.java)
-        assertNull(ranker.getBestNetwork(someRequest, emptyList()))
+        assertNull(ranker.getBestNetwork(someRequest, emptyList(), null))
     }
 
     // Make sure the ranker is "stable" (as in stable sort), that is, it always returns the FIRST
@@ -75,10 +75,10 @@
         val nais1 = listOf(makeNai(true, 30), makeNai(true, 30), makeNai(true, 30),
                 makeNai(true, 30), makeNai(true, 30), makeNai(true, 30))
         val someRequest = mock(NetworkRequest::class.java)
-        assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1))
+        assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1, nais1[0]))
 
         val nais2 = listOf(makeNai(true, 30), makeNai(true, 50), makeNai(true, 20),
                 makeNai(true, 50), makeNai(true, 50), makeNai(true, 40))
-        assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2))
+        assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2, nais2[1]))
     }
 }
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 9334e2c..eeeb4fb 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -89,6 +89,7 @@
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.net.UnderlyingNetworkInfo;
+import android.net.TelephonyNetworkSpecifier;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.os.ConditionVariable;
 import android.os.Handler;
@@ -1280,6 +1281,77 @@
     }
 
     @Test
+    public void testDualVilteProviderStats() throws Exception {
+        // Pretend that network comes online.
+        expectDefaultSettings();
+        final int subId1 = 1;
+        final int subId2 = 2;
+        final NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{
+                buildImsState(IMSI_1, subId1, TEST_IFACE),
+                buildImsState(IMSI_2, subId2, TEST_IFACE2)};
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+
+        // Register custom provider and retrieve callback.
+        final TestableNetworkStatsProviderBinder provider =
+                new TestableNetworkStatsProviderBinder();
+        final INetworkStatsProviderCallback cb =
+                mService.registerNetworkStatsProvider("TEST", provider);
+        assertNotNull(cb);
+
+        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
+
+        // Verifies that one requestStatsUpdate will be called during iface update.
+        provider.expectOnRequestStatsUpdate(0 /* unused */);
+
+        // Create some initial traffic and report to the service.
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        final String vtIface1 = NetworkStats.IFACE_VT + subId1;
+        final String vtIface2 = NetworkStats.IFACE_VT + subId2;
+        final NetworkStats expectedStats = new NetworkStats(0L, 1)
+                .addEntry(new NetworkStats.Entry(vtIface1, UID_RED, SET_DEFAULT,
+                        TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES,
+                        128L, 2L, 128L, 2L, 1L))
+                .addEntry(new NetworkStats.Entry(vtIface2, UID_RED, SET_DEFAULT,
+                        TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES,
+                        64L, 1L, 64L, 1L, 1L));
+        cb.notifyStatsUpdated(0 /* unused */, expectedStats, expectedStats);
+
+        // Make another empty mutable stats object. This is necessary since the new NetworkStats
+        // object will be used to compare with the old one in NetworkStatsRecoder, two of them
+        // cannot be the same object.
+        expectNetworkStatsUidDetail(buildEmptyStats());
+
+        forcePollAndWaitForIdle();
+
+        // Verifies that one requestStatsUpdate and setAlert will be called during polling.
+        provider.expectOnRequestStatsUpdate(0 /* unused */);
+        provider.expectOnSetAlert(MB_IN_BYTES);
+
+        // Verifies that service recorded history, does not verify uid tag part.
+        assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 1);
+
+        // Verifies that onStatsUpdated updates the stats accordingly.
+        NetworkStats stats = mSession.getSummaryForAllUid(
+                sTemplateImsi1, Long.MIN_VALUE, Long.MAX_VALUE, true);
+        assertEquals(1, stats.size());
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 1L);
+
+        stats = mSession.getSummaryForAllUid(
+                sTemplateImsi2, Long.MIN_VALUE, Long.MAX_VALUE, true);
+        assertEquals(1, stats.size());
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 1L);
+
+        // Verifies that unregister the callback will remove the provider from service.
+        cb.unregister();
+        forcePollAndWaitForIdle();
+        provider.assertNoCallback();
+    }
+
+    @Test
     public void testStatsProviderSetAlert() throws Exception {
         // Pretend that network comes online.
         expectDefaultSettings();
@@ -1616,6 +1688,20 @@
                 TYPE_MOBILE);
     }
 
+    private static NetworkStateSnapshot buildImsState(
+            String subscriberId, int subId, String ifaceName) {
+        final LinkProperties prop = new LinkProperties();
+        prop.setInterfaceName(ifaceName);
+        final NetworkCapabilities capabilities = new NetworkCapabilities();
+        capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, true);
+        capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true);
+        capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_IMS, true);
+        capabilities.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+        capabilities.setNetworkSpecifier(new TelephonyNetworkSpecifier(subId));
+        return new NetworkStateSnapshot(
+                MOBILE_NETWORK, capabilities, prop, subscriberId, TYPE_MOBILE);
+    }
+
     private long getElapsedRealtime() {
         return mElapsedRealtime;
     }