Merge changes I852e3a53,I86755647,I0ed8b0c6

* changes:
  Remove unused INetworkManagementService from IpSecService
  Use NetdUtils instead of NetworkManagementService in Vpn
  Use NetdUtils instead of NetworkManagementService in IpSecService
diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java
index e1ef8b5..813fde1 100644
--- a/core/java/android/net/NetworkState.java
+++ b/core/java/android/net/NetworkState.java
@@ -17,6 +17,7 @@
 package android.net;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
@@ -40,7 +41,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public final Network network;
     public final String subscriberId;
-    public final String networkId;
+    public final int legacyNetworkType;
 
     private NetworkState() {
         networkInfo = null;
@@ -48,18 +49,34 @@
         networkCapabilities = null;
         network = null;
         subscriberId = null;
-        networkId = null;
+        legacyNetworkType = 0;
     }
 
+    public NetworkState(int legacyNetworkType, @NonNull LinkProperties linkProperties,
+            @NonNull NetworkCapabilities networkCapabilities, @NonNull Network network,
+            @Nullable String subscriberId) {
+        this(legacyNetworkType, new NetworkInfo(legacyNetworkType, 0, null, null), linkProperties,
+                networkCapabilities, network, subscriberId);
+    }
+
+    // Constructor that used internally in ConnectivityService mainline module.
     public NetworkState(@NonNull NetworkInfo networkInfo, @NonNull LinkProperties linkProperties,
             @NonNull NetworkCapabilities networkCapabilities, @NonNull Network network,
-            String subscriberId, String networkId) {
+            @Nullable String subscriberId) {
+        this(networkInfo.getType(), networkInfo, linkProperties,
+                networkCapabilities, network, subscriberId);
+    }
+
+    public NetworkState(int legacyNetworkType, @NonNull NetworkInfo networkInfo,
+            @NonNull LinkProperties linkProperties,
+            @NonNull NetworkCapabilities networkCapabilities, @NonNull Network network,
+            @Nullable String subscriberId) {
         this.networkInfo = networkInfo;
         this.linkProperties = linkProperties;
         this.networkCapabilities = networkCapabilities;
         this.network = network;
         this.subscriberId = subscriberId;
-        this.networkId = networkId;
+        this.legacyNetworkType = legacyNetworkType;
 
         // This object is an atomic view of a network, so the various components
         // should always agree on roaming state.
@@ -79,7 +96,7 @@
         networkCapabilities = in.readParcelable(null);
         network = in.readParcelable(null);
         subscriberId = in.readString();
-        networkId = in.readString();
+        legacyNetworkType = in.readInt();
     }
 
     @Override
@@ -94,7 +111,7 @@
         out.writeParcelable(networkCapabilities, flags);
         out.writeParcelable(network, flags);
         out.writeString(subscriberId);
-        out.writeString(networkId);
+        out.writeInt(legacyNetworkType);
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/net/OemNetworkPreferences.java b/core/java/android/net/OemNetworkPreferences.java
index 5e56164..b403455 100644
--- a/core/java/android/net/OemNetworkPreferences.java
+++ b/core/java/android/net/OemNetworkPreferences.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.os.Parcelable;
 
@@ -29,11 +30,12 @@
 import java.util.Objects;
 
 /** @hide */
+@SystemApi
 public final class OemNetworkPreferences implements Parcelable {
     /**
-     * Use default behavior requesting networks. Equivalent to not setting any preference at all.
+     * Default in case this value is not set. Using it will result in an error.
      */
-    public static final int OEM_NETWORK_PREFERENCE_DEFAULT = 0;
+    public static final int OEM_NETWORK_PREFERENCE_UNINITIALIZED = 0;
 
     /**
      * If an unmetered network is available, use it.
@@ -45,17 +47,17 @@
     /**
      * If an unmetered network is available, use it.
      * Otherwise, if a network with the OEM_PAID capability is available, use it.
-     * Otherwise, the app doesn't get a network.
+     * Otherwise, the app doesn't get a default network.
      */
     public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2;
 
     /**
-     * Prefer only NET_CAPABILITY_OEM_PAID networks.
+     * Use only NET_CAPABILITY_OEM_PAID networks.
      */
     public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY = 3;
 
     /**
-     * Prefer only NET_CAPABILITY_OEM_PRIVATE networks.
+     * Use only NET_CAPABILITY_OEM_PRIVATE networks.
      */
     public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4;
 
@@ -95,8 +97,6 @@
     /**
      * Builder used to create {@link OemNetworkPreferences} objects.  Specify the preferred Network
      * to package name mappings.
-     *
-     * @hide
      */
     public static final class Builder {
         private final Bundle mNetworkMappings;
@@ -135,7 +135,7 @@
          * @return The builder to facilitate chaining.
          */
         @NonNull
-        public Builder removeNetworkPreference(@NonNull final String packageName) {
+        public Builder clearNetworkPreference(@NonNull final String packageName) {
             Objects.requireNonNull(packageName);
             mNetworkMappings.remove(packageName);
             return this;
@@ -160,7 +160,7 @@
 
     /** @hide */
     @IntDef(prefix = "OEM_NETWORK_PREFERENCE_", value = {
-            OEM_NETWORK_PREFERENCE_DEFAULT,
+            OEM_NETWORK_PREFERENCE_UNINITIALIZED,
             OEM_NETWORK_PREFERENCE_OEM_PAID,
             OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK,
             OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY,
@@ -174,12 +174,14 @@
      *
      * @param value int value of OemNetworkPreference
      * @return string version of OemNetworkPreference
+     *
+     * @hide
      */
     @NonNull
     public static String oemNetworkPreferenceToString(@OemNetworkPreference int value) {
         switch (value) {
-            case OEM_NETWORK_PREFERENCE_DEFAULT:
-                return "OEM_NETWORK_PREFERENCE_DEFAULT";
+            case OEM_NETWORK_PREFERENCE_UNINITIALIZED:
+                return "OEM_NETWORK_PREFERENCE_UNINITIALIZED";
             case OEM_NETWORK_PREFERENCE_OEM_PAID:
                 return "OEM_NETWORK_PREFERENCE_OEM_PAID";
             case OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK:
diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java
index 3bc0f9c..b172ccc 100644
--- a/core/java/android/net/UidRange.java
+++ b/core/java/android/net/UidRange.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 
 import java.util.Collection;
 
@@ -45,6 +46,14 @@
         return new UidRange(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1);
     }
 
+    /** Creates a UidRange for the specified user. */
+    public static UidRange createForUser(UserHandle user) {
+        final UserHandle nextUser = UserHandle.of(user.getIdentifier() + 1);
+        final int start = UserHandle.getUid(user, 0 /* appId */);
+        final int end = UserHandle.getUid(nextUser, 0) - 1;
+        return new UidRange(start, end);
+    }
+
     /** Returns the smallest user Id which is contained in this UidRange */
     public int getStartUser() {
         return start / PER_USER_RANGE;
diff --git a/framework/Android.bp b/framework/Android.bp
index 8db8d76..ffca971 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -14,16 +14,48 @@
 // limitations under the License.
 //
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
 // TODO: use a java_library in the bootclasspath instead
 filegroup {
-    name: "framework-connectivity-sources",
+    name: "framework-connectivity-internal-sources",
     srcs: [
         "src/**/*.java",
         "src/**/*.aidl",
     ],
     path: "src",
     visibility: [
+        "//visibility:private",
+    ],
+}
+
+filegroup {
+    name: "framework-connectivity-aidl-export-sources",
+    srcs: [
+        "aidl-export/**/*.aidl",
+    ],
+    path: "aidl-export",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+// TODO: use a java_library in the bootclasspath instead
+filegroup {
+    name: "framework-connectivity-sources",
+    srcs: [
+        ":framework-connectivity-internal-sources",
+        ":framework-connectivity-aidl-export-sources",
+    ],
+    visibility: [
         "//frameworks/base",
         "//packages/modules/Connectivity:__subpackages__",
     ],
-}
\ No newline at end of file
+}
diff --git a/framework/src/android/net/CaptivePortalData.aidl b/framework/aidl-export/android/net/CaptivePortalData.aidl
similarity index 100%
rename from framework/src/android/net/CaptivePortalData.aidl
rename to framework/aidl-export/android/net/CaptivePortalData.aidl
diff --git a/framework/src/android/net/ConnectivityDiagnosticsManager.aidl b/framework/aidl-export/android/net/ConnectivityDiagnosticsManager.aidl
similarity index 100%
rename from framework/src/android/net/ConnectivityDiagnosticsManager.aidl
rename to framework/aidl-export/android/net/ConnectivityDiagnosticsManager.aidl
diff --git a/framework/src/android/net/DhcpInfo.aidl b/framework/aidl-export/android/net/DhcpInfo.aidl
similarity index 100%
rename from framework/src/android/net/DhcpInfo.aidl
rename to framework/aidl-export/android/net/DhcpInfo.aidl
diff --git a/framework/src/android/net/IpConfiguration.aidl b/framework/aidl-export/android/net/IpConfiguration.aidl
similarity index 100%
rename from framework/src/android/net/IpConfiguration.aidl
rename to framework/aidl-export/android/net/IpConfiguration.aidl
diff --git a/framework/src/android/net/IpPrefix.aidl b/framework/aidl-export/android/net/IpPrefix.aidl
similarity index 100%
rename from framework/src/android/net/IpPrefix.aidl
rename to framework/aidl-export/android/net/IpPrefix.aidl
diff --git a/framework/src/android/net/KeepalivePacketData.aidl b/framework/aidl-export/android/net/KeepalivePacketData.aidl
similarity index 100%
rename from framework/src/android/net/KeepalivePacketData.aidl
rename to framework/aidl-export/android/net/KeepalivePacketData.aidl
diff --git a/framework/src/android/net/LinkAddress.aidl b/framework/aidl-export/android/net/LinkAddress.aidl
similarity index 100%
rename from framework/src/android/net/LinkAddress.aidl
rename to framework/aidl-export/android/net/LinkAddress.aidl
diff --git a/framework/src/android/net/LinkProperties.aidl b/framework/aidl-export/android/net/LinkProperties.aidl
similarity index 100%
rename from framework/src/android/net/LinkProperties.aidl
rename to framework/aidl-export/android/net/LinkProperties.aidl
diff --git a/framework/src/android/net/MacAddress.aidl b/framework/aidl-export/android/net/MacAddress.aidl
similarity index 100%
rename from framework/src/android/net/MacAddress.aidl
rename to framework/aidl-export/android/net/MacAddress.aidl
diff --git a/framework/src/android/net/Network.aidl b/framework/aidl-export/android/net/Network.aidl
similarity index 100%
rename from framework/src/android/net/Network.aidl
rename to framework/aidl-export/android/net/Network.aidl
diff --git a/framework/src/android/net/NetworkAgentConfig.aidl b/framework/aidl-export/android/net/NetworkAgentConfig.aidl
similarity index 100%
rename from framework/src/android/net/NetworkAgentConfig.aidl
rename to framework/aidl-export/android/net/NetworkAgentConfig.aidl
diff --git a/framework/src/android/net/NetworkCapabilities.aidl b/framework/aidl-export/android/net/NetworkCapabilities.aidl
similarity index 100%
rename from framework/src/android/net/NetworkCapabilities.aidl
rename to framework/aidl-export/android/net/NetworkCapabilities.aidl
diff --git a/framework/src/android/net/NetworkInfo.aidl b/framework/aidl-export/android/net/NetworkInfo.aidl
similarity index 100%
rename from framework/src/android/net/NetworkInfo.aidl
rename to framework/aidl-export/android/net/NetworkInfo.aidl
diff --git a/framework/src/android/net/NetworkRequest.aidl b/framework/aidl-export/android/net/NetworkRequest.aidl
similarity index 100%
rename from framework/src/android/net/NetworkRequest.aidl
rename to framework/aidl-export/android/net/NetworkRequest.aidl
diff --git a/framework/src/android/net/ProxyInfo.aidl b/framework/aidl-export/android/net/ProxyInfo.aidl
similarity index 100%
rename from framework/src/android/net/ProxyInfo.aidl
rename to framework/aidl-export/android/net/ProxyInfo.aidl
diff --git a/framework/src/android/net/RouteInfo.aidl b/framework/aidl-export/android/net/RouteInfo.aidl
similarity index 100%
rename from framework/src/android/net/RouteInfo.aidl
rename to framework/aidl-export/android/net/RouteInfo.aidl
diff --git a/framework/src/android/net/StaticIpConfiguration.aidl b/framework/aidl-export/android/net/StaticIpConfiguration.aidl
similarity index 100%
rename from framework/src/android/net/StaticIpConfiguration.aidl
rename to framework/aidl-export/android/net/StaticIpConfiguration.aidl
diff --git a/framework/src/android/net/TestNetworkInterface.aidl b/framework/aidl-export/android/net/TestNetworkInterface.aidl
similarity index 100%
rename from framework/src/android/net/TestNetworkInterface.aidl
rename to framework/aidl-export/android/net/TestNetworkInterface.aidl
diff --git a/framework/src/android/net/apf/ApfCapabilities.aidl b/framework/aidl-export/android/net/apf/ApfCapabilities.aidl
similarity index 100%
rename from framework/src/android/net/apf/ApfCapabilities.aidl
rename to framework/aidl-export/android/net/apf/ApfCapabilities.aidl
diff --git a/framework/src/android/net/CaptivePortalData.java b/framework/src/android/net/CaptivePortalData.java
index 9b56b23..f4b46e9 100644
--- a/framework/src/android/net/CaptivePortalData.java
+++ b/framework/src/android/net/CaptivePortalData.java
@@ -16,12 +16,15 @@
 
 package android.net;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
@@ -40,10 +43,29 @@
     private final long mExpiryTimeMillis;
     private final boolean mCaptive;
     private final String mVenueFriendlyName;
+    private final int mVenueInfoUrlSource;
+    private final int mTermsAndConditionsSource;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CAPTIVE_PORTAL_DATA_SOURCE_"}, value = {
+            CAPTIVE_PORTAL_DATA_SOURCE_OTHER,
+            CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT})
+    public @interface CaptivePortalDataSource {}
+
+    /**
+     * Source of information: Other (default)
+     */
+    public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0;
+
+    /**
+     * Source of information: Wi-Fi Passpoint
+     */
+    public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1;
 
     private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl,
             boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive,
-            String venueFriendlyName) {
+            String venueFriendlyName, int venueInfoUrlSource, int termsAndConditionsSource) {
         mRefreshTimeMillis = refreshTimeMillis;
         mUserPortalUrl = userPortalUrl;
         mVenueInfoUrl = venueInfoUrl;
@@ -52,11 +74,14 @@
         mExpiryTimeMillis = expiryTimeMillis;
         mCaptive = captive;
         mVenueFriendlyName = venueFriendlyName;
+        mVenueInfoUrlSource = venueInfoUrlSource;
+        mTermsAndConditionsSource = termsAndConditionsSource;
     }
 
     private CaptivePortalData(Parcel p) {
         this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(),
-                p.readLong(), p.readLong(), p.readBoolean(), p.readString());
+                p.readLong(), p.readLong(), p.readBoolean(), p.readString(), p.readInt(),
+                p.readInt());
     }
 
     @Override
@@ -74,6 +99,8 @@
         dest.writeLong(mExpiryTimeMillis);
         dest.writeBoolean(mCaptive);
         dest.writeString(mVenueFriendlyName);
+        dest.writeInt(mVenueInfoUrlSource);
+        dest.writeInt(mTermsAndConditionsSource);
     }
 
     /**
@@ -88,6 +115,9 @@
         private long mExpiryTime = -1;
         private boolean mCaptive;
         private String mVenueFriendlyName;
+        private @CaptivePortalDataSource int mVenueInfoUrlSource = CAPTIVE_PORTAL_DATA_SOURCE_OTHER;
+        private @CaptivePortalDataSource int mUserPortalUrlSource =
+                CAPTIVE_PORTAL_DATA_SOURCE_OTHER;
 
         /**
          * Create an empty builder.
@@ -100,8 +130,8 @@
         public Builder(@Nullable CaptivePortalData data) {
             if (data == null) return;
             setRefreshTime(data.mRefreshTimeMillis)
-                    .setUserPortalUrl(data.mUserPortalUrl)
-                    .setVenueInfoUrl(data.mVenueInfoUrl)
+                    .setUserPortalUrl(data.mUserPortalUrl, data.mTermsAndConditionsSource)
+                    .setVenueInfoUrl(data.mVenueInfoUrl, data.mVenueInfoUrlSource)
                     .setSessionExtendable(data.mIsSessionExtendable)
                     .setBytesRemaining(data.mByteLimit)
                     .setExpiryTime(data.mExpiryTimeMillis)
@@ -123,7 +153,18 @@
          */
         @NonNull
         public Builder setUserPortalUrl(@Nullable Uri userPortalUrl) {
+            return setUserPortalUrl(userPortalUrl, CAPTIVE_PORTAL_DATA_SOURCE_OTHER);
+        }
+
+        /**
+         * Set the URL to be used for users to login to the portal, if captive, and the source of
+         * the data, see {@link CaptivePortalDataSource}
+         */
+        @NonNull
+        public Builder setUserPortalUrl(@Nullable Uri userPortalUrl,
+                @CaptivePortalDataSource int source) {
             mUserPortalUrl = userPortalUrl;
+            mUserPortalUrlSource = source;
             return this;
         }
 
@@ -132,7 +173,18 @@
          */
         @NonNull
         public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl) {
+            return setVenueInfoUrl(venueInfoUrl, CAPTIVE_PORTAL_DATA_SOURCE_OTHER);
+        }
+
+        /**
+         * Set the URL that can be used by users to view information about the network venue, and
+         * the source of the data, see {@link CaptivePortalDataSource}
+         */
+        @NonNull
+        public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl,
+                @CaptivePortalDataSource int source) {
             mVenueInfoUrl = venueInfoUrl;
+            mVenueInfoUrlSource = source;
             return this;
         }
 
@@ -188,7 +240,8 @@
         public CaptivePortalData build() {
             return new CaptivePortalData(mRefreshTime, mUserPortalUrl, mVenueInfoUrl,
                     mIsSessionExtendable, mBytesRemaining, mExpiryTime, mCaptive,
-                    mVenueFriendlyName);
+                    mVenueFriendlyName, mVenueInfoUrlSource,
+                    mUserPortalUrlSource);
         }
     }
 
@@ -249,6 +302,22 @@
     }
 
     /**
+     * Get the information source of the Venue URL
+     * @return The source that the Venue URL was obtained from
+     */
+    public @CaptivePortalDataSource int getVenueInfoUrlSource() {
+        return mVenueInfoUrlSource;
+    }
+
+    /**
+     * Get the information source of the user portal URL
+     * @return The source that the user portal URL was obtained from
+     */
+    public @CaptivePortalDataSource int getUserPortalUrlSource() {
+        return mTermsAndConditionsSource;
+    }
+
+    /**
      * Get the venue friendly name
      */
     @Nullable
@@ -272,7 +341,8 @@
     @Override
     public int hashCode() {
         return Objects.hash(mRefreshTimeMillis, mUserPortalUrl, mVenueInfoUrl,
-                mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName);
+                mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName,
+                mVenueInfoUrlSource, mTermsAndConditionsSource);
     }
 
     @Override
@@ -286,7 +356,9 @@
                 && mByteLimit == other.mByteLimit
                 && mExpiryTimeMillis == other.mExpiryTimeMillis
                 && mCaptive == other.mCaptive
-                && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName);
+                && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName)
+                && mVenueInfoUrlSource == other.mVenueInfoUrlSource
+                && mTermsAndConditionsSource == other.mTermsAndConditionsSource;
     }
 
     @Override
@@ -300,6 +372,8 @@
                 + ", expiryTime: " + mExpiryTimeMillis
                 + ", captive: " + mCaptive
                 + ", venueFriendlyName: " + mVenueFriendlyName
+                + ", venueInfoUrlSource: " + mVenueInfoUrlSource
+                + ", termsAndConditionsSource: " + mTermsAndConditionsSource
                 + "}";
     }
 }
diff --git a/framework/src/android/net/ConnectivityFrameworkInitializer.java b/framework/src/android/net/ConnectivityFrameworkInitializer.java
index 9afa5d1..92a792b 100644
--- a/framework/src/android/net/ConnectivityFrameworkInitializer.java
+++ b/framework/src/android/net/ConnectivityFrameworkInitializer.java
@@ -49,17 +49,6 @@
                 }
         );
 
-        // TODO: move outside of the connectivity JAR
-        SystemServiceRegistry.registerContextAwareService(
-                Context.VPN_MANAGEMENT_SERVICE,
-                VpnManager.class,
-                (context) -> {
-                    final ConnectivityManager cm = context.getSystemService(
-                            ConnectivityManager.class);
-                    return cm.createVpnManager();
-                }
-        );
-
         SystemServiceRegistry.registerContextAwareService(
                 Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
                 ConnectivityDiagnosticsManager.class,
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index fef4152..6273f4b 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -21,6 +21,7 @@
 import static android.net.NetworkRequest.Type.LISTEN;
 import static android.net.NetworkRequest.Type.REQUEST;
 import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
+import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
 import static android.net.QosCallback.QosCallbackRegistrationException;
 
 import android.annotation.CallbackExecutor;
@@ -48,8 +49,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.INetworkActivityListener;
-import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
@@ -455,7 +454,7 @@
      * @hide
      */
     @SystemApi
-    public static final int TETHERING_WIFI      = TetheringManager.TETHERING_WIFI;
+    public static final int TETHERING_WIFI      = 0;
 
     /**
      * USB tethering type.
@@ -463,7 +462,7 @@
      * @hide
      */
     @SystemApi
-    public static final int TETHERING_USB       = TetheringManager.TETHERING_USB;
+    public static final int TETHERING_USB       = 1;
 
     /**
      * Bluetooth tethering type.
@@ -471,7 +470,7 @@
      * @hide
      */
     @SystemApi
-    public static final int TETHERING_BLUETOOTH = TetheringManager.TETHERING_BLUETOOTH;
+    public static final int TETHERING_BLUETOOTH = 2;
 
     /**
      * Wifi P2p tethering type.
@@ -823,6 +822,7 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     private final IConnectivityManager mService;
+
     /**
      * A kludge to facilitate static access where a Context pointer isn't available, like in the
      * case of the static set/getProcessDefaultNetwork methods and from the Network class.
@@ -833,7 +833,6 @@
 
     private final Context mContext;
 
-    private INetworkManagementService mNMService;
     private INetworkPolicyManager mNPManager;
     private final TetheringManager mTetheringManager;
 
@@ -1068,109 +1067,6 @@
     }
 
     /**
-     * Checks if a VPN app supports always-on mode.
-     *
-     * In order to support the always-on feature, an app has to
-     * <ul>
-     *     <li>target {@link VERSION_CODES#N API 24} or above, and
-     *     <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
-     *         meta-data field.
-     * </ul>
-     *
-     * @param userId The identifier of the user for whom the VPN app is installed.
-     * @param vpnPackage The canonical package name of the VPN app.
-     * @return {@code true} if and only if the VPN app exists and supports always-on mode.
-     * @hide
-     */
-    public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) {
-        try {
-            return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Configures an always-on VPN connection through a specific application.
-     * This connection is automatically granted and persisted after a reboot.
-     *
-     * <p>The designated package should declare a {@link VpnService} in its
-     *    manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
-     *    otherwise the call will fail.
-     *
-     * @param userId The identifier of the user to set an always-on VPN for.
-     * @param vpnPackage The package name for an installed VPN app on the device, or {@code null}
-     *                   to remove an existing always-on VPN configuration.
-     * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
-     *        {@code false} otherwise.
-     * @param lockdownAllowlist The list of packages that are allowed to access network directly
-     *         when VPN is in lockdown mode but is not running. Non-existent packages are ignored so
-     *         this method must be called when a package that should be allowed is installed or
-     *         uninstalled.
-     * @return {@code true} if the package is set as always-on VPN controller;
-     *         {@code false} otherwise.
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
-    public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage,
-            boolean lockdownEnabled, @Nullable List<String> lockdownAllowlist) {
-        try {
-            return mService.setAlwaysOnVpnPackage(
-                    userId, vpnPackage, lockdownEnabled, lockdownAllowlist);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-   /**
-     * Returns the package name of the currently set always-on VPN application.
-     * If there is no always-on VPN set, or the VPN is provided by the system instead
-     * of by an app, {@code null} will be returned.
-     *
-     * @return Package name of VPN controller responsible for always-on VPN,
-     *         or {@code null} if none is set.
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
-    public String getAlwaysOnVpnPackageForUser(int userId) {
-        try {
-            return mService.getAlwaysOnVpnPackage(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * @return whether always-on VPN is in lockdown mode.
-     *
-     * @hide
-     **/
-    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
-    public boolean isVpnLockdownEnabled(int userId) {
-        try {
-            return mService.isVpnLockdownEnabled(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-
-    }
-
-    /**
-     * @return the list of packages that are allowed to access network when always-on VPN is in
-     * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active.
-     *
-     * @hide
-     **/
-    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
-    public List<String> getVpnLockdownWhitelist(int userId) {
-        try {
-            return mService.getVpnLockdownWhitelist(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Adds or removes a requirement for given UID ranges to use the VPN.
      *
      * If set to {@code true}, informs the system that the UIDs in the specified ranges must not
@@ -1220,6 +1116,45 @@
     }
 
     /**
+     * Informs ConnectivityService of whether the legacy lockdown VPN, as implemented by
+     * LockdownVpnTracker, is in use. This is deprecated for new devices starting from Android 12
+     * but is still supported for backwards compatibility.
+     * <p>
+     * This type of VPN is assumed always to use the system default network, and must always declare
+     * exactly one underlying network, which is the network that was the default when the VPN
+     * connected.
+     * <p>
+     * Calling this method with {@code true} enables legacy behaviour, specifically:
+     * <ul>
+     *     <li>Any VPN that applies to userId 0 behaves specially with respect to deprecated
+     *     {@link #CONNECTIVITY_ACTION} broadcasts. Any such broadcasts will have the state in the
+     *     {@link #EXTRA_NETWORK_INFO} replaced by state of the VPN network. Also, any time the VPN
+     *     connects, a {@link #CONNECTIVITY_ACTION} broadcast will be sent for the network
+     *     underlying the VPN.</li>
+     *     <li>Deprecated APIs that return {@link NetworkInfo} objects will have their state
+     *     similarly replaced by the VPN network state.</li>
+     *     <li>Information on current network interfaces passed to NetworkStatsService will not
+     *     include any VPN interfaces.</li>
+     * </ul>
+     *
+     * @param enabled whether legacy lockdown VPN is enabled or disabled
+     *
+     * TODO: @SystemApi(client = MODULE_LIBRARIES)
+     *
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    public void setLegacyLockdownVpnEnabled(boolean enabled) {
+        try {
+            mService.setLegacyLockdownVpnEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns details about the currently active default data network
      * for a given uid.  This is for internal use only to avoid spying
      * other apps.
@@ -1368,7 +1303,7 @@
     public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) {
         try {
             return mService.getDefaultNetworkCapabilitiesForUser(
-                    userId, mContext.getOpPackageName());
+                    userId, mContext.getOpPackageName(), getAttributionTag());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1450,7 +1385,8 @@
     @Nullable
     public NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
         try {
-            return mService.getNetworkCapabilities(network, mContext.getOpPackageName());
+            return mService.getNetworkCapabilities(
+                    network, mContext.getOpPackageName(), getAttributionTag());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2142,7 +2078,7 @@
      */
     // TODO: Remove method and replace with direct call once R code is pushed to AOSP
     private @Nullable String getAttributionTag() {
-        return null;
+        return mContext.getAttributionTag();
     }
 
     /**
@@ -2220,17 +2156,6 @@
         void onNetworkActive();
     }
 
-    private INetworkManagementService getNetworkManagementService() {
-        synchronized (this) {
-            if (mNMService != null) {
-                return mNMService;
-            }
-            IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
-            mNMService = INetworkManagementService.Stub.asInterface(b);
-            return mNMService;
-        }
-    }
-
     private final ArrayMap<OnNetworkActiveListener, INetworkActivityListener>
             mNetworkActivityListeners = new ArrayMap<>();
 
@@ -2255,7 +2180,7 @@
         };
 
         try {
-            getNetworkManagementService().registerNetworkActivityListener(rl);
+            mService.registerNetworkActivityListener(rl);
             mNetworkActivityListeners.put(l, rl);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -2272,7 +2197,7 @@
         INetworkActivityListener rl = mNetworkActivityListeners.get(l);
         Preconditions.checkArgument(rl != null, "Listener was not registered.");
         try {
-            getNetworkManagementService().unregisterNetworkActivityListener(rl);
+            mService.registerNetworkActivityListener(rl);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2288,7 +2213,7 @@
      */
     public boolean isDefaultNetworkActive() {
         try {
-            return getNetworkManagementService().isNetworkActive();
+            return mService.isDefaultNetworkActive();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2808,7 +2733,7 @@
      */
     @SystemApi
     @Deprecated
-    public static final int TETHER_ERROR_NO_ERROR = TetheringManager.TETHER_ERROR_NO_ERROR;
+    public static final int TETHER_ERROR_NO_ERROR = 0;
     /**
      * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNKNOWN_IFACE}.
      * {@hide}
@@ -2884,8 +2809,7 @@
      */
     @SystemApi
     @Deprecated
-    public static final int TETHER_ERROR_PROVISION_FAILED =
-            TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
+    public static final int TETHER_ERROR_PROVISION_FAILED = 11;
     /**
      * @deprecated Use {@link TetheringManager#TETHER_ERROR_DHCPSERVER_ERROR}.
      * {@hide}
@@ -2899,8 +2823,7 @@
      */
     @SystemApi
     @Deprecated
-    public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN =
-            TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
+    public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13;
 
     /**
      * Get a more detailed error code after a Tethering or Untethering
@@ -3178,23 +3101,6 @@
     }
 
     /**
-     * If the LockdownVpn mechanism is enabled, updates the vpn
-     * with a reload of its profile.
-     *
-     * @return a boolean with {@code} indicating success
-     *
-     * <p>This method can only be called by the system UID
-     * {@hide}
-     */
-    public boolean updateLockdownVpn() {
-        try {
-            return mService.updateLockdownVpn();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Set sign in error notification to visible or invisible
      *
      * @hide
@@ -3720,7 +3626,8 @@
         printStackTrace();
         checkCallbackNotNull(callback);
         Preconditions.checkArgument(
-                reqType == TRACK_DEFAULT || need != null, "null NetworkCapabilities");
+                reqType == TRACK_DEFAULT || reqType == TRACK_SYSTEM_DEFAULT || need != null,
+                "null NetworkCapabilities");
         final NetworkRequest request;
         final String callingPackageName = mContext.getOpPackageName();
         try {
@@ -3735,7 +3642,8 @@
                 Binder binder = new Binder();
                 if (reqType == LISTEN) {
                     request = mService.listenForNetwork(
-                            need, messenger, binder, callingPackageName);
+                            need, messenger, binder, callingPackageName,
+                            getAttributionTag());
                 } else {
                     request = mService.requestNetwork(
                             need, reqType.ordinal(), messenger, timeoutMs, binder, legacyType,
@@ -4180,7 +4088,8 @@
         checkPendingIntentNotNull(operation);
         try {
             mService.pendingListenForNetwork(
-                    request.networkCapabilities, operation, mContext.getOpPackageName());
+                    request.networkCapabilities, operation, mContext.getOpPackageName(),
+                    getAttributionTag());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (ServiceSpecificException e) {
@@ -4189,8 +4098,9 @@
     }
 
     /**
-     * Registers to receive notifications about changes in the system default network. The callbacks
-     * will continue to be called until either the application exits or
+     * Registers to receive notifications about changes in the application's default network. This
+     * may be a physical network or a virtual network, such as a VPN that applies to the
+     * application. The callbacks will continue to be called until either the application exits or
      * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
      *
      * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the
@@ -4203,7 +4113,7 @@
      * {@link #unregisterNetworkCallback(NetworkCallback)}.
      *
      * @param networkCallback The {@link NetworkCallback} that the system will call as the
-     *                        system default network changes.
+     *                        application's default network changes.
      *                        The callback is invoked on the default internal Handler.
      * @throws RuntimeException if the app already has too many callbacks registered.
      */
@@ -4213,10 +4123,46 @@
     }
 
     /**
+     * Registers to receive notifications about changes in the application's default network. This
+     * may be a physical network or a virtual network, such as a VPN that applies to the
+     * application. The callbacks will continue to be called until either the application exits or
+     * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
+     *
+     * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the
+     * number of outstanding requests to 100 per app (identified by their UID), shared with
+     * all variants of this method, of {@link #requestNetwork} as well as
+     * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}.
+     * Requesting a network with this method will count toward this limit. If this limit is
+     * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources,
+     * make sure to unregister the callbacks with
+     * {@link #unregisterNetworkCallback(NetworkCallback)}.
+     *
+     * @param networkCallback The {@link NetworkCallback} that the system will call as the
+     *                        application's default network changes.
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     * @throws RuntimeException if the app already has too many callbacks registered.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+    public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
+            @NonNull Handler handler) {
+        CallbackHandler cbHandler = new CallbackHandler(handler);
+        sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0,
+                TRACK_DEFAULT, TYPE_NONE, cbHandler);
+    }
+
+    /**
      * Registers to receive notifications about changes in the system default network. The callbacks
      * will continue to be called until either the application exits or
      * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
      *
+     * This method should not be used to determine networking state seen by applications, because in
+     * many cases, most or even all application traffic may not use the default network directly,
+     * and traffic from different applications may go on different networks by default. As an
+     * example, if a VPN is connected, traffic from all applications might be sent through the VPN
+     * and not onto the system default network. Applications or system components desiring to do
+     * determine network state as seen by applications should use other methods such as
+     * {@link #registerDefaultNetworkCallback(NetworkCallback, Handler)}.
+     *
      * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the
      * number of outstanding requests to 100 per app (identified by their UID), shared with
      * all variants of this method, of {@link #requestNetwork} as well as
@@ -4230,20 +4176,19 @@
      *                        system default network changes.
      * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
      * @throws RuntimeException if the app already has too many callbacks registered.
+     *
+     * @hide
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
-    public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
+    @SystemApi(client = MODULE_LIBRARIES)
+    @SuppressLint({"ExecutorRegistration", "PairedRegistration"})
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    public void registerSystemDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
             @NonNull Handler handler) {
-        // This works because if the NetworkCapabilities are null,
-        // ConnectivityService takes them from the default request.
-        //
-        // Since the capabilities are exactly the same as the default request's
-        // capabilities, this request is guaranteed, at all times, to be
-        // satisfied by the same network, if any, that satisfies the default
-        // request, i.e., the system default network.
         CallbackHandler cbHandler = new CallbackHandler(handler);
         sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0,
-                TRACK_DEFAULT, TYPE_NONE, cbHandler);
+                TRACK_SYSTEM_DEFAULT, TYPE_NONE, cbHandler);
     }
 
     /**
@@ -4583,7 +4528,7 @@
             // Set HTTP proxy system properties to match network.
             // TODO: Deprecate this static method and replace it with a non-static version.
             try {
-                Proxy.setHttpProxySystemProperty(getInstance().getDefaultProxy());
+                Proxy.setHttpProxyConfiguration(getInstance().getDefaultProxy());
             } catch (SecurityException e) {
                 // The process doesn't have ACCESS_NETWORK_STATE, so we can't fetch the proxy.
                 Log.e(TAG, "Can't set proxy properties", e);
@@ -4810,11 +4755,6 @@
     }
 
     /** @hide */
-    public VpnManager createVpnManager() {
-        return new VpnManager(mContext, mService);
-    }
-
-    /** @hide */
     public ConnectivityDiagnosticsManager createDiagnosticsManager() {
         return new ConnectivityDiagnosticsManager(mContext, mService);
     }
@@ -4845,15 +4785,6 @@
         }
     }
 
-    private void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) {
-        try {
-            mService.setOemNetworkPreference(preference);
-        } catch (RemoteException e) {
-            Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString());
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
     @NonNull
     private final List<QosCallbackConnection> mQosCallbackConnections = new ArrayList<>();
 
@@ -5055,4 +4986,60 @@
         sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST,
                 TYPE_NONE, handler == null ? getDefaultHandler() : new CallbackHandler(handler));
     }
+
+    /**
+     * Listener for {@link #setOemNetworkPreference(OemNetworkPreferences, Executor,
+     * OnSetOemNetworkPreferenceListener)}.
+     * @hide
+     */
+    @SystemApi
+    public interface OnSetOemNetworkPreferenceListener {
+        /**
+         * Called when setOemNetworkPreference() successfully completes.
+         */
+        void onComplete();
+    }
+
+    /**
+     * Used by automotive devices to set the network preferences used to direct traffic at an
+     * application level as per the given OemNetworkPreferences. An example use-case would be an
+     * automotive OEM wanting to provide connectivity for applications critical to the usage of a
+     * vehicle via a particular network.
+     *
+     * Calling this will overwrite the existing preference.
+     *
+     * @param preference {@link OemNetworkPreferences} The application network preference to be set.
+     * @param executor the executor on which listener will be invoked.
+     * @param listener {@link OnSetOemNetworkPreferenceListener} optional listener used to
+     *                  communicate completion of setOemNetworkPreference(). This will only be
+     *                  called once upon successful completion of setOemNetworkPreference().
+     * @throws IllegalArgumentException if {@code preference} contains invalid preference values.
+     * @throws SecurityException if missing the appropriate permissions.
+     * @throws UnsupportedOperationException if called on a non-automotive device.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE)
+    public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference,
+            @Nullable @CallbackExecutor final Executor executor,
+            @Nullable final OnSetOemNetworkPreferenceListener listener) {
+        Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
+        if (null != listener) {
+            Objects.requireNonNull(executor, "Executor must be non-null");
+        }
+        final IOnSetOemNetworkPreferenceListener listenerInternal = listener == null ? null :
+                new IOnSetOemNetworkPreferenceListener.Stub() {
+                    @Override
+                    public void onComplete() {
+                        executor.execute(listener::onComplete);
+                    }
+        };
+
+        try {
+            mService.setOemNetworkPreference(preference, listenerInternal);
+        } catch (RemoteException e) {
+            Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString());
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/framework/src/android/net/ConnectivityMetricsEvent.aidl b/framework/src/android/net/ConnectivityMetricsEvent.aidl
deleted file mode 100644
index 1c541dc..0000000
--- a/framework/src/android/net/ConnectivityMetricsEvent.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-/** {@hide} */
-parcelable ConnectivityMetricsEvent;
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index e2672c4..160338d 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -20,6 +20,8 @@
 import android.net.ConnectionInfo;
 import android.net.ConnectivityDiagnosticsManager;
 import android.net.IConnectivityDiagnosticsCallback;
+import android.net.IOnSetOemNetworkPreferenceListener;
+import android.net.INetworkActivityListener;
 import android.net.IQosCallback;
 import android.net.ISocketKeepaliveCallback;
 import android.net.LinkProperties;
@@ -35,16 +37,12 @@
 import android.net.QosSocketInfo;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.os.INetworkActivityListener;
 import android.os.Messenger;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.ResultReceiver;
 
 import com.android.connectivity.aidl.INetworkAgent;
-import com.android.internal.net.LegacyVpnInfo;
-import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnProfile;
 
 /**
  * Interface that answers queries about, and allows changing, the
@@ -66,7 +64,7 @@
     Network getNetworkForType(int networkType);
     Network[] getAllNetworks();
     NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(
-            int userId, String callingPackageName);
+            int userId, String callingPackageName, String callingAttributionTag);
 
     boolean isNetworkSupported(int networkType);
 
@@ -75,7 +73,8 @@
     LinkProperties getLinkPropertiesForType(int networkType);
     LinkProperties getLinkProperties(in Network network);
 
-    NetworkCapabilities getNetworkCapabilities(in Network network, String callingPackageName);
+    NetworkCapabilities getNetworkCapabilities(in Network network, String callingPackageName,
+            String callingAttributionTag);
 
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     NetworkState[] getAllNetworkState();
@@ -121,35 +120,8 @@
 
     ProxyInfo getProxyForNetwork(in Network nework);
 
-    boolean prepareVpn(String oldPackage, String newPackage, int userId);
-
-    void setVpnPackageAuthorization(String packageName, int userId, int vpnType);
-
-    ParcelFileDescriptor establishVpn(in VpnConfig config);
-
-    boolean provisionVpnProfile(in VpnProfile profile, String packageName);
-
-    void deleteVpnProfile(String packageName);
-
-    void startVpnProfile(String packageName);
-
-    void stopVpnProfile(String packageName);
-
-    VpnConfig getVpnConfig(int userId);
-
-    @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
-    void startLegacyVpn(in VpnProfile profile);
-
-    LegacyVpnInfo getLegacyVpnInfo(int userId);
-
-    boolean updateLockdownVpn();
-    boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
-    boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown,
-            in List<String> lockdownWhitelist);
-    String getAlwaysOnVpnPackage(int userId);
-    boolean isVpnLockdownEnabled(int userId);
-    List<String> getVpnLockdownWhitelist(int userId);
     void setRequireVpnForUids(boolean requireVpn, in UidRange[] ranges);
+    void setLegacyLockdownVpnEnabled(boolean enabled);
 
     void setProvisioningNotificationVisible(boolean visible, int networkType, in String action);
 
@@ -176,10 +148,12 @@
     void releasePendingNetworkRequest(in PendingIntent operation);
 
     NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities,
-            in Messenger messenger, in IBinder binder, String callingPackageName);
+            in Messenger messenger, in IBinder binder, String callingPackageName,
+            String callingAttributionTag);
 
     void pendingListenForNetwork(in NetworkCapabilities networkCapabilities,
-            in PendingIntent operation, String callingPackageName);
+            in PendingIntent operation, String callingPackageName,
+            String callingAttributionTag);
 
     void releaseNetworkRequest(in NetworkRequest networkRequest);
 
@@ -196,10 +170,6 @@
 
     int getRestoreDefaultNetworkDelay(int networkType);
 
-    boolean addVpnAddress(String address, int prefixLength);
-    boolean removeVpnAddress(String address, int prefixLength);
-    boolean setUnderlyingNetworksForVpn(in Network[] networks);
-
     void factoryReset();
 
     void startNattKeepalive(in Network network, int intervalSeconds,
@@ -219,8 +189,6 @@
     byte[] getNetworkWatchlistConfigHash();
 
     int getConnectionOwnerUid(in ConnectionInfo connectionInfo);
-    boolean isCallerCurrentAlwaysOnVpnApp();
-    boolean isCallerCurrentAlwaysOnVpnLockdownApp();
 
     void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback,
             in NetworkRequest request, String callingPackageName);
@@ -242,5 +210,6 @@
     void registerQosSocketCallback(in QosSocketInfo socketInfo, in IQosCallback callback);
     void unregisterQosCallback(in IQosCallback callback);
 
-    void setOemNetworkPreference(in OemNetworkPreferences preference);
+    void setOemNetworkPreference(in OemNetworkPreferences preference,
+            in IOnSetOemNetworkPreferenceListener listener);
 }
diff --git a/framework/src/android/net/INetworkActivityListener.aidl b/framework/src/android/net/INetworkActivityListener.aidl
new file mode 100644
index 0000000..79687dd
--- /dev/null
+++ b/framework/src/android/net/INetworkActivityListener.aidl
@@ -0,0 +1,24 @@
+/* Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.net;
+
+/**
+ * @hide
+ */
+oneway interface INetworkActivityListener
+{
+    void onNetworkActive();
+}
diff --git a/framework/src/android/net/InterfaceConfiguration.aidl b/framework/src/android/net/InterfaceConfiguration.aidl
deleted file mode 100644
index 8aa5e34..0000000
--- a/framework/src/android/net/InterfaceConfiguration.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (c) 2008, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); 
- * you may not use this file except in compliance with the License. 
- * You may obtain a copy of the License at 
- *
- *     http://www.apache.org/licenses/LICENSE-2.0 
- *
- * Unless required by applicable law or agreed to in writing, software 
- * distributed under the License is distributed on an "AS IS" BASIS, 
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
- * See the License for the specific language governing permissions and 
- * limitations under the License.
- */
-
-package android.net;
-
-parcelable InterfaceConfiguration;
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 55b2c3c..26d14cb 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -762,12 +762,14 @@
         final int originalSignalStrength = mSignalStrength;
         final int originalOwnerUid = getOwnerUid();
         final int[] originalAdministratorUids = getAdministratorUids();
+        final TransportInfo originalTransportInfo = getTransportInfo();
         clearAll();
         mTransportTypes = (originalTransportTypes & TEST_NETWORKS_ALLOWED_TRANSPORTS)
                 | (1 << TRANSPORT_TEST);
         mNetworkCapabilities = originalCapabilities & TEST_NETWORKS_ALLOWED_CAPABILITIES;
         mNetworkSpecifier = originalSpecifier;
         mSignalStrength = originalSignalStrength;
+        mTransportInfo = originalTransportInfo;
 
         // Only retain the owner and administrator UIDs if they match the app registering the remote
         // caller that registered the network.
@@ -2083,9 +2085,10 @@
     /**
      * Check if private dns is broken.
      *
-     * @return {@code true} if {@code mPrivateDnsBroken} is set when private DNS is broken.
+     * @return {@code true} if private DNS is broken on this network.
      * @hide
      */
+    @SystemApi
     public boolean isPrivateDnsBroken() {
         return mPrivateDnsBroken;
     }
@@ -2328,6 +2331,17 @@
         }
 
         /**
+         * Completely clears the contents of this object, removing even the capabilities that are
+         * set by default when the object is constructed.
+         * @return this builder
+         */
+        @NonNull
+        public Builder clearAll() {
+            mCaps.clearAll();
+            return this;
+        }
+
+        /**
          * Sets the owner UID.
          *
          * The default value is {@link Process#INVALID_UID}. Pass this value to reset.
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index b9ef4c2..4e3085f 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -86,17 +86,14 @@
      *       callbacks about the single, highest scoring current network
      *       (if any) that matches the specified NetworkCapabilities, or
      *
-     *     - TRACK_DEFAULT, a hybrid of the two designed such that the
-     *       framework will issue callbacks for the single, highest scoring
-     *       current network (if any) that matches the capabilities of the
-     *       default Internet request (mDefaultRequest), but which cannot cause
-     *       the framework to either create or retain the existence of any
-     *       specific network. Note that from the point of view of the request
-     *       matching code, TRACK_DEFAULT is identical to REQUEST: its special
-     *       behaviour is not due to different semantics, but to the fact that
-     *       the system will only ever create a TRACK_DEFAULT with capabilities
-     *       that are identical to the default request's capabilities, thus
-     *       causing it to share fate in every way with the default request.
+     *     - TRACK_DEFAULT, which causes the framework to issue callbacks for
+     *       the single, highest scoring current network (if any) that will
+     *       be chosen for an app, but which cannot cause the framework to
+     *       either create or retain the existence of any specific network.
+     *
+     *     - TRACK_SYSTEM_DEFAULT, which causes the framework to send callbacks
+     *       for the network (if any) that satisfies the default Internet
+     *       request.
      *
      *     - BACKGROUND_REQUEST, like REQUEST but does not cause any networks
      *       to retain the NET_CAPABILITY_FOREGROUND capability. A network with
@@ -119,6 +116,7 @@
         TRACK_DEFAULT,
         REQUEST,
         BACKGROUND_REQUEST,
+        TRACK_SYSTEM_DEFAULT,
     };
 
     /**
@@ -532,6 +530,8 @@
                 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;
         }
diff --git a/framework/src/android/net/Proxy.java b/framework/src/android/net/Proxy.java
index 03b07e0..77c8a4f 100644
--- a/framework/src/android/net/Proxy.java
+++ b/framework/src/android/net/Proxy.java
@@ -16,8 +16,10 @@
 
 package android.net;
 
+import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Build;
@@ -30,8 +32,6 @@
 import java.net.ProxySelector;
 import java.net.URI;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * A convenience class for accessing the user and default proxy
@@ -64,40 +64,9 @@
     @Deprecated
     public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO";
 
-    /** @hide */
-    public static final int PROXY_VALID             = 0;
-    /** @hide */
-    public static final int PROXY_HOSTNAME_EMPTY    = 1;
-    /** @hide */
-    public static final int PROXY_HOSTNAME_INVALID  = 2;
-    /** @hide */
-    public static final int PROXY_PORT_EMPTY        = 3;
-    /** @hide */
-    public static final int PROXY_PORT_INVALID      = 4;
-    /** @hide */
-    public static final int PROXY_EXCLLIST_INVALID  = 5;
-
     private static ConnectivityManager sConnectivityManager = null;
 
-    // Hostname / IP REGEX validation
-    // Matches blank input, ips, and domain names
-    private static final String NAME_IP_REGEX =
-        "[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*";
-
-    private static final String HOSTNAME_REGEXP = "^$|^" + NAME_IP_REGEX + "$";
-
-    private static final Pattern HOSTNAME_PATTERN;
-
-    private static final String EXCL_REGEX =
-        "[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*(\\.[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*)*";
-
-    private static final String EXCLLIST_REGEXP = "^$|^" + EXCL_REGEX + "(," + EXCL_REGEX + ")*$";
-
-    private static final Pattern EXCLLIST_PATTERN;
-
     static {
-        HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP);
-        EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP);
         sDefaultProxySelector = ProxySelector.getDefault();
     }
 
@@ -216,36 +185,21 @@
         return false;
     }
 
-    /**
-     * Validate syntax of hostname, port and exclusion list entries
-     * {@hide}
-     */
-    public static int validate(String hostname, String port, String exclList) {
-        Matcher match = HOSTNAME_PATTERN.matcher(hostname);
-        Matcher listMatch = EXCLLIST_PATTERN.matcher(exclList);
-
-        if (!match.matches()) return PROXY_HOSTNAME_INVALID;
-
-        if (!listMatch.matches()) return PROXY_EXCLLIST_INVALID;
-
-        if (hostname.length() > 0 && port.length() == 0) return PROXY_PORT_EMPTY;
-
-        if (port.length() > 0) {
-            if (hostname.length() == 0) return PROXY_HOSTNAME_EMPTY;
-            int portVal = -1;
-            try {
-                portVal = Integer.parseInt(port);
-            } catch (NumberFormatException ex) {
-                return PROXY_PORT_INVALID;
-            }
-            if (portVal <= 0 || portVal > 0xFFFF) return PROXY_PORT_INVALID;
-        }
-        return PROXY_VALID;
-    }
-
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final void setHttpProxySystemProperty(ProxyInfo p) {
+    @Deprecated
+    public static void setHttpProxySystemProperty(ProxyInfo p) {
+        setHttpProxyConfiguration(p);
+    }
+
+    /**
+     * Set HTTP proxy configuration for the process to match the provided ProxyInfo.
+     *
+     * If the provided ProxyInfo is null, the proxy configuration will be cleared.
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static void setHttpProxyConfiguration(@Nullable ProxyInfo p) {
         String host = null;
         String port = null;
         String exclList = null;
@@ -256,11 +210,11 @@
             exclList = ProxyUtils.exclusionListAsString(p.getExclusionList());
             pacFileUrl = p.getPacFileUrl();
         }
-        setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
+        setHttpProxyConfiguration(host, port, exclList, pacFileUrl);
     }
 
     /** @hide */
-    public static final void setHttpProxySystemProperty(String host, String port, String exclList,
+    public static void setHttpProxyConfiguration(String host, String port, String exclList,
             Uri pacFileUrl) {
         if (exclList != null) exclList = exclList.replace(",", "|");
         if (false) Log.d(TAG, "setHttpProxySystemProperty :"+host+":"+port+" - "+exclList);
diff --git a/framework/src/android/net/ProxyInfo.java b/framework/src/android/net/ProxyInfo.java
index 9c9fed1..229db0d 100644
--- a/framework/src/android/net/ProxyInfo.java
+++ b/framework/src/android/net/ProxyInfo.java
@@ -23,6 +23,8 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.net.module.util.ProxyUtils;
+
 import java.net.InetSocketAddress;
 import java.net.URLConnection;
 import java.util.List;
@@ -233,7 +235,7 @@
      */
     public boolean isValid() {
         if (!Uri.EMPTY.equals(mPacFileUrl)) return true;
-        return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost,
+        return ProxyUtils.PROXY_VALID == ProxyUtils.validate(mHost == null ? "" : mHost,
                 mPort == 0 ? "" : Integer.toString(mPort),
                 mExclusionList == null ? "" : mExclusionList);
     }
diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java
index 4e89414..a174a7b 100644
--- a/framework/src/android/net/TestNetworkManager.java
+++ b/framework/src/android/net/TestNetworkManager.java
@@ -41,7 +41,6 @@
 
     /**
      * Prefix for tap interfaces created by this class.
-     * @hide
      */
     public static final String TEST_TAP_PREFIX = "testtap";
 
diff --git a/framework/src/android/net/UidRange.aidl b/framework/src/android/net/UidRange.aidl
deleted file mode 100644
index f70fc8e..0000000
--- a/framework/src/android/net/UidRange.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-/**
- * An inclusive range of UIDs.
- *
- * {@hide}
- */
-parcelable UidRange;
\ No newline at end of file
diff --git a/framework/src/android/net/VpnManager.java b/framework/src/android/net/VpnManager.java
deleted file mode 100644
index c87b827..0000000
--- a/framework/src/android/net/VpnManager.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import static com.android.internal.util.Preconditions.checkNotNull;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.os.RemoteException;
-
-import com.android.internal.net.VpnProfile;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.security.GeneralSecurityException;
-
-/**
- * This class provides an interface for apps to manage platform VPN profiles
- *
- * <p>Apps can use this API to provide profiles with which the platform can set up a VPN without
- * further app intermediation. When a VPN profile is present and the app is selected as an always-on
- * VPN, the platform will directly trigger the negotiation of the VPN without starting or waking the
- * app (unlike VpnService).
- *
- * <p>VPN apps using supported protocols should preferentially use this API over the {@link
- * VpnService} API for ease-of-development and reduced maintainance burden. This also give the user
- * the guarantee that VPN network traffic is not subjected to on-device packet interception.
- *
- * @see Ikev2VpnProfile
- */
-public class VpnManager {
-    /** Type representing a lack of VPN @hide */
-    public static final int TYPE_VPN_NONE = -1;
-    /** VPN service type code @hide */
-    public static final int TYPE_VPN_SERVICE = 1;
-    /** Platform VPN type code @hide */
-    public static final int TYPE_VPN_PLATFORM = 2;
-
-    /** @hide */
-    @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface VpnType {}
-
-    @NonNull private final Context mContext;
-    @NonNull private final IConnectivityManager mService;
-
-    private static Intent getIntentForConfirmation() {
-        final Intent intent = new Intent();
-        final ComponentName componentName = ComponentName.unflattenFromString(
-                Resources.getSystem().getString(
-                        com.android.internal.R.string.config_platformVpnConfirmDialogComponent));
-        intent.setComponent(componentName);
-        return intent;
-    }
-
-    /**
-     * Create an instance of the VpnManager with the given context.
-     *
-     * <p>Internal only. Applications are expected to obtain an instance of the VpnManager via the
-     * {@link Context.getSystemService()} method call.
-     *
-     * @hide
-     */
-    public VpnManager(@NonNull Context ctx, @NonNull IConnectivityManager service) {
-        mContext = checkNotNull(ctx, "missing Context");
-        mService = checkNotNull(service, "missing IConnectivityManager");
-    }
-
-    /**
-     * Install a VpnProfile configuration keyed on the calling app's package name.
-     *
-     * <p>This method returns {@code null} if user consent has already been granted, or an {@link
-     * Intent} to a system activity. If an intent is returned, the application should launch the
-     * activity using {@link Activity#startActivityForResult} to request user consent. The activity
-     * may pop up a dialog to require user action, and the result will come back via its {@link
-     * Activity#onActivityResult}. If the result is {@link Activity#RESULT_OK}, the user has
-     * consented, and the VPN profile can be started.
-     *
-     * @param profile the VpnProfile provided by this package. Will override any previous VpnProfile
-     *     stored for this package.
-     * @return an Intent requesting user consent to start the VPN, or null if consent is not
-     *     required based on privileges or previous user consent.
-     */
-    @Nullable
-    public Intent provisionVpnProfile(@NonNull PlatformVpnProfile profile) {
-        final VpnProfile internalProfile;
-
-        try {
-            internalProfile = profile.toVpnProfile();
-        } catch (GeneralSecurityException | IOException e) {
-            // Conversion to VpnProfile failed; this is an invalid profile. Both of these exceptions
-            // indicate a failure to convert a PrivateKey or X509Certificate to a Base64 encoded
-            // string as required by the VpnProfile.
-            throw new IllegalArgumentException("Failed to serialize PlatformVpnProfile", e);
-        }
-
-        try {
-            // Profile can never be null; it either gets set, or an exception is thrown.
-            if (mService.provisionVpnProfile(internalProfile, mContext.getOpPackageName())) {
-                return null;
-            }
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-        return getIntentForConfirmation();
-    }
-
-    /**
-     * Delete the VPN profile configuration that was provisioned by the calling app
-     *
-     * @throws SecurityException if this would violate user settings
-     */
-    public void deleteProvisionedVpnProfile() {
-        try {
-            mService.deleteVpnProfile(mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Request the startup of a previously provisioned VPN.
-     *
-     * @throws SecurityException exception if user or device settings prevent this VPN from being
-     *     setup, or if user consent has not been granted
-     */
-    public void startProvisionedVpnProfile() {
-        try {
-            mService.startVpnProfile(mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /** Tear down the VPN provided by the calling app (if any) */
-    public void stopProvisionedVpnProfile() {
-        try {
-            mService.stopVpnProfile(mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-}
diff --git a/framework/src/android/net/VpnService.java b/framework/src/android/net/VpnService.java
deleted file mode 100644
index 8e90a11..0000000
--- a/framework/src/android/net/VpnService.java
+++ /dev/null
@@ -1,903 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.AF_INET6;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.app.admin.DevicePolicyManager;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-
-import com.android.internal.net.VpnConfig;
-
-import java.net.DatagramSocket;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-/**
- * VpnService is a base class for applications to extend and build their
- * own VPN solutions. In general, it creates a virtual network interface,
- * configures addresses and routing rules, and returns a file descriptor
- * to the application. Each read from the descriptor retrieves an outgoing
- * packet which was routed to the interface. Each write to the descriptor
- * injects an incoming packet just like it was received from the interface.
- * The interface is running on Internet Protocol (IP), so packets are
- * always started with IP headers. The application then completes a VPN
- * connection by processing and exchanging packets with the remote server
- * over a tunnel.
- *
- * <p>Letting applications intercept packets raises huge security concerns.
- * A VPN application can easily break the network. Besides, two of them may
- * conflict with each other. The system takes several actions to address
- * these issues. Here are some key points:
- * <ul>
- *   <li>User action is required the first time an application creates a VPN
- *       connection.</li>
- *   <li>There can be only one VPN connection running at the same time. The
- *       existing interface is deactivated when a new one is created.</li>
- *   <li>A system-managed notification is shown during the lifetime of a
- *       VPN connection.</li>
- *   <li>A system-managed dialog gives the information of the current VPN
- *       connection. It also provides a button to disconnect.</li>
- *   <li>The network is restored automatically when the file descriptor is
- *       closed. It also covers the cases when a VPN application is crashed
- *       or killed by the system.</li>
- * </ul>
- *
- * <p>There are two primary methods in this class: {@link #prepare} and
- * {@link Builder#establish}. The former deals with user action and stops
- * the VPN connection created by another application. The latter creates
- * a VPN interface using the parameters supplied to the {@link Builder}.
- * An application must call {@link #prepare} to grant the right to use
- * other methods in this class, and the right can be revoked at any time.
- * Here are the general steps to create a VPN connection:
- * <ol>
- *   <li>When the user presses the button to connect, call {@link #prepare}
- *       and launch the returned intent, if non-null.</li>
- *   <li>When the application becomes prepared, start the service.</li>
- *   <li>Create a tunnel to the remote server and negotiate the network
- *       parameters for the VPN connection.</li>
- *   <li>Supply those parameters to a {@link Builder} and create a VPN
- *       interface by calling {@link Builder#establish}.</li>
- *   <li>Process and exchange packets between the tunnel and the returned
- *       file descriptor.</li>
- *   <li>When {@link #onRevoke} is invoked, close the file descriptor and
- *       shut down the tunnel gracefully.</li>
- * </ol>
- *
- * <p>Services extending this class need to be declared with an appropriate
- * permission and intent filter. Their access must be secured by
- * {@link android.Manifest.permission#BIND_VPN_SERVICE} permission, and
- * their intent filter must match {@link #SERVICE_INTERFACE} action. Here
- * is an example of declaring a VPN service in {@code AndroidManifest.xml}:
- * <pre>
- * &lt;service android:name=".ExampleVpnService"
- *         android:permission="android.permission.BIND_VPN_SERVICE"&gt;
- *     &lt;intent-filter&gt;
- *         &lt;action android:name="android.net.VpnService"/&gt;
- *     &lt;/intent-filter&gt;
- * &lt;/service&gt;</pre>
- *
- * <p> The Android system starts a VPN in the background by calling
- * {@link android.content.Context#startService startService()}. In Android 8.0
- * (API level 26) and higher, the system places VPN apps on the temporary
- * allowlist for a short period so the app can start in the background. The VPN
- * app must promote itself to the foreground after it's launched or the system
- * will shut down the app.
- *
- * <h3>Developer's guide</h3>
- *
- * <p>To learn more about developing VPN apps, read the
- * <a href="{@docRoot}guide/topics/connectivity/vpn">VPN developer's guide</a>.
- *
- * @see Builder
- */
-public class VpnService extends Service {
-
-    /**
-     * The action must be matched by the intent filter of this service. It also
-     * needs to require {@link android.Manifest.permission#BIND_VPN_SERVICE}
-     * permission so that other applications cannot abuse it.
-     */
-    public static final String SERVICE_INTERFACE = VpnConfig.SERVICE_INTERFACE;
-
-    /**
-     * Key for boolean meta-data field indicating whether this VpnService supports always-on mode.
-     *
-     * <p>For a VPN app targeting {@link android.os.Build.VERSION_CODES#N API 24} or above, Android
-     * provides users with the ability to set it as always-on, so that VPN connection is
-     * persisted after device reboot and app upgrade. Always-on VPN can also be enabled by device
-     * owner and profile owner apps through
-     * {@link DevicePolicyManager#setAlwaysOnVpnPackage}.
-     *
-     * <p>VPN apps not supporting this feature should opt out by adding this meta-data field to the
-     * {@code VpnService} component of {@code AndroidManifest.xml}. In case there is more than one
-     * {@code VpnService} component defined in {@code AndroidManifest.xml}, opting out any one of
-     * them will opt out the entire app. For example,
-     * <pre> {@code
-     * <service android:name=".ExampleVpnService"
-     *         android:permission="android.permission.BIND_VPN_SERVICE">
-     *     <intent-filter>
-     *         <action android:name="android.net.VpnService"/>
-     *     </intent-filter>
-     *     <meta-data android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
-     *             android:value=false/>
-     * </service>
-     * } </pre>
-     *
-     * <p>This meta-data field defaults to {@code true} if absent. It will only have effect on
-     * {@link android.os.Build.VERSION_CODES#O_MR1} or higher.
-     */
-    public static final String SERVICE_META_DATA_SUPPORTS_ALWAYS_ON =
-            "android.net.VpnService.SUPPORTS_ALWAYS_ON";
-
-    /**
-     * Use IConnectivityManager since those methods are hidden and not
-     * available in ConnectivityManager.
-     */
-    private static IConnectivityManager getService() {
-        return IConnectivityManager.Stub.asInterface(
-                ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
-    }
-
-    /**
-     * Prepare to establish a VPN connection. This method returns {@code null}
-     * if the VPN application is already prepared or if the user has previously
-     * consented to the VPN application. Otherwise, it returns an
-     * {@link Intent} to a system activity. The application should launch the
-     * activity using {@link Activity#startActivityForResult} to get itself
-     * prepared. The activity may pop up a dialog to require user action, and
-     * the result will come back via its {@link Activity#onActivityResult}.
-     * If the result is {@link Activity#RESULT_OK}, the application becomes
-     * prepared and is granted to use other methods in this class.
-     *
-     * <p>Only one application can be granted at the same time. The right
-     * is revoked when another application is granted. The application
-     * losing the right will be notified via its {@link #onRevoke}. Unless
-     * it becomes prepared again, subsequent calls to other methods in this
-     * class will fail.
-     *
-     * <p>The user may disable the VPN at any time while it is activated, in
-     * which case this method will return an intent the next time it is
-     * executed to obtain the user's consent again.
-     *
-     * @see #onRevoke
-     */
-    public static Intent prepare(Context context) {
-        try {
-            if (getService().prepareVpn(context.getPackageName(), null, context.getUserId())) {
-                return null;
-            }
-        } catch (RemoteException e) {
-            // ignore
-        }
-        return VpnConfig.getIntentForConfirmation();
-    }
-
-    /**
-     * Version of {@link #prepare(Context)} which does not require user consent.
-     *
-     * <p>Requires {@link android.Manifest.permission#CONTROL_VPN} and should generally not be
-     * used. Only acceptable in situations where user consent has been obtained through other means.
-     *
-     * <p>Once this is run, future preparations may be done with the standard prepare method as this
-     * will authorize the package to prepare the VPN without consent in the future.
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.CONTROL_VPN)
-    public static void prepareAndAuthorize(Context context) {
-        IConnectivityManager cm = getService();
-        String packageName = context.getPackageName();
-        try {
-            // Only prepare if we're not already prepared.
-            int userId = context.getUserId();
-            if (!cm.prepareVpn(packageName, null, userId)) {
-                cm.prepareVpn(null, packageName, userId);
-            }
-            cm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE);
-        } catch (RemoteException e) {
-            // ignore
-        }
-    }
-
-    /**
-     * Protect a socket from VPN connections. After protecting, data sent
-     * through this socket will go directly to the underlying network,
-     * so its traffic will not be forwarded through the VPN.
-     * This method is useful if some connections need to be kept
-     * outside of VPN. For example, a VPN tunnel should protect itself if its
-     * destination is covered by VPN routes. Otherwise its outgoing packets
-     * will be sent back to the VPN interface and cause an infinite loop. This
-     * method will fail if the application is not prepared or is revoked.
-     *
-     * <p class="note">The socket is NOT closed by this method.
-     *
-     * @return {@code true} on success.
-     */
-    public boolean protect(int socket) {
-        return NetworkUtils.protectFromVpn(socket);
-    }
-
-    /**
-     * Convenience method to protect a {@link Socket} from VPN connections.
-     *
-     * @return {@code true} on success.
-     * @see #protect(int)
-     */
-    public boolean protect(Socket socket) {
-        return protect(socket.getFileDescriptor$().getInt$());
-    }
-
-    /**
-     * Convenience method to protect a {@link DatagramSocket} from VPN
-     * connections.
-     *
-     * @return {@code true} on success.
-     * @see #protect(int)
-     */
-    public boolean protect(DatagramSocket socket) {
-        return protect(socket.getFileDescriptor$().getInt$());
-    }
-
-    /**
-     * Adds a network address to the VPN interface.
-     *
-     * Both IPv4 and IPv6 addresses are supported. The VPN must already be established. Fails if the
-     * address is already in use or cannot be assigned to the interface for any other reason.
-     *
-     * Adding an address implicitly allows traffic from that address family (i.e., IPv4 or IPv6) to
-     * be routed over the VPN. @see Builder#allowFamily
-     *
-     * @throws IllegalArgumentException if the address is invalid.
-     *
-     * @param address The IP address (IPv4 or IPv6) to assign to the VPN interface.
-     * @param prefixLength The prefix length of the address.
-     *
-     * @return {@code true} on success.
-     * @see Builder#addAddress
-     *
-     * @hide
-     */
-    public boolean addAddress(InetAddress address, int prefixLength) {
-        check(address, prefixLength);
-        try {
-            return getService().addVpnAddress(address.getHostAddress(), prefixLength);
-        } catch (RemoteException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    /**
-     * Removes a network address from the VPN interface.
-     *
-     * Both IPv4 and IPv6 addresses are supported. The VPN must already be established. Fails if the
-     * address is not assigned to the VPN interface, or if it is the only address assigned (thus
-     * cannot be removed), or if the address cannot be removed for any other reason.
-     *
-     * After removing an address, if there are no addresses, routes or DNS servers of a particular
-     * address family (i.e., IPv4 or IPv6) configured on the VPN, that <b>DOES NOT</b> block that
-     * family from being routed. In other words, once an address family has been allowed, it stays
-     * allowed for the rest of the VPN's session. @see Builder#allowFamily
-     *
-     * @throws IllegalArgumentException if the address is invalid.
-     *
-     * @param address The IP address (IPv4 or IPv6) to assign to the VPN interface.
-     * @param prefixLength The prefix length of the address.
-     *
-     * @return {@code true} on success.
-     *
-     * @hide
-     */
-    public boolean removeAddress(InetAddress address, int prefixLength) {
-        check(address, prefixLength);
-        try {
-            return getService().removeVpnAddress(address.getHostAddress(), prefixLength);
-        } catch (RemoteException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    /**
-     * Sets the underlying networks used by the VPN for its upstream connections.
-     *
-     * <p>Used by the system to know the actual networks that carry traffic for apps affected by
-     * this VPN in order to present this information to the user (e.g., via status bar icons).
-     *
-     * <p>This method only needs to be called if the VPN has explicitly bound its underlying
-     * communications channels &mdash; such as the socket(s) passed to {@link #protect(int)} &mdash;
-     * to a {@code Network} using APIs such as {@link Network#bindSocket(Socket)} or
-     * {@link Network#bindSocket(DatagramSocket)}. The VPN should call this method every time
-     * the set of {@code Network}s it is using changes.
-     *
-     * <p>{@code networks} is one of the following:
-     * <ul>
-     * <li><strong>a non-empty array</strong>: an array of one or more {@link Network}s, in
-     * decreasing preference order. For example, if this VPN uses both wifi and mobile (cellular)
-     * networks to carry app traffic, but prefers or uses wifi more than mobile, wifi should appear
-     * first in the array.</li>
-     * <li><strong>an empty array</strong>: a zero-element array, meaning that the VPN has no
-     * underlying network connection, and thus, app traffic will not be sent or received.</li>
-     * <li><strong>null</strong>: (default) signifies that the VPN uses whatever is the system's
-     * default network. I.e., it doesn't use the {@code bindSocket} or {@code bindDatagramSocket}
-     * APIs mentioned above to send traffic over specific channels.</li>
-     * </ul>
-     *
-     * <p>This call will succeed only if the VPN is currently established. For setting this value
-     * when the VPN has not yet been established, see {@link Builder#setUnderlyingNetworks}.
-     *
-     * @param networks An array of networks the VPN uses to tunnel traffic to/from its servers.
-     *
-     * @return {@code true} on success.
-     */
-    public boolean setUnderlyingNetworks(Network[] networks) {
-        try {
-            return getService().setUnderlyingNetworksForVpn(networks);
-        } catch (RemoteException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    /**
-     * Returns whether the service is running in always-on VPN mode. In this mode the system ensures
-     * that the service is always running by restarting it when necessary, e.g. after reboot.
-     *
-     * @see DevicePolicyManager#setAlwaysOnVpnPackage(ComponentName, String, boolean, Set)
-     */
-    public final boolean isAlwaysOn() {
-        try {
-            return getService().isCallerCurrentAlwaysOnVpnApp();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Returns whether the service is running in always-on VPN lockdown mode. In this mode the
-     * system ensures that the service is always running and that the apps aren't allowed to bypass
-     * the VPN.
-     *
-     * @see DevicePolicyManager#setAlwaysOnVpnPackage(ComponentName, String, boolean, Set)
-     */
-    public final boolean isLockdownEnabled() {
-        try {
-            return getService().isCallerCurrentAlwaysOnVpnLockdownApp();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Return the communication interface to the service. This method returns
-     * {@code null} on {@link Intent}s other than {@link #SERVICE_INTERFACE}
-     * action. Applications overriding this method must identify the intent
-     * and return the corresponding interface accordingly.
-     *
-     * @see Service#onBind
-     */
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (intent != null && SERVICE_INTERFACE.equals(intent.getAction())) {
-            return new Callback();
-        }
-        return null;
-    }
-
-    /**
-     * Invoked when the application is revoked. At this moment, the VPN
-     * interface is already deactivated by the system. The application should
-     * close the file descriptor and shut down gracefully. The default
-     * implementation of this method is calling {@link Service#stopSelf()}.
-     *
-     * <p class="note">Calls to this method may not happen on the main thread
-     * of the process.
-     *
-     * @see #prepare
-     */
-    public void onRevoke() {
-        stopSelf();
-    }
-
-    /**
-     * Use raw Binder instead of AIDL since now there is only one usage.
-     */
-    private class Callback extends Binder {
-        @Override
-        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
-            if (code == IBinder.LAST_CALL_TRANSACTION) {
-                onRevoke();
-                return true;
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Private method to validate address and prefixLength.
-     */
-    private static void check(InetAddress address, int prefixLength) {
-        if (address.isLoopbackAddress()) {
-            throw new IllegalArgumentException("Bad address");
-        }
-        if (address instanceof Inet4Address) {
-            if (prefixLength < 0 || prefixLength > 32) {
-                throw new IllegalArgumentException("Bad prefixLength");
-            }
-        } else if (address instanceof Inet6Address) {
-            if (prefixLength < 0 || prefixLength > 128) {
-                throw new IllegalArgumentException("Bad prefixLength");
-            }
-        } else {
-            throw new IllegalArgumentException("Unsupported family");
-        }
-    }
-
-    /**
-     * Helper class to create a VPN interface. This class should be always
-     * used within the scope of the outer {@link VpnService}.
-     *
-     * @see VpnService
-     */
-    public class Builder {
-
-        private final VpnConfig mConfig = new VpnConfig();
-        @UnsupportedAppUsage
-        private final List<LinkAddress> mAddresses = new ArrayList<LinkAddress>();
-        @UnsupportedAppUsage
-        private final List<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
-
-        public Builder() {
-            mConfig.user = VpnService.this.getClass().getName();
-        }
-
-        /**
-         * Set the name of this session. It will be displayed in
-         * system-managed dialogs and notifications. This is recommended
-         * not required.
-         */
-        @NonNull
-        public Builder setSession(@NonNull String session) {
-            mConfig.session = session;
-            return this;
-        }
-
-        /**
-         * Set the {@link PendingIntent} to an activity for users to
-         * configure the VPN connection. If it is not set, the button
-         * to configure will not be shown in system-managed dialogs.
-         */
-        @NonNull
-        public Builder setConfigureIntent(@NonNull PendingIntent intent) {
-            mConfig.configureIntent = intent;
-            return this;
-        }
-
-        /**
-         * Set the maximum transmission unit (MTU) of the VPN interface. If
-         * it is not set, the default value in the operating system will be
-         * used.
-         *
-         * @throws IllegalArgumentException if the value is not positive.
-         */
-        @NonNull
-        public Builder setMtu(int mtu) {
-            if (mtu <= 0) {
-                throw new IllegalArgumentException("Bad mtu");
-            }
-            mConfig.mtu = mtu;
-            return this;
-        }
-
-        /**
-         * Sets an HTTP proxy for the VPN network. This proxy is only a recommendation
-         * and it is possible that some apps will ignore it.
-         */
-        @NonNull
-        public Builder setHttpProxy(@NonNull ProxyInfo proxyInfo) {
-            mConfig.proxyInfo = proxyInfo;
-            return this;
-        }
-
-        /**
-         * Add a network address to the VPN interface. Both IPv4 and IPv6
-         * addresses are supported. At least one address must be set before
-         * calling {@link #establish}.
-         *
-         * Adding an address implicitly allows traffic from that address family
-         * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
-         *
-         * @throws IllegalArgumentException if the address is invalid.
-         */
-        @NonNull
-        public Builder addAddress(@NonNull InetAddress address, int prefixLength) {
-            check(address, prefixLength);
-
-            if (address.isAnyLocalAddress()) {
-                throw new IllegalArgumentException("Bad address");
-            }
-            mAddresses.add(new LinkAddress(address, prefixLength));
-            mConfig.updateAllowedFamilies(address);
-            return this;
-        }
-
-        /**
-         * Convenience method to add a network address to the VPN interface
-         * using a numeric address string. See {@link InetAddress} for the
-         * definitions of numeric address formats.
-         *
-         * Adding an address implicitly allows traffic from that address family
-         * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
-         *
-         * @throws IllegalArgumentException if the address is invalid.
-         * @see #addAddress(InetAddress, int)
-         */
-        @NonNull
-        public Builder addAddress(@NonNull String address, int prefixLength) {
-            return addAddress(InetAddress.parseNumericAddress(address), prefixLength);
-        }
-
-        /**
-         * Add a network route to the VPN interface. Both IPv4 and IPv6
-         * routes are supported.
-         *
-         * Adding a route implicitly allows traffic from that address family
-         * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
-         *
-         * @throws IllegalArgumentException if the route is invalid.
-         */
-        @NonNull
-        public Builder addRoute(@NonNull InetAddress address, int prefixLength) {
-            check(address, prefixLength);
-
-            int offset = prefixLength / 8;
-            byte[] bytes = address.getAddress();
-            if (offset < bytes.length) {
-                for (bytes[offset] <<= prefixLength % 8; offset < bytes.length; ++offset) {
-                    if (bytes[offset] != 0) {
-                        throw new IllegalArgumentException("Bad address");
-                    }
-                }
-            }
-            mRoutes.add(new RouteInfo(new IpPrefix(address, prefixLength), null));
-            mConfig.updateAllowedFamilies(address);
-            return this;
-        }
-
-        /**
-         * Convenience method to add a network route to the VPN interface
-         * using a numeric address string. See {@link InetAddress} for the
-         * definitions of numeric address formats.
-         *
-         * Adding a route implicitly allows traffic from that address family
-         * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
-         *
-         * @throws IllegalArgumentException if the route is invalid.
-         * @see #addRoute(InetAddress, int)
-         */
-        @NonNull
-        public Builder addRoute(@NonNull String address, int prefixLength) {
-            return addRoute(InetAddress.parseNumericAddress(address), prefixLength);
-        }
-
-        /**
-         * Add a DNS server to the VPN connection. Both IPv4 and IPv6
-         * addresses are supported. If none is set, the DNS servers of
-         * the default network will be used.
-         *
-         * Adding a server implicitly allows traffic from that address family
-         * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
-         *
-         * @throws IllegalArgumentException if the address is invalid.
-         */
-        @NonNull
-        public Builder addDnsServer(@NonNull InetAddress address) {
-            if (address.isLoopbackAddress() || address.isAnyLocalAddress()) {
-                throw new IllegalArgumentException("Bad address");
-            }
-            if (mConfig.dnsServers == null) {
-                mConfig.dnsServers = new ArrayList<String>();
-            }
-            mConfig.dnsServers.add(address.getHostAddress());
-            return this;
-        }
-
-        /**
-         * Convenience method to add a DNS server to the VPN connection
-         * using a numeric address string. See {@link InetAddress} for the
-         * definitions of numeric address formats.
-         *
-         * Adding a server implicitly allows traffic from that address family
-         * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
-         *
-         * @throws IllegalArgumentException if the address is invalid.
-         * @see #addDnsServer(InetAddress)
-         */
-        @NonNull
-        public Builder addDnsServer(@NonNull String address) {
-            return addDnsServer(InetAddress.parseNumericAddress(address));
-        }
-
-        /**
-         * Add a search domain to the DNS resolver.
-         */
-        @NonNull
-        public Builder addSearchDomain(@NonNull String domain) {
-            if (mConfig.searchDomains == null) {
-                mConfig.searchDomains = new ArrayList<String>();
-            }
-            mConfig.searchDomains.add(domain);
-            return this;
-        }
-
-        /**
-         * Allows traffic from the specified address family.
-         *
-         * By default, if no address, route or DNS server of a specific family (IPv4 or IPv6) is
-         * added to this VPN, then all outgoing traffic of that family is blocked. If any address,
-         * route or DNS server is added, that family is allowed.
-         *
-         * This method allows an address family to be unblocked even without adding an address,
-         * route or DNS server of that family. Traffic of that family will then typically
-         * fall-through to the underlying network if it's supported.
-         *
-         * {@code family} must be either {@code AF_INET} (for IPv4) or {@code AF_INET6} (for IPv6).
-         * {@link IllegalArgumentException} is thrown if it's neither.
-         *
-         * @param family The address family ({@code AF_INET} or {@code AF_INET6}) to allow.
-         *
-         * @return this {@link Builder} object to facilitate chaining of method calls.
-         */
-        @NonNull
-        public Builder allowFamily(int family) {
-            if (family == AF_INET) {
-                mConfig.allowIPv4 = true;
-            } else if (family == AF_INET6) {
-                mConfig.allowIPv6 = true;
-            } else {
-                throw new IllegalArgumentException(family + " is neither " + AF_INET + " nor " +
-                        AF_INET6);
-            }
-            return this;
-        }
-
-        private void verifyApp(String packageName) throws PackageManager.NameNotFoundException {
-            IPackageManager pm = IPackageManager.Stub.asInterface(
-                    ServiceManager.getService("package"));
-            try {
-                pm.getApplicationInfo(packageName, 0, UserHandle.getCallingUserId());
-            } catch (RemoteException e) {
-                throw new IllegalStateException(e);
-            }
-        }
-
-        /**
-         * Adds an application that's allowed to access the VPN connection.
-         *
-         * If this method is called at least once, only applications added through this method (and
-         * no others) are allowed access. Else (if this method is never called), all applications
-         * are allowed by default.  If some applications are added, other, un-added applications
-         * will use networking as if the VPN wasn't running.
-         *
-         * A {@link Builder} may have only a set of allowed applications OR a set of disallowed
-         * ones, but not both. Calling this method after {@link #addDisallowedApplication} has
-         * already been called, or vice versa, will throw an {@link UnsupportedOperationException}.
-         *
-         * {@code packageName} must be the canonical name of a currently installed application.
-         * {@link PackageManager.NameNotFoundException} is thrown if there's no such application.
-         *
-         * @throws PackageManager.NameNotFoundException If the application isn't installed.
-         *
-         * @param packageName The full name (e.g.: "com.google.apps.contacts") of an application.
-         *
-         * @return this {@link Builder} object to facilitate chaining method calls.
-         */
-        @NonNull
-        public Builder addAllowedApplication(@NonNull String packageName)
-                throws PackageManager.NameNotFoundException {
-            if (mConfig.disallowedApplications != null) {
-                throw new UnsupportedOperationException("addDisallowedApplication already called");
-            }
-            verifyApp(packageName);
-            if (mConfig.allowedApplications == null) {
-                mConfig.allowedApplications = new ArrayList<String>();
-            }
-            mConfig.allowedApplications.add(packageName);
-            return this;
-        }
-
-        /**
-         * Adds an application that's denied access to the VPN connection.
-         *
-         * By default, all applications are allowed access, except for those denied through this
-         * method.  Denied applications will use networking as if the VPN wasn't running.
-         *
-         * A {@link Builder} may have only a set of allowed applications OR a set of disallowed
-         * ones, but not both. Calling this method after {@link #addAllowedApplication} has already
-         * been called, or vice versa, will throw an {@link UnsupportedOperationException}.
-         *
-         * {@code packageName} must be the canonical name of a currently installed application.
-         * {@link PackageManager.NameNotFoundException} is thrown if there's no such application.
-         *
-         * @throws PackageManager.NameNotFoundException If the application isn't installed.
-         *
-         * @param packageName The full name (e.g.: "com.google.apps.contacts") of an application.
-         *
-         * @return this {@link Builder} object to facilitate chaining method calls.
-         */
-        @NonNull
-        public Builder addDisallowedApplication(@NonNull String packageName)
-                throws PackageManager.NameNotFoundException {
-            if (mConfig.allowedApplications != null) {
-                throw new UnsupportedOperationException("addAllowedApplication already called");
-            }
-            verifyApp(packageName);
-            if (mConfig.disallowedApplications == null) {
-                mConfig.disallowedApplications = new ArrayList<String>();
-            }
-            mConfig.disallowedApplications.add(packageName);
-            return this;
-        }
-
-        /**
-         * Allows all apps to bypass this VPN connection.
-         *
-         * By default, all traffic from apps is forwarded through the VPN interface and it is not
-         * possible for apps to side-step the VPN. If this method is called, apps may use methods
-         * such as {@link ConnectivityManager#bindProcessToNetwork} to instead send/receive
-         * directly over the underlying network or any other network they have permissions for.
-         *
-         * @return this {@link Builder} object to facilitate chaining of method calls.
-         */
-        @NonNull
-        public Builder allowBypass() {
-            mConfig.allowBypass = true;
-            return this;
-        }
-
-        /**
-         * Sets the VPN interface's file descriptor to be in blocking/non-blocking mode.
-         *
-         * By default, the file descriptor returned by {@link #establish} is non-blocking.
-         *
-         * @param blocking True to put the descriptor into blocking mode; false for non-blocking.
-         *
-         * @return this {@link Builder} object to facilitate chaining method calls.
-         */
-        @NonNull
-        public Builder setBlocking(boolean blocking) {
-            mConfig.blocking = blocking;
-            return this;
-        }
-
-        /**
-         * Sets the underlying networks used by the VPN for its upstream connections.
-         *
-         * @see VpnService#setUnderlyingNetworks
-         *
-         * @param networks An array of networks the VPN uses to tunnel traffic to/from its servers.
-         *
-         * @return this {@link Builder} object to facilitate chaining method calls.
-         */
-        @NonNull
-        public Builder setUnderlyingNetworks(@Nullable Network[] networks) {
-            mConfig.underlyingNetworks = networks != null ? networks.clone() : null;
-            return this;
-        }
-
-        /**
-         * Marks the VPN network as metered. A VPN network is classified as metered when the user is
-         * sensitive to heavy data usage due to monetary costs and/or data limitations. In such
-         * cases, you should set this to {@code true} so that apps on the system can avoid doing
-         * large data transfers. Otherwise, set this to {@code false}. Doing so would cause VPN
-         * network to inherit its meteredness from its underlying networks.
-         *
-         * <p>VPN apps targeting {@link android.os.Build.VERSION_CODES#Q} or above will be
-         * considered metered by default.
-         *
-         * @param isMetered {@code true} if VPN network should be treated as metered regardless of
-         *     underlying network meteredness
-         * @return this {@link Builder} object to facilitate chaining method calls
-         * @see #setUnderlyingNetworks(Network[])
-         * @see ConnectivityManager#isActiveNetworkMetered()
-         */
-        @NonNull
-        public Builder setMetered(boolean isMetered) {
-            mConfig.isMetered = isMetered;
-            return this;
-        }
-
-        /**
-         * Create a VPN interface using the parameters supplied to this
-         * builder. The interface works on IP packets, and a file descriptor
-         * is returned for the application to access them. Each read
-         * retrieves an outgoing packet which was routed to the interface.
-         * Each write injects an incoming packet just like it was received
-         * from the interface. The file descriptor is put into non-blocking
-         * mode by default to avoid blocking Java threads. To use the file
-         * descriptor completely in native space, see
-         * {@link ParcelFileDescriptor#detachFd()}. The application MUST
-         * close the file descriptor when the VPN connection is terminated.
-         * The VPN interface will be removed and the network will be
-         * restored by the system automatically.
-         *
-         * <p>To avoid conflicts, there can be only one active VPN interface
-         * at the same time. Usually network parameters are never changed
-         * during the lifetime of a VPN connection. It is also common for an
-         * application to create a new file descriptor after closing the
-         * previous one. However, it is rare but not impossible to have two
-         * interfaces while performing a seamless handover. In this case, the
-         * old interface will be deactivated when the new one is created
-         * successfully. Both file descriptors are valid but now outgoing
-         * packets will be routed to the new interface. Therefore, after
-         * draining the old file descriptor, the application MUST close it
-         * and start using the new file descriptor. If the new interface
-         * cannot be created, the existing interface and its file descriptor
-         * remain untouched.
-         *
-         * <p>An exception will be thrown if the interface cannot be created
-         * for any reason. However, this method returns {@code null} if the
-         * application is not prepared or is revoked. This helps solve
-         * possible race conditions between other VPN applications.
-         *
-         * @return {@link ParcelFileDescriptor} of the VPN interface, or
-         *         {@code null} if the application is not prepared.
-         * @throws IllegalArgumentException if a parameter is not accepted
-         *         by the operating system.
-         * @throws IllegalStateException if a parameter cannot be applied
-         *         by the operating system.
-         * @throws SecurityException if the service is not properly declared
-         *         in {@code AndroidManifest.xml}.
-         * @see VpnService
-         */
-        @Nullable
-        public ParcelFileDescriptor establish() {
-            mConfig.addresses = mAddresses;
-            mConfig.routes = mRoutes;
-
-            try {
-                return getService().establishVpn(mConfig);
-            } catch (RemoteException e) {
-                throw new IllegalStateException(e);
-            }
-        }
-    }
-}
diff --git a/framework/src/android/net/VpnTransportInfo.java b/framework/src/android/net/VpnTransportInfo.java
new file mode 100644
index 0000000..0242ba0
--- /dev/null
+++ b/framework/src/android/net/VpnTransportInfo.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import com.android.internal.util.MessageUtils;
+
+import java.util.Objects;
+
+/**
+ * Container for VPN-specific transport information.
+ *
+ * @see android.net.TransportInfo
+ * @see NetworkCapabilities#getTransportInfo()
+ *
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
+public final class VpnTransportInfo implements TransportInfo, Parcelable {
+    private static final SparseArray<String> sTypeToString =
+            MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"});
+
+    /** Type of this VPN. */
+    @VpnManager.VpnType public final int type;
+
+    public VpnTransportInfo(@VpnManager.VpnType int type) {
+        this.type = type;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof VpnTransportInfo)) return false;
+
+        VpnTransportInfo that = (VpnTransportInfo) o;
+        return this.type == that.type;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(type);
+    }
+
+    @Override
+    public String toString() {
+        final String typeString = sTypeToString.get(type, "VPN_TYPE_???");
+        return String.format("VpnTransportInfo{%s}", typeString);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(type);
+    }
+
+    public static final @NonNull Creator<VpnTransportInfo> CREATOR =
+            new Creator<VpnTransportInfo>() {
+        public VpnTransportInfo createFromParcel(Parcel in) {
+            return new VpnTransportInfo(in.readInt());
+        }
+        public VpnTransportInfo[] newArray(int size) {
+            return new VpnTransportInfo[size];
+        }
+    };
+}
diff --git a/service/Android.bp b/service/Android.bp
index 8fc3181..f20b89f 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -14,6 +14,15 @@
 // limitations under the License.
 //
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
 cc_library_shared {
     name: "libservice-connectivity",
     // TODO: build against the NDK (sdk_version: "30" for example)
@@ -25,7 +34,6 @@
     ],
     srcs: [
         "jni/com_android_server_TestNetworkService.cpp",
-        "jni/com_android_server_connectivity_Vpn.cpp",
         "jni/onload.cpp",
     ],
     shared_libs: [
diff --git a/service/jni/com_android_server_connectivity_Vpn.cpp b/service/jni/com_android_server_connectivity_Vpn.cpp
deleted file mode 100644
index ea5e718..0000000
--- a/service/jni/com_android_server_connectivity_Vpn.cpp
+++ /dev/null
@@ -1,377 +0,0 @@
-/*
- * Copyright (C) 2011 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.
- */
-
-#define LOG_NDEBUG 0
-
-#define LOG_TAG "VpnJni"
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <linux/if.h>
-#include <linux/if_tun.h>
-#include <linux/route.h>
-#include <linux/ipv6_route.h>
-#include <netinet/in.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <log/log.h>
-#include <android/log.h>
-
-#include "netutils/ifc.h"
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-
-namespace android
-{
-
-static int inet4 = -1;
-static int inet6 = -1;
-
-static inline in_addr_t *as_in_addr(sockaddr *sa) {
-    return &((sockaddr_in *)sa)->sin_addr.s_addr;
-}
-
-//------------------------------------------------------------------------------
-
-#define SYSTEM_ERROR (-1)
-#define BAD_ARGUMENT (-2)
-
-static int create_interface(int mtu)
-{
-    int tun = open("/dev/tun", O_RDWR | O_NONBLOCK | O_CLOEXEC);
-
-    ifreq ifr4;
-    memset(&ifr4, 0, sizeof(ifr4));
-
-    // Allocate interface.
-    ifr4.ifr_flags = IFF_TUN | IFF_NO_PI;
-    if (ioctl(tun, TUNSETIFF, &ifr4)) {
-        ALOGE("Cannot allocate TUN: %s", strerror(errno));
-        goto error;
-    }
-
-    // Activate interface.
-    ifr4.ifr_flags = IFF_UP;
-    if (ioctl(inet4, SIOCSIFFLAGS, &ifr4)) {
-        ALOGE("Cannot activate %s: %s", ifr4.ifr_name, strerror(errno));
-        goto error;
-    }
-
-    // Set MTU if it is specified.
-    ifr4.ifr_mtu = mtu;
-    if (mtu > 0 && ioctl(inet4, SIOCSIFMTU, &ifr4)) {
-        ALOGE("Cannot set MTU on %s: %s", ifr4.ifr_name, strerror(errno));
-        goto error;
-    }
-
-    return tun;
-
-error:
-    close(tun);
-    return SYSTEM_ERROR;
-}
-
-static int get_interface_name(char *name, int tun)
-{
-    ifreq ifr4;
-    if (ioctl(tun, TUNGETIFF, &ifr4)) {
-        ALOGE("Cannot get interface name: %s", strerror(errno));
-        return SYSTEM_ERROR;
-    }
-    strncpy(name, ifr4.ifr_name, IFNAMSIZ);
-    return 0;
-}
-
-static int get_interface_index(const char *name)
-{
-    ifreq ifr4;
-    strncpy(ifr4.ifr_name, name, IFNAMSIZ);
-    if (ioctl(inet4, SIOGIFINDEX, &ifr4)) {
-        ALOGE("Cannot get index of %s: %s", name, strerror(errno));
-        return SYSTEM_ERROR;
-    }
-    return ifr4.ifr_ifindex;
-}
-
-static int set_addresses(const char *name, const char *addresses)
-{
-    int index = get_interface_index(name);
-    if (index < 0) {
-        return index;
-    }
-
-    ifreq ifr4;
-    memset(&ifr4, 0, sizeof(ifr4));
-    strncpy(ifr4.ifr_name, name, IFNAMSIZ);
-    ifr4.ifr_addr.sa_family = AF_INET;
-    ifr4.ifr_netmask.sa_family = AF_INET;
-
-    in6_ifreq ifr6;
-    memset(&ifr6, 0, sizeof(ifr6));
-    ifr6.ifr6_ifindex = index;
-
-    char address[65];
-    int prefix;
-    int chars;
-    int count = 0;
-
-    while (sscanf(addresses, " %64[^/]/%d %n", address, &prefix, &chars) == 2) {
-        addresses += chars;
-
-        if (strchr(address, ':')) {
-            // Add an IPv6 address.
-            if (inet_pton(AF_INET6, address, &ifr6.ifr6_addr) != 1 ||
-                    prefix < 0 || prefix > 128) {
-                count = BAD_ARGUMENT;
-                break;
-            }
-
-            ifr6.ifr6_prefixlen = prefix;
-            if (ioctl(inet6, SIOCSIFADDR, &ifr6)) {
-                count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR;
-                break;
-            }
-        } else {
-            // Add an IPv4 address.
-            if (inet_pton(AF_INET, address, as_in_addr(&ifr4.ifr_addr)) != 1 ||
-                    prefix < 0 || prefix > 32) {
-                count = BAD_ARGUMENT;
-                break;
-            }
-
-            if (count) {
-                snprintf(ifr4.ifr_name, sizeof(ifr4.ifr_name), "%s:%d", name, count);
-            }
-            if (ioctl(inet4, SIOCSIFADDR, &ifr4)) {
-                count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR;
-                break;
-            }
-
-            in_addr_t mask = prefix ? (~0 << (32 - prefix)) : 0;
-            *as_in_addr(&ifr4.ifr_netmask) = htonl(mask);
-            if (ioctl(inet4, SIOCSIFNETMASK, &ifr4)) {
-                count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR;
-                break;
-            }
-        }
-        ALOGD("Address added on %s: %s/%d", name, address, prefix);
-        ++count;
-    }
-
-    if (count == BAD_ARGUMENT) {
-        ALOGE("Invalid address: %s/%d", address, prefix);
-    } else if (count == SYSTEM_ERROR) {
-        ALOGE("Cannot add address: %s/%d: %s", address, prefix, strerror(errno));
-    } else if (*addresses) {
-        ALOGE("Invalid address: %s", addresses);
-        count = BAD_ARGUMENT;
-    }
-
-    return count;
-}
-
-static int reset_interface(const char *name)
-{
-    ifreq ifr4;
-    strncpy(ifr4.ifr_name, name, IFNAMSIZ);
-    ifr4.ifr_flags = 0;
-
-    if (ioctl(inet4, SIOCSIFFLAGS, &ifr4) && errno != ENODEV) {
-        ALOGE("Cannot reset %s: %s", name, strerror(errno));
-        return SYSTEM_ERROR;
-    }
-    return 0;
-}
-
-static int check_interface(const char *name)
-{
-    ifreq ifr4;
-    strncpy(ifr4.ifr_name, name, IFNAMSIZ);
-    ifr4.ifr_flags = 0;
-
-    if (ioctl(inet4, SIOCGIFFLAGS, &ifr4) && errno != ENODEV) {
-        ALOGE("Cannot check %s: %s", name, strerror(errno));
-    }
-    return ifr4.ifr_flags;
-}
-
-static bool modifyAddress(JNIEnv *env, jobject thiz, jstring jName, jstring jAddress,
-                          jint jPrefixLength, bool add)
-{
-    int error = SYSTEM_ERROR;
-    const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL;
-    const char *address = jAddress ? env->GetStringUTFChars(jAddress, NULL) : NULL;
-
-    if (!name) {
-        jniThrowNullPointerException(env, "name");
-    } else if (!address) {
-        jniThrowNullPointerException(env, "address");
-    } else {
-        if (add) {
-            if ((error = ifc_add_address(name, address, jPrefixLength)) != 0) {
-                ALOGE("Cannot add address %s/%d on interface %s (%s)", address, jPrefixLength, name,
-                      strerror(-error));
-            }
-        } else {
-            if ((error = ifc_del_address(name, address, jPrefixLength)) != 0) {
-                ALOGE("Cannot del address %s/%d on interface %s (%s)", address, jPrefixLength, name,
-                      strerror(-error));
-            }
-        }
-    }
-
-    if (name) {
-        env->ReleaseStringUTFChars(jName, name);
-    }
-    if (address) {
-        env->ReleaseStringUTFChars(jAddress, address);
-    }
-    return !error;
-}
-
-//------------------------------------------------------------------------------
-
-static void throwException(JNIEnv *env, int error, const char *message)
-{
-    if (error == SYSTEM_ERROR) {
-        jniThrowException(env, "java/lang/IllegalStateException", message);
-    } else {
-        jniThrowException(env, "java/lang/IllegalArgumentException", message);
-    }
-}
-
-static jint create(JNIEnv *env, jobject /* thiz */, jint mtu)
-{
-    int tun = create_interface(mtu);
-    if (tun < 0) {
-        throwException(env, tun, "Cannot create interface");
-        return -1;
-    }
-    return tun;
-}
-
-static jstring getName(JNIEnv *env, jobject /* thiz */, jint tun)
-{
-    char name[IFNAMSIZ];
-    if (get_interface_name(name, tun) < 0) {
-        throwException(env, SYSTEM_ERROR, "Cannot get interface name");
-        return NULL;
-    }
-    return env->NewStringUTF(name);
-}
-
-static jint setAddresses(JNIEnv *env, jobject /* thiz */, jstring jName,
-        jstring jAddresses)
-{
-    const char *name = NULL;
-    const char *addresses = NULL;
-    int count = -1;
-
-    name = jName ? env->GetStringUTFChars(jName, NULL) : NULL;
-    if (!name) {
-        jniThrowNullPointerException(env, "name");
-        goto error;
-    }
-    addresses = jAddresses ? env->GetStringUTFChars(jAddresses, NULL) : NULL;
-    if (!addresses) {
-        jniThrowNullPointerException(env, "addresses");
-        goto error;
-    }
-    count = set_addresses(name, addresses);
-    if (count < 0) {
-        throwException(env, count, "Cannot set address");
-        count = -1;
-    }
-
-error:
-    if (name) {
-        env->ReleaseStringUTFChars(jName, name);
-    }
-    if (addresses) {
-        env->ReleaseStringUTFChars(jAddresses, addresses);
-    }
-    return count;
-}
-
-static void reset(JNIEnv *env, jobject /* thiz */, jstring jName)
-{
-    const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL;
-    if (!name) {
-        jniThrowNullPointerException(env, "name");
-        return;
-    }
-    if (reset_interface(name) < 0) {
-        throwException(env, SYSTEM_ERROR, "Cannot reset interface");
-    }
-    env->ReleaseStringUTFChars(jName, name);
-}
-
-static jint check(JNIEnv *env, jobject /* thiz */, jstring jName)
-{
-    const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL;
-    if (!name) {
-        jniThrowNullPointerException(env, "name");
-        return 0;
-    }
-    int flags = check_interface(name);
-    env->ReleaseStringUTFChars(jName, name);
-    return flags;
-}
-
-static bool addAddress(JNIEnv *env, jobject thiz, jstring jName, jstring jAddress,
-                       jint jPrefixLength)
-{
-    return modifyAddress(env, thiz, jName, jAddress, jPrefixLength, true);
-}
-
-static bool delAddress(JNIEnv *env, jobject thiz, jstring jName, jstring jAddress,
-                       jint jPrefixLength)
-{
-    return modifyAddress(env, thiz, jName, jAddress, jPrefixLength, false);
-}
-
-//------------------------------------------------------------------------------
-
-static const JNINativeMethod gMethods[] = {
-    {"jniCreate", "(I)I", (void *)create},
-    {"jniGetName", "(I)Ljava/lang/String;", (void *)getName},
-    {"jniSetAddresses", "(Ljava/lang/String;Ljava/lang/String;)I", (void *)setAddresses},
-    {"jniReset", "(Ljava/lang/String;)V", (void *)reset},
-    {"jniCheck", "(Ljava/lang/String;)I", (void *)check},
-    {"jniAddAddress", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)addAddress},
-    {"jniDelAddress", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)delAddress},
-};
-
-int register_android_server_connectivity_Vpn(JNIEnv *env)
-{
-    if (inet4 == -1) {
-        inet4 = socket(AF_INET, SOCK_DGRAM, 0);
-    }
-    if (inet6 == -1) {
-        inet6 = socket(AF_INET6, SOCK_DGRAM, 0);
-    }
-    return jniRegisterNativeMethods(env, "com/android/server/connectivity/Vpn",
-            gMethods, NELEM(gMethods));
-}
-
-};
diff --git a/service/jni/onload.cpp b/service/jni/onload.cpp
index 3afcb0e..0012879 100644
--- a/service/jni/onload.cpp
+++ b/service/jni/onload.cpp
@@ -19,7 +19,6 @@
 
 namespace android {
 
-int register_android_server_connectivity_Vpn(JNIEnv* env);
 int register_android_server_TestNetworkService(JNIEnv* env);
 
 extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
@@ -29,12 +28,11 @@
         return JNI_ERR;
     }
 
-    if (register_android_server_connectivity_Vpn(env) < 0
-        || register_android_server_TestNetworkService(env) < 0) {
+    if (register_android_server_TestNetworkService(env) < 0) {
         return JNI_ERR;
     }
 
     return JNI_VERSION_1_6;
 }
 
-};
\ No newline at end of file
+};
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 3933e37..d26a7b1 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -16,7 +16,6 @@
 
 package com.android.server;
 
-import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
@@ -46,6 +45,8 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -89,11 +90,13 @@
 import android.net.IConnectivityManager;
 import android.net.IDnsResolver;
 import android.net.INetd;
+import android.net.INetworkActivityListener;
 import android.net.INetworkManagementEventObserver;
 import android.net.INetworkMonitor;
 import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkPolicyListener;
 import android.net.INetworkStatsService;
+import android.net.IOnSetOemNetworkPreferenceListener;
 import android.net.IQosCallback;
 import android.net.ISocketKeepaliveCallback;
 import android.net.InetAddresses;
@@ -131,12 +134,13 @@
 import android.net.RouteInfoParcel;
 import android.net.SocketKeepalive;
 import android.net.TetheringManager;
+import android.net.TransportInfo;
 import android.net.UidRange;
 import android.net.UidRangeParcel;
 import android.net.UnderlyingNetworkInfo;
 import android.net.Uri;
 import android.net.VpnManager;
-import android.net.VpnService;
+import android.net.VpnTransportInfo;
 import android.net.metrics.INetdEventListener;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
@@ -144,13 +148,13 @@
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.NetdService;
+import android.os.BatteryStatsManager;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.INetworkActivityListener;
 import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
@@ -160,6 +164,7 @@
 import android.os.PersistableBundle;
 import android.os.PowerManager;
 import android.os.Process;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.os.SystemClock;
@@ -167,10 +172,9 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.security.Credentials;
-import android.security.KeyStore;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.Log;
@@ -184,15 +188,13 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.net.LegacyVpnInfo;
-import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnProfile;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.BitUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.LocationPermissionChecker;
 import com.android.internal.util.MessageUtils;
 import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
 import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
 import com.android.server.am.BatteryStatsService;
@@ -211,9 +213,7 @@
 import com.android.server.connectivity.PermissionMonitor;
 import com.android.server.connectivity.ProxyTracker;
 import com.android.server.connectivity.QosCallbackTracker;
-import com.android.server.connectivity.Vpn;
 import com.android.server.net.BaseNetworkObserver;
-import com.android.server.net.LockdownVpnTracker;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.utils.PriorityDump;
 
@@ -306,18 +306,7 @@
 
     private final PerUidCounter mNetworkRequestCounter;
 
-    private KeyStore mKeyStore;
-
-    @VisibleForTesting
-    @GuardedBy("mVpns")
-    protected final SparseArray<Vpn> mVpns = new SparseArray<>();
-
-    // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by
-    // a direct call to LockdownVpnTracker.isEnabled().
-    @GuardedBy("mVpns")
-    private boolean mLockdownEnabled;
-    @GuardedBy("mVpns")
-    private LockdownVpnTracker mLockdownTracker;
+    private volatile boolean mLockdownEnabled;
 
     /**
      * Stale copy of uid rules provided by NPMS. As long as they are accessed only in internal
@@ -568,6 +557,17 @@
     private static final int EVENT_SET_REQUIRE_VPN_FOR_UIDS = 47;
 
     /**
+     * used internally when setting the default networks for OemNetworkPreferences.
+     * obj = OemNetworkPreferences
+     */
+    private static final int EVENT_SET_OEM_NETWORK_PREFERENCE = 48;
+
+    /**
+     * Used to indicate the system default network becomes active.
+     */
+    private static final int EVENT_REPORT_NETWORK_ACTIVITY = 49;
+
+    /**
      * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
      * should be shown.
      */
@@ -752,6 +752,27 @@
             }
         }
 
+        // When a lockdown VPN connects, send another CONNECTED broadcast for the underlying
+        // network type, to preserve previous behaviour.
+        private void maybeSendLegacyLockdownBroadcast(@NonNull NetworkAgentInfo vpnNai) {
+            if (vpnNai != mService.getLegacyLockdownNai()) return;
+
+            if (vpnNai.declaredUnderlyingNetworks == null
+                    || vpnNai.declaredUnderlyingNetworks.length != 1) {
+                Log.wtf(TAG, "Legacy lockdown VPN must have exactly one underlying network: "
+                        + Arrays.toString(vpnNai.declaredUnderlyingNetworks));
+                return;
+            }
+            final NetworkAgentInfo underlyingNai = mService.getNetworkAgentInfoForNetwork(
+                    vpnNai.declaredUnderlyingNetworks[0]);
+            if (underlyingNai == null) return;
+
+            final int type = underlyingNai.networkInfo.getType();
+            final DetailedState state = DetailedState.CONNECTED;
+            maybeLogBroadcast(underlyingNai, state, type, true /* isDefaultNetwork */);
+            mService.sendLegacyNetworkBroadcast(underlyingNai, state, type);
+        }
+
         /** Adds the given network to the specified legacy type list. */
         public void add(int type, NetworkAgentInfo nai) {
             if (!isTypeSupported(type)) {
@@ -769,9 +790,17 @@
 
             // Send a broadcast if this is the first network of its type or if it's the default.
             final boolean isDefaultNetwork = mService.isDefaultNetwork(nai);
+
+            // If a legacy lockdown VPN is active, override the NetworkInfo state in all broadcasts
+            // to preserve previous behaviour.
+            final DetailedState state = mService.getLegacyLockdownState(DetailedState.CONNECTED);
             if ((list.size() == 1) || isDefaultNetwork) {
-                maybeLogBroadcast(nai, DetailedState.CONNECTED, type, isDefaultNetwork);
-                mService.sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type);
+                maybeLogBroadcast(nai, state, type, isDefaultNetwork);
+                mService.sendLegacyNetworkBroadcast(nai, state, type);
+            }
+
+            if (type == TYPE_VPN && state == DetailedState.CONNECTED) {
+                maybeSendLegacyLockdownBroadcast(nai);
             }
         }
 
@@ -966,13 +995,6 @@
         }
 
         /**
-         * Get a reference to the system keystore.
-         */
-        public KeyStore getKeyStore() {
-            return KeyStore.getInstance();
-        }
-
-        /**
          * @see ProxyTracker
          */
         public ProxyTracker makeProxyTracker(@NonNull Context context,
@@ -1036,9 +1058,10 @@
 
         mMetricsLog = logger;
         mNetworkRanker = new NetworkRanker();
-        final NetworkRequest defaultInternetRequest = createDefaultInternetRequestForTransport(
-                -1, NetworkRequest.Type.REQUEST);
-        mDefaultRequest = new NetworkRequestInfo(null, defaultInternetRequest, new Binder());
+        final NetworkRequest defaultInternetRequest = createDefaultRequest();
+        mDefaultRequest = new NetworkRequestInfo(
+                defaultInternetRequest, null, new Binder(),
+                null /* attributionTags */);
         mNetworkRequests.put(defaultInternetRequest, mDefaultRequest);
         mDefaultNetworkRequests.add(mDefaultRequest);
         mNetworkRequestInfoLogs.log("REGISTER " + mDefaultRequest);
@@ -1080,7 +1103,6 @@
         mProxyTracker = mDeps.makeProxyTracker(mContext, mHandler);
 
         mNetd = netd;
-        mKeyStore = mDeps.getKeyStore();
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mLocationPermissionChecker = new LocationPermissionChecker(mContext);
@@ -1169,45 +1191,17 @@
 
         mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
 
-        // Set up the listener for user state for creating user VPNs.
+        // 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_STARTED);
-        intentFilter.addAction(Intent.ACTION_USER_STOPPED);
         intentFilter.addAction(Intent.ACTION_USER_ADDED);
         intentFilter.addAction(Intent.ACTION_USER_REMOVED);
-        intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
 
         mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
-        mUserAllContext.registerReceiver(
-                mIntentReceiver,
-                intentFilter,
-                null /* broadcastPermission */,
-                mHandler);
-        mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver(
-                mUserPresentReceiver,
-                new IntentFilter(Intent.ACTION_USER_PRESENT),
-                null /* broadcastPermission */,
-                null /* scheduler */);
+        mUserAllContext.registerReceiver(mIntentReceiver, intentFilter,
+                null /* broadcastPermission */, mHandler);
 
-        // Listen to package add and removal events for all users.
-        intentFilter = new IntentFilter();
-        intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        intentFilter.addDataScheme("package");
-        mUserAllContext.registerReceiver(
-                mIntentReceiver,
-                intentFilter,
-                null /* broadcastPermission */,
-                mHandler);
-
-        // Listen to lockdown VPN reset.
-        intentFilter = new IntentFilter();
-        intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET);
-        mUserAllContext.registerReceiver(
-                mIntentReceiver, intentFilter, NETWORK_STACK, mHandler);
-
-        mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mNMS);
+        mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mHandler, mNMS, mNetd);
 
         mSettingsObserver = new SettingsObserver(mContext, mHandler);
         registerSettingsCallbacks();
@@ -1251,14 +1245,24 @@
         return netCap;
     }
 
+    private NetworkRequest createDefaultRequest() {
+        return createDefaultInternetRequestForTransport(
+                TYPE_NONE, NetworkRequest.Type.REQUEST);
+    }
+
     private NetworkRequest createDefaultInternetRequestForTransport(
             int transportType, NetworkRequest.Type type) {
         final NetworkCapabilities netCap = new NetworkCapabilities();
         netCap.addCapability(NET_CAPABILITY_INTERNET);
         netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
-        if (transportType > -1) {
+        if (transportType > TYPE_NONE) {
             netCap.addTransportType(transportType);
         }
+        return createNetworkRequest(type, netCap);
+    }
+
+    private NetworkRequest createNetworkRequest(
+            NetworkRequest.Type type, NetworkCapabilities netCap) {
         return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId(), type);
     }
 
@@ -1308,7 +1312,8 @@
 
         if (enable) {
             handleRegisterNetworkRequest(new NetworkRequestInfo(
-                    null, networkRequest, new Binder()));
+                    networkRequest, null, new Binder(),
+                    null /* attributionTags */));
         } else {
             handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID,
                     /* callOnUnavailable */ false);
@@ -1381,9 +1386,7 @@
     }
 
     private Network[] getVpnUnderlyingNetworks(int uid) {
-        synchronized (mVpns) {
-            if (mLockdownEnabled) return null;
-        }
+        if (mLockdownEnabled) return null;
         final NetworkAgentInfo nai = getVpnForUid(uid);
         if (nai != null) return nai.declaredUnderlyingNetworks;
         return null;
@@ -1468,11 +1471,9 @@
         if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) {
             networkInfo.setDetailedState(DetailedState.BLOCKED, null, null);
         }
-        synchronized (mVpns) {
-            if (mLockdownTracker != null) {
-                mLockdownTracker.augmentNetworkInfo(networkInfo);
-            }
-        }
+        networkInfo.setDetailedState(
+                getLegacyLockdownState(networkInfo.getDetailedState()),
+                "" /* reason */, null /* extraInfo */);
     }
 
     /**
@@ -1531,14 +1532,6 @@
         return nai.network;
     }
 
-    // Public because it's used by mLockdownTracker.
-    public NetworkInfo getActiveNetworkInfoUnfiltered() {
-        enforceAccessPermission();
-        final int uid = mDeps.getCallingUid();
-        NetworkState state = getUnfilteredActiveNetworkState(uid);
-        return state.networkInfo;
-    }
-
     @Override
     public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) {
         NetworkStack.checkNetworkStackPermission(mContext);
@@ -1643,7 +1636,7 @@
 
     @Override
     public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(
-                int userId, String callingPackageName) {
+                int userId, String callingPackageName, @Nullable String callingAttributionTag) {
         // The basic principle is: if an app's traffic could possibly go over a
         // network, without the app doing anything multinetwork-specific,
         // (hence, by "default"), then include that network's capabilities in
@@ -1674,7 +1667,8 @@
                 result.put(
                         nai.network,
                         createWithLocationInfoSanitizedIfNecessaryWhenParceled(
-                                nc, mDeps.getCallingUid(), callingPackageName));
+                                nc, mDeps.getCallingUid(), callingPackageName,
+                                callingAttributionTag));
             }
         }
 
@@ -1687,7 +1681,8 @@
                     result.put(
                             network,
                             createWithLocationInfoSanitizedIfNecessaryWhenParceled(
-                                    nc, mDeps.getCallingUid(), callingPackageName));
+                                    nc, mDeps.getCallingUid(), callingPackageName,
+                                    callingAttributionTag));
                 }
             }
         }
@@ -1762,12 +1757,13 @@
     }
 
     @Override
-    public NetworkCapabilities getNetworkCapabilities(Network network, String callingPackageName) {
+    public NetworkCapabilities getNetworkCapabilities(Network network, String callingPackageName,
+            @Nullable String callingAttributionTag) {
         mAppOpsManager.checkPackage(mDeps.getCallingUid(), callingPackageName);
         enforceAccessPermission();
         return createWithLocationInfoSanitizedIfNecessaryWhenParceled(
                 getNetworkCapabilitiesInternal(network),
-                mDeps.getCallingUid(), callingPackageName);
+                mDeps.getCallingUid(), callingPackageName, callingAttributionTag);
     }
 
     @VisibleForTesting
@@ -1786,11 +1782,12 @@
         return newNc;
     }
 
-    private boolean hasLocationPermission(int callerUid, @NonNull String callerPkgName) {
+    private boolean hasLocationPermission(int callerUid, @NonNull String callerPkgName,
+            @Nullable String callingAttributionTag) {
         final long token = Binder.clearCallingIdentity();
         try {
             return mLocationPermissionChecker.checkLocationPermission(
-                    callerPkgName, null /* featureId */, callerUid, null /* message */);
+                    callerPkgName, callingAttributionTag, callerUid, null /* message */);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -1799,7 +1796,8 @@
     @VisibleForTesting
     @Nullable
     NetworkCapabilities createWithLocationInfoSanitizedIfNecessaryWhenParceled(
-            @Nullable NetworkCapabilities nc, int callerUid, @NonNull String callerPkgName) {
+            @Nullable NetworkCapabilities nc, int callerUid, @NonNull String callerPkgName,
+            @Nullable String callingAttributionTag) {
         if (nc == null) {
             return null;
         }
@@ -1808,7 +1806,8 @@
         // Avoid doing location permission check if the transport info has no location sensitive
         // data.
         if (nc.getTransportInfo() != null && nc.getTransportInfo().hasLocationSensitiveFields()) {
-            hasLocationPermission = hasLocationPermission(callerUid, callerPkgName);
+            hasLocationPermission =
+                    hasLocationPermission(callerUid, callerPkgName, callingAttributionTag);
             newNc = new NetworkCapabilities(nc, hasLocationPermission);
         } else {
             newNc = new NetworkCapabilities(nc, false /* parcelLocationSensitiveFields */);
@@ -1825,7 +1824,8 @@
         }
         if (hasLocationPermission == null) {
             // Location permission not checked yet, check now for masking owner UID.
-            hasLocationPermission = hasLocationPermission(callerUid, callerPkgName);
+            hasLocationPermission =
+                    hasLocationPermission(callerUid, callerPkgName, callingAttributionTag);
         }
         // Reset owner uid if the app has no location permission.
         if (!hasLocationPermission) {
@@ -1883,7 +1883,8 @@
         final ArrayList<NetworkState> result = new ArrayList<>();
         for (Network network : getAllNetworks()) {
             final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
-            if (nai != null) {
+            // TODO: Consider include SUSPENDED networks.
+            if (nai != null && nai.networkInfo.isConnected()) {
                 // TODO (b/73321673) : NetworkState contains a copy of the
                 // NetworkCapabilities, which may contain UIDs of apps to which the
                 // network applies. Should the UIDs be cleared so as not to leak or
@@ -2152,22 +2153,6 @@
                 isBackgroundRestricted);
     }
 
-    /**
-     * Require that the caller is either in the same user or has appropriate permission to interact
-     * across users.
-     *
-     * @param userId Target user for whatever operation the current IPC is supposed to perform.
-     */
-    private void enforceCrossUserPermission(int userId) {
-        if (userId == UserHandle.getCallingUserId()) {
-            // Not a cross-user call.
-            return;
-        }
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                "ConnectivityService");
-    }
-
     private boolean checkAnyPermissionOf(String... permissions) {
         for (String permission : permissions) {
             if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
@@ -2248,12 +2233,6 @@
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid);
     }
 
-    private void enforceControlAlwaysOnVpnPermission() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CONTROL_ALWAYS_ON_VPN,
-                "ConnectivityService");
-    }
-
     private void enforceNetworkStackOrSettingsPermission() {
         enforceAnyPermissionOf(
                 android.Manifest.permission.NETWORK_SETTINGS,
@@ -2278,6 +2257,12 @@
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
 
+    private void enforceOemNetworkPreferencesPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE,
+                "ConnectivityService");
+    }
+
     private boolean checkNetworkStackPermission() {
         return checkAnyPermissionOf(
                 android.Manifest.permission.NETWORK_STACK,
@@ -2326,13 +2311,6 @@
     }
 
     private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
-        synchronized (mVpns) {
-            if (mLockdownTracker != null) {
-                info = new NetworkInfo(info);
-                mLockdownTracker.augmentNetworkInfo(info);
-            }
-        }
-
         Intent intent = new Intent(bcastType);
         intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info));
         intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
@@ -2426,10 +2404,6 @@
             }
         }
 
-        // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait
-        // for user to unlock device too.
-        updateLockdownVpn();
-
         // Create network requests for always-on networks.
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS));
     }
@@ -2439,7 +2413,7 @@
      */
     @Override
     public void registerNetworkActivityListener(@NonNull INetworkActivityListener l) {
-        // TODO: Replace network activity listener registry in ConnectivityManager from NMS to here
+        mNetworkActivityTracker.registerNetworkActivityListener(l);
     }
 
     /**
@@ -2447,7 +2421,7 @@
      */
     @Override
     public void unregisterNetworkActivityListener(@NonNull INetworkActivityListener l) {
-        // TODO: Replace network activity listener registry in ConnectivityManager from NMS to here
+        mNetworkActivityTracker.unregisterNetworkActivityListener(l);
     }
 
     /**
@@ -2455,8 +2429,7 @@
      */
     @Override
     public boolean isDefaultNetworkActive() {
-        // TODO: Replace isNetworkActive() in NMS.
-        return false;
+        return mNetworkActivityTracker.isDefaultNetworkActive();
     }
 
     /**
@@ -2593,13 +2566,13 @@
         if (!checkDumpPermission(mContext, TAG, pw)) return;
         if (asProto) return;
 
-        if (ArrayUtils.contains(args, DIAG_ARG)) {
+        if (CollectionUtils.contains(args, DIAG_ARG)) {
             dumpNetworkDiagnostics(pw);
             return;
-        } else if (ArrayUtils.contains(args, NETWORK_ARG)) {
+        } else if (CollectionUtils.contains(args, NETWORK_ARG)) {
             dumpNetworks(pw);
             return;
-        } else if (ArrayUtils.contains(args, REQUEST_ARG)) {
+        } else if (CollectionUtils.contains(args, REQUEST_ARG)) {
             dumpNetworkRequests(pw);
             return;
         }
@@ -2620,6 +2593,12 @@
         }
         pw.println();
 
+        pw.print("Current per-app default networks: ");
+        pw.increaseIndent();
+        dumpPerAppNetworkPreferences(pw);
+        pw.decreaseIndent();
+        pw.println();
+
         pw.println("Current Networks:");
         pw.increaseIndent();
         dumpNetworks(pw);
@@ -2664,7 +2643,7 @@
 
         pw.println();
 
-        if (ArrayUtils.contains(args, SHORT_ARG) == false) {
+        if (!CollectionUtils.contains(args, SHORT_ARG)) {
             pw.println();
             pw.println("mNetworkRequestInfoLogs (most recent first):");
             pw.increaseIndent();
@@ -2715,6 +2694,12 @@
         pw.increaseIndent();
         mPermissionMonitor.dump(pw);
         pw.decreaseIndent();
+
+        pw.println();
+        pw.println("Legacy network activity:");
+        pw.increaseIndent();
+        mNetworkActivityTracker.dump(pw);
+        pw.decreaseIndent();
     }
 
     private void dumpNetworks(IndentingPrintWriter pw) {
@@ -2740,6 +2725,40 @@
         }
     }
 
+    private void dumpPerAppNetworkPreferences(IndentingPrintWriter pw) {
+        pw.println("Per-App Network Preference:");
+        pw.increaseIndent();
+        if (0 == mOemNetworkPreferences.getNetworkPreferences().size()) {
+            pw.println("none");
+        } else {
+            pw.println(mOemNetworkPreferences.toString());
+        }
+        pw.decreaseIndent();
+
+        for (final NetworkRequestInfo defaultRequest : mDefaultNetworkRequests) {
+            if (mDefaultRequest == defaultRequest) {
+                continue;
+            }
+
+            final boolean isActive = null != defaultRequest.getSatisfier();
+            pw.println("Is per-app network active:");
+            pw.increaseIndent();
+            pw.println(isActive);
+            if (isActive) {
+                pw.println("Active network: " + defaultRequest.getSatisfier().network.netId);
+            }
+            pw.println("Tracked UIDs:");
+            pw.increaseIndent();
+            if (0 == defaultRequest.mRequests.size()) {
+                pw.println("none, this should never occur.");
+            } else {
+                pw.println(defaultRequest.mRequests.get(0).networkCapabilities.getUids());
+            }
+            pw.decreaseIndent();
+            pw.decreaseIndent();
+        }
+    }
+
     private void dumpNetworkRequests(IndentingPrintWriter pw) {
         for (NetworkRequestInfo nri : requestsSortedById()) {
             pw.println(nri.toString());
@@ -2873,7 +2892,15 @@
                         Log.wtf(TAG, "Non-virtual networks cannot have underlying networks");
                         break;
                     }
+
                     final List<Network> underlying = (List<Network>) arg.second;
+
+                    if (isLegacyLockdownNai(nai)
+                            && (underlying == null || underlying.size() != 1)) {
+                        Log.wtf(TAG, "Legacy lockdown VPN " + nai.toShortString()
+                                + " must have exactly one underlying network: " + underlying);
+                    }
+
                     final Network[] oldUnderlying = nai.declaredUnderlyingNetworks;
                     nai.declaredUnderlyingNetworks = (underlying != null)
                             ? underlying.toArray(new Network[0]) : null;
@@ -3482,7 +3509,6 @@
                     //  incorrect) behavior.
                     mNetworkActivityTracker.updateDataActivityTracking(
                             null /* newNetwork */, nai);
-                    notifyLockdownVpn(nai);
                     ensureNetworkTransitionWakelock(nai.toShortString());
                 }
             }
@@ -3572,29 +3598,38 @@
     }
 
     private void handleRegisterNetworkRequest(@NonNull final NetworkRequestInfo nri) {
+        handleRegisterNetworkRequests(Collections.singleton(nri));
+    }
+
+    private void handleRegisterNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
         ensureRunningOnConnectivityServiceThread();
-        mNetworkRequestInfoLogs.log("REGISTER " + nri);
-        for (final NetworkRequest req : nri.mRequests) {
-            mNetworkRequests.put(req, nri);
-            if (req.isListen()) {
-                for (final NetworkAgentInfo network : mNetworkAgentInfos) {
-                    if (req.networkCapabilities.hasSignalStrength()
-                            && network.satisfiesImmutableCapabilitiesOf(req)) {
-                        updateSignalStrengthThresholds(network, "REGISTER", req);
+        for (final NetworkRequestInfo nri : nris) {
+            mNetworkRequestInfoLogs.log("REGISTER " + nri);
+            for (final NetworkRequest req : nri.mRequests) {
+                mNetworkRequests.put(req, nri);
+                if (req.isListen()) {
+                    for (final NetworkAgentInfo network : mNetworkAgentInfos) {
+                        if (req.networkCapabilities.hasSignalStrength()
+                                && network.satisfiesImmutableCapabilitiesOf(req)) {
+                            updateSignalStrengthThresholds(network, "REGISTER", req);
+                        }
                     }
                 }
             }
         }
-        rematchAllNetworksAndRequests();
-        // If the nri is satisfied, return as its score has already been sent if needed.
-        if (nri.isBeingSatisfied()) {
-            return;
-        }
 
-        // As this request was not satisfied on rematch and thus never had any scores sent to the
-        // factories, send null now for each request of type REQUEST.
-        for (final NetworkRequest req : nri.mRequests) {
-            if (req.isRequest()) sendUpdatedScoreToFactories(req, null);
+        rematchAllNetworksAndRequests();
+        for (final NetworkRequestInfo nri : nris) {
+            // If the nri is satisfied, return as its score has already been sent if needed.
+            if (nri.isBeingSatisfied()) {
+                return;
+            }
+
+            // As this request was not satisfied on rematch and thus never had any scores sent to
+            // the factories, send null now for each request of type REQUEST.
+            for (final NetworkRequest req : nri.mRequests) {
+                if (req.isRequest()) sendUpdatedScoreToFactories(req, null);
+            }
         }
     }
 
@@ -3697,7 +3732,15 @@
 
     private NetworkRequestInfo getNriForAppRequest(
             NetworkRequest request, int callingUid, String requestedOperation) {
-        final NetworkRequestInfo nri = mNetworkRequests.get(request);
+        // Looking up the app passed param request in mRequests isn't possible since it may return
+        // null for a request managed by a per-app default. Therefore use getNriForAppRequest() to
+        // do the lookup since that will also find per-app default managed requests.
+        // Additionally, this lookup needs to be relatively fast (hence the lookup optimization)
+        // to avoid potential race conditions when validating a package->uid mapping when sending
+        // the callback on the very low-chance that an application shuts down prior to the callback
+        // being sent.
+        final NetworkRequestInfo nri = mNetworkRequests.get(request) != null
+                ? mNetworkRequests.get(request) : getNriForAppRequest(request);
 
         if (nri != null) {
             if (Process.SYSTEM_UID != callingUid && Process.NETWORK_STACK_UID != callingUid
@@ -3746,8 +3789,6 @@
         if (nri == null) {
             return;
         }
-        // handleReleaseNetworkRequest() paths don't apply to multilayer requests.
-        ensureNotMultilayerRequest(nri, "handleReleaseNetworkRequest");
         if (VDBG || (DBG && request.isRequest())) {
             log("releasing " + request + " (release request)");
         }
@@ -3759,7 +3800,6 @@
 
     private void handleRemoveNetworkRequest(@NonNull final NetworkRequestInfo nri) {
         ensureRunningOnConnectivityServiceThread();
-
         nri.unlinkDeathRecipient();
         for (final NetworkRequest req : nri.mRequests) {
             mNetworkRequests.remove(req);
@@ -3767,6 +3807,7 @@
                 removeListenRequestFromNetworks(req);
             }
         }
+        mDefaultNetworkRequests.remove(nri);
         mNetworkRequestCounter.decrementCount(nri.mUid);
         mNetworkRequestInfoLogs.log("RELEASE " + nri);
 
@@ -3781,6 +3822,16 @@
         cancelNpiRequests(nri);
     }
 
+    private void handleRemoveNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
+        for (final NetworkRequestInfo nri : nris) {
+            if (mDefaultRequest == nri) {
+                // Make sure we never remove the default request.
+                continue;
+            }
+            handleRemoveNetworkRequest(nri);
+        }
+    }
+
     private void cancelNpiRequests(@NonNull final NetworkRequestInfo nri) {
         for (final NetworkRequest req : nri.mRequests) {
             cancelNpiRequest(req);
@@ -4405,6 +4456,19 @@
                 case EVENT_SET_REQUIRE_VPN_FOR_UIDS:
                     handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj);
                     break;
+                case EVENT_SET_OEM_NETWORK_PREFERENCE:
+                    final Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener> arg =
+                            (Pair<OemNetworkPreferences,
+                                    IOnSetOemNetworkPreferenceListener>) msg.obj;
+                    try {
+                        handleSetOemNetworkPreference(arg.first, arg.second);
+                    } catch (RemoteException e) {
+                        loge("handleMessage.EVENT_SET_OEM_NETWORK_PREFERENCE failed", e);
+                    }
+                    break;
+                case EVENT_REPORT_NETWORK_ACTIVITY:
+                    mNetworkActivityTracker.handleReportNetworkActivity();
+                    break;
             }
         }
     }
@@ -4707,183 +4771,6 @@
     }
 
     /**
-     * Prepare for a VPN application.
-     * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
-     * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
-     *
-     * @param oldPackage Package name of the application which currently controls VPN, which will
-     *                   be replaced. If there is no such application, this should should either be
-     *                   {@code null} or {@link VpnConfig.LEGACY_VPN}.
-     * @param newPackage Package name of the application which should gain control of VPN, or
-     *                   {@code null} to disable.
-     * @param userId User for whom to prepare the new VPN.
-     *
-     * @hide
-     */
-    @Override
-    public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage,
-            int userId) {
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            Vpn vpn = mVpns.get(userId);
-            if (vpn != null) {
-                return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE);
-            } else {
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Set whether the VPN package has the ability to launch VPNs without user intervention. This
-     * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn}
-     * class. If the caller is not {@code userId}, {@link
-     * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
-     *
-     * @param packageName The package for which authorization state should change.
-     * @param userId User for whom {@code packageName} is installed.
-     * @param authorized {@code true} if this app should be able to start a VPN connection without
-     *     explicit user approval, {@code false} if not.
-     * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN
-     *     permissions should be granted. When unauthorizing an app, {@link
-     *     VpnManager.TYPE_VPN_NONE} should be used.
-     * @hide
-     */
-    @Override
-    public void setVpnPackageAuthorization(
-            String packageName, int userId, @VpnManager.VpnType int vpnType) {
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn != null) {
-                vpn.setPackageAuthorization(packageName, vpnType);
-            }
-        }
-    }
-
-    /**
-     * Configure a TUN interface and return its file descriptor. Parameters
-     * are encoded and opaque to this class. This method is used by VpnBuilder
-     * and not available in ConnectivityManager. Permissions are checked in
-     * Vpn class.
-     * @hide
-     */
-    @Override
-    public ParcelFileDescriptor establishVpn(VpnConfig config) {
-        int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            return mVpns.get(user).establish(config);
-        }
-    }
-
-    /**
-     * Stores the given VPN profile based on the provisioning package name.
-     *
-     * <p>If there is already a VPN profile stored for the provisioning package, this call will
-     * overwrite the profile.
-     *
-     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
-     * exclusively by the Settings app, and passed into the platform at startup time.
-     *
-     * @return {@code true} if user consent has already been granted, {@code false} otherwise.
-     * @hide
-     */
-    @Override
-    public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) {
-        final int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore);
-        }
-    }
-
-    /**
-     * Deletes the stored VPN profile for the provisioning package
-     *
-     * <p>If there are no profiles for the given package, this method will silently succeed.
-     *
-     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
-     * exclusively by the Settings app, and passed into the platform at startup time.
-     *
-     * @hide
-     */
-    @Override
-    public void deleteVpnProfile(@NonNull String packageName) {
-        final int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            mVpns.get(user).deleteVpnProfile(packageName, mKeyStore);
-        }
-    }
-
-    /**
-     * Starts the VPN based on the stored profile for the given package
-     *
-     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
-     * exclusively by the Settings app, and passed into the platform at startup time.
-     *
-     * @throws IllegalArgumentException if no profile was found for the given package name.
-     * @hide
-     */
-    @Override
-    public void startVpnProfile(@NonNull String packageName) {
-        final int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            mVpns.get(user).startVpnProfile(packageName, mKeyStore);
-        }
-    }
-
-    /**
-     * Stops the Platform VPN if the provided package is running one.
-     *
-     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
-     * exclusively by the Settings app, and passed into the platform at startup time.
-     *
-     * @hide
-     */
-    @Override
-    public void stopVpnProfile(@NonNull String packageName) {
-        final int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            mVpns.get(user).stopVpnProfile(packageName);
-        }
-    }
-
-    /**
-     * Start legacy VPN, controlling native daemons as needed. Creates a
-     * secondary thread to perform connection work, returning quickly.
-     */
-    @Override
-    public void startLegacyVpn(VpnProfile profile) {
-        int user = UserHandle.getUserId(mDeps.getCallingUid());
-        final LinkProperties egress = getActiveLinkProperties();
-        if (egress == null) {
-            throw new IllegalStateException("Missing active network connection");
-        }
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress);
-        }
-    }
-
-    /**
-     * Return the information of the ongoing legacy VPN. This method is used
-     * by VpnSettings and not available in ConnectivityManager. Permissions
-     * are checked in Vpn class.
-     */
-    @Override
-    public LegacyVpnInfo getLegacyVpnInfo(int userId) {
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            return mVpns.get(userId).getLegacyVpnInfo();
-        }
-    }
-
-    /**
      * Return the information of all ongoing VPNs.
      *
      * <p>This method is used to update NetworkStatsService.
@@ -4892,10 +4779,8 @@
      */
     private UnderlyingNetworkInfo[] getAllVpnInfo() {
         ensureRunningOnConnectivityServiceThread();
-        synchronized (mVpns) {
-            if (mLockdownEnabled) {
-                return new UnderlyingNetworkInfo[0];
-            }
+        if (mLockdownEnabled) {
+            return new UnderlyingNetworkInfo[0];
         }
         List<UnderlyingNetworkInfo> infoList = new ArrayList<>();
         for (NetworkAgentInfo nai : mNetworkAgentInfos) {
@@ -4925,7 +4810,7 @@
             }
         }
 
-        if (ArrayUtils.isEmpty(underlyingNetworks)) return null;
+        if (CollectionUtils.isEmpty(underlyingNetworks)) return null;
 
         List<String> interfaces = new ArrayList<>();
         for (Network network : underlyingNetworks) {
@@ -4951,25 +4836,6 @@
                 nai.linkProperties.getInterfaceName(), interfaces);
     }
 
-    /**
-     * Returns the information of the ongoing VPN for {@code userId}. This method is used by
-     * VpnDialogs and not available in ConnectivityManager.
-     * Permissions are checked in Vpn class.
-     * @hide
-     */
-    @Override
-    public VpnConfig getVpnConfig(int userId) {
-        enforceCrossUserPermission(userId);
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn != null) {
-                return vpn.getVpnConfig();
-            } else {
-                return null;
-            }
-        }
-    }
-
     // TODO This needs to be the default network that applies to the NAI.
     private Network[] underlyingNetworksOrDefault(final int ownerUid,
             Network[] underlyingNetworks) {
@@ -4988,7 +4854,7 @@
         if (!nai.supportsUnderlyingNetworks()) return false;
         final Network[] underlying = underlyingNetworksOrDefault(
                 nai.networkCapabilities.getOwnerUid(), nai.declaredUnderlyingNetworks);
-        return ArrayUtils.contains(underlying, network);
+        return CollectionUtils.contains(underlying, network);
     }
 
     /**
@@ -5057,195 +4923,54 @@
         mVpnBlockedUidRanges = newVpnBlockedUidRanges;
     }
 
-    private boolean isLockdownVpnEnabled() {
-        return mKeyStore.contains(Credentials.LOCKDOWN_VPN);
-    }
-
     @Override
-    public boolean updateLockdownVpn() {
-        // Allow the system UID for the system server and for Settings.
-        // Also, for unit tests, allow the process that ConnectivityService is running in.
-        if (mDeps.getCallingUid() != Process.SYSTEM_UID
-                && Binder.getCallingPid() != Process.myPid()) {
-            logw("Lockdown VPN only available to system process or AID_SYSTEM");
-            return false;
-        }
-
-        synchronized (mVpns) {
-            // Tear down existing lockdown if profile was removed
-            mLockdownEnabled = isLockdownVpnEnabled();
-            if (mLockdownEnabled) {
-                byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
-                if (profileTag == null) {
-                    loge("Lockdown VPN configured but cannot be read from keystore");
-                    return false;
-                }
-                String profileName = new String(profileTag);
-                final VpnProfile profile = VpnProfile.decode(
-                        profileName, mKeyStore.get(Credentials.VPN + profileName));
-                if (profile == null) {
-                    loge("Lockdown VPN configured invalid profile " + profileName);
-                    setLockdownTracker(null);
-                    return true;
-                }
-                int user = UserHandle.getUserId(mDeps.getCallingUid());
-                Vpn vpn = mVpns.get(user);
-                if (vpn == null) {
-                    logw("VPN for user " + user + " not ready yet. Skipping lockdown");
-                    return false;
-                }
-                setLockdownTracker(
-                        new LockdownVpnTracker(mContext, this, mHandler, mKeyStore, vpn,  profile));
-            } else {
-                setLockdownTracker(null);
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Internally set new {@link LockdownVpnTracker}, shutting down any existing
-     * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
-     */
-    @GuardedBy("mVpns")
-    private void setLockdownTracker(LockdownVpnTracker tracker) {
-        // Shutdown any existing tracker
-        final LockdownVpnTracker existing = mLockdownTracker;
-        // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the
-        // necessary onBlockedStatusChanged callbacks.
-        mLockdownTracker = null;
-        if (existing != null) {
-            existing.shutdown();
-        }
-
-        if (tracker != null) {
-            mLockdownTracker = tracker;
-            mLockdownTracker.init();
-        }
-    }
-
-    /**
-     * Throws if there is any currently running, always-on Legacy VPN.
-     *
-     * <p>The LockdownVpnTracker and mLockdownEnabled both track whether an always-on Legacy VPN is
-     * running across the entire system. Tracking for app-based VPNs is done on a per-user,
-     * per-package basis in Vpn.java
-     */
-    @GuardedBy("mVpns")
-    private void throwIfLockdownEnabled() {
-        if (mLockdownEnabled) {
-            throw new IllegalStateException("Unavailable in lockdown mode");
-        }
-    }
-
-    /**
-     * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform
-     * some setup and then call {@code establish()} to connect.
-     *
-     * @return {@code true} if the service was started, the service was already connected, or there
-     *         was no always-on VPN to start. {@code false} otherwise.
-     */
-    private boolean startAlwaysOnVpn(int userId) {
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                // Shouldn't happen as all code paths that point here should have checked the Vpn
-                // exists already.
-                Log.wtf(TAG, "User " + userId + " has no Vpn configuration");
-                return false;
-            }
-
-            return vpn.startAlwaysOnVpn(mKeyStore);
-        }
-    }
-
-    @Override
-    public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) {
+    public void setLegacyLockdownVpnEnabled(boolean enabled) {
         enforceSettingsPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                logw("User " + userId + " has no Vpn configuration");
-                return false;
-            }
-            return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
-        }
+        mHandler.post(() -> mLockdownEnabled = enabled);
     }
 
-    @Override
-    public boolean setAlwaysOnVpnPackage(
-            int userId, String packageName, boolean lockdown, List<String> lockdownWhitelist) {
-        enforceControlAlwaysOnVpnPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            // Can't set always-on VPN if legacy VPN is already in lockdown mode.
-            if (isLockdownVpnEnabled()) {
-                return false;
-            }
-
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                logw("User " + userId + " has no Vpn configuration");
-                return false;
-            }
-            if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist, mKeyStore)) {
-                return false;
-            }
-            if (!startAlwaysOnVpn(userId)) {
-                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
-                return false;
-            }
-        }
-        return true;
+    private boolean isLegacyLockdownNai(NetworkAgentInfo nai) {
+        return mLockdownEnabled
+                && getVpnType(nai) == VpnManager.TYPE_VPN_LEGACY
+                && nai.networkCapabilities.appliesToUid(Process.FIRST_APPLICATION_UID);
     }
 
-    @Override
-    public String getAlwaysOnVpnPackage(int userId) {
-        enforceControlAlwaysOnVpnPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                logw("User " + userId + " has no Vpn configuration");
-                return null;
-            }
-            return vpn.getAlwaysOnPackage();
+    private NetworkAgentInfo getLegacyLockdownNai() {
+        if (!mLockdownEnabled) {
+            return null;
         }
-    }
+        // The legacy lockdown VPN always only applies to userId 0.
+        final NetworkAgentInfo nai = getVpnForUid(Process.FIRST_APPLICATION_UID);
+        if (nai == null || !isLegacyLockdownNai(nai)) return null;
 
-    @Override
-    public boolean isVpnLockdownEnabled(int userId) {
-        enforceControlAlwaysOnVpnPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                logw("User " + userId + " has no Vpn configuration");
-                return false;
-            }
-            return vpn.getLockdown();
+        // The legacy lockdown VPN must always have exactly one underlying network.
+        // This code may run on any thread and declaredUnderlyingNetworks may change, so store it in
+        // a local variable. There is no need to make a copy because its contents cannot change.
+        final Network[] underlying = nai.declaredUnderlyingNetworks;
+        if (underlying == null ||  underlying.length != 1) {
+            return null;
         }
-    }
 
-    @Override
-    public List<String> getVpnLockdownWhitelist(int userId) {
-        enforceControlAlwaysOnVpnPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                logw("User " + userId + " has no Vpn configuration");
-                return null;
-            }
-            return vpn.getLockdownAllowlist();
+        // The legacy lockdown VPN always uses the default network.
+        // If the VPN's underlying network is no longer the current default network, it means that
+        // the default network has just switched, and the VPN is about to disconnect.
+        // Report that the VPN is not connected, so when the state of NetworkInfo objects
+        // overwritten by getLegacyLockdownState will be set to CONNECTING and not CONNECTED.
+        final NetworkAgentInfo defaultNetwork = getDefaultNetwork();
+        if (defaultNetwork == null || !defaultNetwork.network.equals(underlying[0])) {
+            return null;
         }
+
+        return nai;
+    };
+
+    private DetailedState getLegacyLockdownState(DetailedState origState) {
+        if (origState != DetailedState.CONNECTED) {
+            return origState;
+        }
+        return (mLockdownEnabled && getLegacyLockdownNai() == null)
+                ? DetailedState.CONNECTING
+                : DetailedState.CONNECTED;
     }
 
     @Override
@@ -5280,111 +5005,12 @@
         }
     }
 
-    private void onUserStarted(int userId) {
-        synchronized (mVpns) {
-            Vpn userVpn = mVpns.get(userId);
-            if (userVpn != null) {
-                loge("Starting user already has a VPN");
-                return;
-            }
-            userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore);
-            mVpns.put(userId, userVpn);
-            if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
-                updateLockdownVpn();
-            }
-        }
+    private void onUserAdded(UserHandle user) {
+        mPermissionMonitor.onUserAdded(user);
     }
 
-    private void onUserStopped(int userId) {
-        synchronized (mVpns) {
-            Vpn userVpn = mVpns.get(userId);
-            if (userVpn == null) {
-                loge("Stopped user has no VPN");
-                return;
-            }
-            userVpn.onUserStopped();
-            mVpns.delete(userId);
-        }
-    }
-
-    private void onUserAdded(int userId) {
-        mPermissionMonitor.onUserAdded(userId);
-        synchronized (mVpns) {
-            final int vpnsSize = mVpns.size();
-            for (int i = 0; i < vpnsSize; i++) {
-                Vpn vpn = mVpns.valueAt(i);
-                vpn.onUserAdded(userId);
-            }
-        }
-    }
-
-    private void onUserRemoved(int userId) {
-        mPermissionMonitor.onUserRemoved(userId);
-        synchronized (mVpns) {
-            final int vpnsSize = mVpns.size();
-            for (int i = 0; i < vpnsSize; i++) {
-                Vpn vpn = mVpns.valueAt(i);
-                vpn.onUserRemoved(userId);
-            }
-        }
-    }
-
-    private void onPackageReplaced(String packageName, int uid) {
-        if (TextUtils.isEmpty(packageName) || uid < 0) {
-            Log.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid);
-            return;
-        }
-        final int userId = UserHandle.getUserId(uid);
-        synchronized (mVpns) {
-            final Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                return;
-            }
-            // Legacy always-on VPN won't be affected since the package name is not set.
-            if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
-                log("Restarting always-on VPN package " + packageName + " for user "
-                        + userId);
-                vpn.startAlwaysOnVpn(mKeyStore);
-            }
-        }
-    }
-
-    private void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
-        if (TextUtils.isEmpty(packageName) || uid < 0) {
-            Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid);
-            return;
-        }
-
-        final int userId = UserHandle.getUserId(uid);
-        synchronized (mVpns) {
-            final Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                return;
-            }
-            // Legacy always-on VPN won't be affected since the package name is not set.
-            if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
-                log("Removing always-on VPN package " + packageName + " for user "
-                        + userId);
-                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
-            }
-        }
-    }
-
-    private void onUserUnlocked(int userId) {
-        synchronized (mVpns) {
-            // User present may be sent because of an unlock, which might mean an unlocked keystore.
-            if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
-                updateLockdownVpn();
-            } else {
-                startAlwaysOnVpn(userId);
-            }
-        }
-    }
-
-    private void onVpnLockdownReset() {
-        synchronized (mVpns) {
-            if (mLockdownTracker != null) mLockdownTracker.reset();
-        }
+    private void onUserRemoved(UserHandle user) {
+        mPermissionMonitor.onUserRemoved(user);
     }
 
     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@@ -5392,53 +5018,24 @@
         public void onReceive(Context context, Intent intent) {
             ensureRunningOnConnectivityServiceThread();
             final String action = intent.getAction();
-            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-            final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
-            final Uri packageData = intent.getData();
-            final String packageName =
-                    packageData != null ? packageData.getSchemeSpecificPart() : null;
+            final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
 
-            if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) {
-                onVpnLockdownReset();
+            // User should be filled for below intents, check the existence.
+            if (user == null) {
+                Log.wtf(TAG, intent.getAction() + " broadcast without EXTRA_USER");
+                return;
             }
 
-            // UserId should be filled for below intents, check the existence.
-            if (userId == UserHandle.USER_NULL) return;
-
-            if (Intent.ACTION_USER_STARTED.equals(action)) {
-                onUserStarted(userId);
-            } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
-                onUserStopped(userId);
-            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
-                onUserAdded(userId);
+            if (Intent.ACTION_USER_ADDED.equals(action)) {
+                onUserAdded(user);
             } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
-                onUserRemoved(userId);
-            } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
-                onUserUnlocked(userId);
-            } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
-                onPackageReplaced(packageName, uid);
-            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
-                final boolean isReplacing = intent.getBooleanExtra(
-                        Intent.EXTRA_REPLACING, false);
-                onPackageRemoved(packageName, uid, isReplacing);
-            } else {
+                onUserRemoved(user);
+            }  else {
                 Log.wtf(TAG, "received unexpected intent: " + action);
             }
         }
     };
 
-    private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            // Try creating lockdown tracker, since user present usually means
-            // unlocked keystore.
-            updateLockdownVpn();
-            // Use the same context that registered receiver before to unregister it. Because use
-            // different context to unregister receiver will cause exception.
-            context.unregisterReceiver(this);
-        }
-    };
-
     private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>();
     private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>();
 
@@ -5537,10 +5134,29 @@
 
         final PendingIntent mPendingIntent;
         boolean mPendingIntentSent;
+        @Nullable
+        final Messenger mMessenger;
+        @Nullable
         private final IBinder mBinder;
         final int mPid;
         final int mUid;
-        final Messenger messenger;
+        @Nullable
+        final String mCallingAttributionTag;
+        // In order to preserve the mapping of NetworkRequest-to-callback when apps register
+        // callbacks using a returned NetworkRequest, the original NetworkRequest needs to be
+        // maintained for keying off of. This is only a concern when the original nri
+        // mNetworkRequests changes which happens currently for apps that register callbacks to
+        // track the default network. In those cases, the nri is updated to have mNetworkRequests
+        // that match the per-app default nri that currently tracks the calling app's uid so that
+        // callbacks are fired at the appropriate time. When the callbacks fire,
+        // mNetworkRequestForCallback will be used so as to preserve the caller's mapping. When
+        // callbacks are updated to key off of an nri vs NetworkRequest, this stops being an issue.
+        // TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
+        @NonNull
+        private final NetworkRequest mNetworkRequestForCallback;
+        NetworkRequest getNetworkRequestForCallback() {
+            return mNetworkRequestForCallback;
+        }
 
         /**
          * Get the list of UIDs this nri applies to.
@@ -5554,27 +5170,45 @@
             return uids;
         }
 
-        NetworkRequestInfo(NetworkRequest r, PendingIntent pi) {
+        NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final PendingIntent pi,
+                @Nullable String callingAttributionTag) {
+            this(Collections.singletonList(r), r, pi, callingAttributionTag);
+        }
+
+        NetworkRequestInfo(@NonNull final List<NetworkRequest> r,
+                @NonNull final NetworkRequest requestForCallback, @Nullable final PendingIntent pi,
+                @Nullable String callingAttributionTag) {
+            ensureAllNetworkRequestsHaveType(r);
             mRequests = initializeRequests(r);
-            ensureAllNetworkRequestsHaveType(mRequests);
+            mNetworkRequestForCallback = requestForCallback;
             mPendingIntent = pi;
-            messenger = null;
+            mMessenger = null;
             mBinder = null;
             mPid = getCallingPid();
             mUid = mDeps.getCallingUid();
             mNetworkRequestCounter.incrementCountOrThrow(mUid);
+            mCallingAttributionTag = callingAttributionTag;
         }
 
-        NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder) {
+        NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final Messenger m,
+                @Nullable final IBinder binder, @Nullable String callingAttributionTag) {
+            this(Collections.singletonList(r), r, m, binder, callingAttributionTag);
+        }
+
+        NetworkRequestInfo(@NonNull final List<NetworkRequest> r,
+                @NonNull final NetworkRequest requestForCallback, @Nullable final Messenger m,
+                @Nullable final IBinder binder, @Nullable String callingAttributionTag) {
             super();
-            messenger = m;
+            ensureAllNetworkRequestsHaveType(r);
             mRequests = initializeRequests(r);
-            ensureAllNetworkRequestsHaveType(mRequests);
+            mNetworkRequestForCallback = requestForCallback;
+            mMessenger = m;
             mBinder = binder;
             mPid = getCallingPid();
             mUid = mDeps.getCallingUid();
             mPendingIntent = null;
             mNetworkRequestCounter.incrementCountOrThrow(mUid);
+            mCallingAttributionTag = callingAttributionTag;
 
             try {
                 mBinder.linkToDeath(this, 0);
@@ -5583,8 +5217,26 @@
             }
         }
 
-        NetworkRequestInfo(NetworkRequest r) {
-            this(r, null);
+        NetworkRequestInfo(@NonNull final NetworkRequestInfo nri,
+                @NonNull final List<NetworkRequest> r) {
+            super();
+            ensureAllNetworkRequestsHaveType(r);
+            mRequests = initializeRequests(r);
+            mNetworkRequestForCallback = nri.getNetworkRequestForCallback();
+            mMessenger = nri.mMessenger;
+            mBinder = nri.mBinder;
+            mPid = nri.mPid;
+            mUid = nri.mUid;
+            mPendingIntent = nri.mPendingIntent;
+            mCallingAttributionTag = nri.mCallingAttributionTag;
+        }
+
+        NetworkRequestInfo(@NonNull final NetworkRequest r) {
+            this(Collections.singletonList(r));
+        }
+
+        NetworkRequestInfo(@NonNull final List<NetworkRequest> r) {
+            this(r, r.get(0), null /* pi */, null /* callingAttributionTag */);
         }
 
         // True if this NRI is being satisfied. It also accounts for if the nri has its satisifer
@@ -5598,9 +5250,10 @@
             return mRequests.size() > 1;
         }
 
-        private List<NetworkRequest> initializeRequests(NetworkRequest r) {
-            final ArrayList<NetworkRequest> tempRequests = new ArrayList<>();
-            tempRequests.add(new NetworkRequest(r));
+        private List<NetworkRequest> initializeRequests(List<NetworkRequest> r) {
+            // Creating a defensive copy to prevent the sender from modifying the list being
+            // reflected in the return value of this method.
+            final List<NetworkRequest> tempRequests = new ArrayList<>(r);
             return Collections.unmodifiableList(tempRequests);
         }
 
@@ -5665,8 +5318,7 @@
                 }
             }
         }
-        // TODO: use NetworkStackUtils.convertToIntArray after moving it
-        return ArrayUtils.convertToIntArray(new ArrayList<>(thresholds));
+        return CollectionUtils.toIntArray(new ArrayList<>(thresholds));
     }
 
     private void updateSignalStrengthThresholds(
@@ -5729,6 +5381,7 @@
                 throw new SecurityException("Insufficient permissions to specify legacy type");
             }
         }
+        final NetworkCapabilities defaultNc = mDefaultRequest.mRequests.get(0).networkCapabilities;
         final int callingUid = mDeps.getCallingUid();
         final NetworkRequest.Type reqType;
         try {
@@ -5739,11 +5392,16 @@
         switch (reqType) {
             case TRACK_DEFAULT:
                 // If the request type is TRACK_DEFAULT, the passed {@code networkCapabilities}
-                // is unused and will be replaced by the one from the default network request.
-                // This allows callers to keep track of the system default network.
-                networkCapabilities = createDefaultNetworkCapabilitiesForUid(callingUid);
+                // is unused and will be replaced by ones appropriate for the caller.
+                // This allows callers to keep track of the default network for their app.
+                networkCapabilities = copyDefaultNetworkCapabilitiesForUid(
+                        defaultNc, callingUid, callingPackageName);
                 enforceAccessPermission();
                 break;
+            case TRACK_SYSTEM_DEFAULT:
+                enforceSettingsPermission();
+                networkCapabilities = new NetworkCapabilities(defaultNc);
+                break;
             case BACKGROUND_REQUEST:
                 enforceNetworkStackOrSettingsPermission();
                 // Fall-through since other checks are the same with normal requests.
@@ -5762,6 +5420,7 @@
         ensureRequestableCapabilities(networkCapabilities);
         ensureSufficientPermissionsForRequest(networkCapabilities,
                 Binder.getCallingPid(), callingUid, callingPackageName);
+
         // Set the UID range for this request to the single UID of the requester, or to an empty
         // set of UIDs if the caller has the appropriate permission and UIDs have not been set.
         // This will overwrite any allowed UIDs in the requested capabilities. Though there
@@ -5775,11 +5434,23 @@
         }
         ensureValid(networkCapabilities);
 
-        NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
+        final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
                 nextNetworkRequestId(), reqType);
-        NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder);
+        final NetworkRequestInfo nri = getNriToRegister(
+                networkRequest, messenger, binder, callingAttributionTag);
         if (DBG) log("requestNetwork for " + nri);
 
+        // For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were
+        // copied from the default request above. (This is necessary to ensure, for example, that
+        // the callback does not leak sensitive information to unprivileged apps.) Check that the
+        // changes don't alter request matching.
+        if (reqType == NetworkRequest.Type.TRACK_SYSTEM_DEFAULT &&
+                (!networkCapabilities.equalRequestableCapabilities(defaultNc))) {
+            throw new IllegalStateException(
+                    "TRACK_SYSTEM_DEFAULT capabilities don't match default request: "
+                    + networkCapabilities + " vs. " + defaultNc);
+        }
+
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri));
         if (timeoutMs > 0) {
             mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NETWORK_REQUEST,
@@ -5788,6 +5459,30 @@
         return networkRequest;
     }
 
+    /**
+     * Return the nri to be used when registering a network request. Specifically, this is used with
+     * requests registered to track the default request. If there is currently a per-app default
+     * tracking the app requestor, then we need to create a version of this nri that mirrors that of
+     * the tracking per-app default so that callbacks are sent to the app requestor appropriately.
+     * @param nr the network request for the nri.
+     * @param msgr the messenger for the nri.
+     * @param binder the binder for the nri.
+     * @param callingAttributionTag the calling attribution tag for the nri.
+     * @return the nri to register.
+     */
+    private NetworkRequestInfo getNriToRegister(@NonNull final NetworkRequest nr,
+            @Nullable final Messenger msgr, @Nullable final IBinder binder,
+            @Nullable String callingAttributionTag) {
+        final List<NetworkRequest> requests;
+        if (NetworkRequest.Type.TRACK_DEFAULT == nr.type) {
+            requests = copyDefaultNetworkRequestsForUid(
+                    nr.getRequestorUid(), nr.getRequestorPackageName());
+        } else {
+            requests = Collections.singletonList(nr);
+        }
+        return new NetworkRequestInfo(requests, nr, msgr, binder, callingAttributionTag);
+    }
+
     private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
             String callingPackageName, String callingAttributionTag) {
         if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
@@ -5866,7 +5561,8 @@
 
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
                 nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
-        NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation);
+        NetworkRequestInfo nri =
+                new NetworkRequestInfo(networkRequest, operation, callingAttributionTag);
         if (DBG) log("pendingRequest for " + nri);
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT,
                 nri));
@@ -5910,7 +5606,8 @@
 
     @Override
     public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities,
-            Messenger messenger, IBinder binder, @NonNull String callingPackageName) {
+            Messenger messenger, IBinder binder, @NonNull String callingPackageName,
+            @Nullable String callingAttributionTag) {
         final int callingUid = mDeps.getCallingUid();
         if (!hasWifiNetworkListenPermission(networkCapabilities)) {
             enforceAccessPermission();
@@ -5930,7 +5627,8 @@
 
         NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
                 NetworkRequest.Type.LISTEN);
-        NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder);
+        NetworkRequestInfo nri =
+                new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag);
         if (VDBG) log("listenForNetwork for " + nri);
 
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
@@ -5939,7 +5637,8 @@
 
     @Override
     public void pendingListenForNetwork(NetworkCapabilities networkCapabilities,
-            PendingIntent operation, @NonNull String callingPackageName) {
+            PendingIntent operation, @NonNull String callingPackageName,
+            @Nullable String callingAttributionTag) {
         Objects.requireNonNull(operation, "PendingIntent cannot be null.");
         final int callingUid = mDeps.getCallingUid();
         if (!hasWifiNetworkListenPermission(networkCapabilities)) {
@@ -5953,7 +5652,8 @@
 
         NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
                 NetworkRequest.Type.LISTEN);
-        NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation);
+        NetworkRequestInfo nri =
+                new NetworkRequestInfo(networkRequest, operation, callingAttributionTag);
         if (VDBG) log("pendingListenForNetwork for " + nri);
 
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
@@ -6056,19 +5756,122 @@
     @GuardedBy("mBlockedAppUids")
     private final HashSet<Integer> mBlockedAppUids = new HashSet<>();
 
+    // Current OEM network preferences.
+    @NonNull
+    private OemNetworkPreferences mOemNetworkPreferences =
+            new OemNetworkPreferences.Builder().build();
+
     // The always-on request for an Internet-capable network that apps without a specific default
     // fall back to.
+    @VisibleForTesting
     @NonNull
-    private final NetworkRequestInfo mDefaultRequest;
+    final NetworkRequestInfo mDefaultRequest;
     // Collection of NetworkRequestInfo's used for default networks.
+    @VisibleForTesting
     @NonNull
-    private final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>();
+    final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>();
 
     private boolean isPerAppDefaultRequest(@NonNull final NetworkRequestInfo nri) {
         return (mDefaultNetworkRequests.contains(nri) && mDefaultRequest != nri);
     }
 
     /**
+     * Return the default network request currently tracking the given uid.
+     * @param uid the uid to check.
+     * @return the NetworkRequestInfo tracking the given uid.
+     */
+    @NonNull
+    private NetworkRequestInfo getDefaultRequestTrackingUid(@NonNull final int uid) {
+        for (final NetworkRequestInfo nri : mDefaultNetworkRequests) {
+            if (nri == mDefaultRequest) {
+                continue;
+            }
+            // Checking the first request is sufficient as only multilayer requests will have more
+            // than one request and for multilayer, all requests will track the same uids.
+            if (nri.mRequests.get(0).networkCapabilities.appliesToUid(uid)) {
+                return nri;
+            }
+        }
+        return mDefaultRequest;
+    }
+
+    /**
+     * Get a copy of the network requests of the default request that is currently tracking the
+     * given uid.
+     * @param requestorUid the uid to check the default for.
+     * @param requestorPackageName the requestor's package name.
+     * @return a copy of the default's NetworkRequest that is tracking the given uid.
+     */
+    @NonNull
+    private List<NetworkRequest> copyDefaultNetworkRequestsForUid(
+            @NonNull final int requestorUid, @NonNull final String requestorPackageName) {
+        return copyNetworkRequestsForUid(
+                getDefaultRequestTrackingUid(requestorUid).mRequests,
+                requestorUid, requestorPackageName);
+    }
+
+    /**
+     * Copy the given nri's NetworkRequest collection.
+     * @param requestsToCopy the NetworkRequest collection to be copied.
+     * @param requestorUid the uid to set on the copied collection.
+     * @param requestorPackageName the package name to set on the copied collection.
+     * @return the copied NetworkRequest collection.
+     */
+    @NonNull
+    private List<NetworkRequest> copyNetworkRequestsForUid(
+            @NonNull final List<NetworkRequest> requestsToCopy, @NonNull final int requestorUid,
+            @NonNull final String requestorPackageName) {
+        final List<NetworkRequest> requests = new ArrayList<>();
+        for (final NetworkRequest nr : requestsToCopy) {
+            requests.add(new NetworkRequest(copyDefaultNetworkCapabilitiesForUid(
+                            nr.networkCapabilities, requestorUid, requestorPackageName),
+                    nr.legacyType, nextNetworkRequestId(), nr.type));
+        }
+        return requests;
+    }
+
+    @NonNull
+    private NetworkCapabilities copyDefaultNetworkCapabilitiesForUid(
+            @NonNull final NetworkCapabilities netCapToCopy, @NonNull final int requestorUid,
+            @NonNull final String requestorPackageName) {
+        final NetworkCapabilities netCap = new NetworkCapabilities(netCapToCopy);
+        netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
+        netCap.setSingleUid(requestorUid);
+        netCap.setUids(new ArraySet<>());
+        restrictRequestUidsForCallerAndSetRequestorInfo(
+                netCap, requestorUid, requestorPackageName);
+        return netCap;
+    }
+
+    /**
+     * Get the nri that is currently being tracked for callbacks by per-app defaults.
+     * @param nr the network request to check for equality against.
+     * @return the nri if one exists, null otherwise.
+     */
+    @Nullable
+    private NetworkRequestInfo getNriForAppRequest(@NonNull final NetworkRequest nr) {
+        for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
+            if (nri.getNetworkRequestForCallback().equals(nr)) {
+                return nri;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Check if an nri is currently being managed by per-app default networking.
+     * @param nri the nri to check.
+     * @return true if this nri is currently being managed by per-app default networking.
+     */
+    private boolean isPerAppTrackedNri(@NonNull final NetworkRequestInfo nri) {
+        // nri.mRequests.get(0) is only different from the original request filed in
+        // nri.getNetworkRequestForCallback() if nri.mRequests was changed by per-app default
+        // functionality therefore if these two don't match, it means this particular nri is
+        // currently being managed by a per-app default.
+        return nri.getNetworkRequestForCallback() != nri.mRequests.get(0);
+    }
+
+    /**
      * Determine if an nri is a managed default request that disallows default networking.
      * @param nri the request to evaluate
      * @return true if device-default networking is disallowed
@@ -6365,20 +6168,18 @@
                     Math.max(naData.getRefreshTimeMillis(), apiData.getRefreshTimeMillis()));
         }
 
-        // Prioritize the user portal URL from the network agent.
-        if (apiData.getUserPortalUrl() != null && (naData.getUserPortalUrl() == null
-                || TextUtils.isEmpty(naData.getUserPortalUrl().toSafeString()))) {
-            captivePortalBuilder.setUserPortalUrl(apiData.getUserPortalUrl());
+        // Prioritize the user portal URL from the network agent if the source is authenticated.
+        if (apiData.getUserPortalUrl() != null && naData.getUserPortalUrlSource()
+                != CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) {
+            captivePortalBuilder.setUserPortalUrl(apiData.getUserPortalUrl(),
+                    apiData.getUserPortalUrlSource());
         }
-        // Prioritize the venue information URL from the network agent.
-        if (apiData.getVenueInfoUrl() != null && (naData.getVenueInfoUrl() == null
-                || TextUtils.isEmpty(naData.getVenueInfoUrl().toSafeString()))) {
-            captivePortalBuilder.setVenueInfoUrl(apiData.getVenueInfoUrl());
-
-            // Note that venue friendly name can only come from the network agent because it is not
-            // in use in RFC8908. However, if using the Capport venue URL, make sure that the
-            // friendly name is not set from the network agent.
-            captivePortalBuilder.setVenueFriendlyName(null);
+        // Prioritize the venue information URL from the network agent if the source is
+        // authenticated.
+        if (apiData.getVenueInfoUrl() != null && naData.getVenueInfoUrlSource()
+                != CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) {
+            captivePortalBuilder.setVenueInfoUrl(apiData.getVenueInfoUrl(),
+                    apiData.getVenueInfoUrlSource());
         }
         return captivePortalBuilder.build();
     }
@@ -6636,7 +6437,7 @@
             @NonNull NetworkCapabilities agentCaps, @NonNull NetworkCapabilities newNc) {
         underlyingNetworks = underlyingNetworksOrDefault(
                 agentCaps.getOwnerUid(), underlyingNetworks);
-        int[] transportTypes = agentCaps.getTransportTypes();
+        long transportTypes = BitUtils.packBits(agentCaps.getTransportTypes());
         int downKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
         int upKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
         // metered if any underlying is metered, or originally declared metered by the agent.
@@ -6655,7 +6456,7 @@
                 final NetworkCapabilities underlyingCaps = underlying.networkCapabilities;
                 hadUnderlyingNetworks = true;
                 for (int underlyingType : underlyingCaps.getTransportTypes()) {
-                    transportTypes = ArrayUtils.appendInt(transportTypes, underlyingType);
+                    transportTypes |= 1L << underlyingType;
                 }
 
                 // Merge capabilities of this underlying network. For bandwidth, assume the
@@ -6686,7 +6487,7 @@
             suspended = false;
         }
 
-        newNc.setTransportTypes(transportTypes);
+        newNc.setTransportTypes(BitUtils.unpackBits(transportTypes));
         newNc.setLinkDownstreamBandwidthKbps(downKbps);
         newNc.setLinkUpstreamBandwidthKbps(upKbps);
         newNc.setCapability(NET_CAPABILITY_NOT_METERED, !metered);
@@ -7141,20 +6942,16 @@
     private void callCallbackForRequest(@NonNull final NetworkRequestInfo nri,
             @NonNull final NetworkAgentInfo networkAgent, final int notificationType,
             final int arg1) {
-        if (nri.messenger == null) {
+        if (nri.mMessenger == null) {
             // Default request has no msgr. Also prevents callbacks from being invoked for
             // NetworkRequestInfos registered with ConnectivityDiagnostics requests. Those callbacks
             // are Type.LISTEN, but should not have NetworkCallbacks invoked.
             return;
         }
         Bundle bundle = new Bundle();
-        // In the case of multi-layer NRIs, the first request is not necessarily the one that
-        // is satisfied. This is vexing, but the ConnectivityManager code that receives this
-        // callback is only using the request as a token to identify the callback, so it doesn't
-        // matter too much at this point as long as the callback can be found.
         // TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
         // TODO: check if defensive copies of data is needed.
-        final NetworkRequest nrForCallback = new NetworkRequest(nri.mRequests.get(0));
+        final NetworkRequest nrForCallback = nri.getNetworkRequestForCallback();
         putParcelable(bundle, nrForCallback);
         Message msg = Message.obtain();
         if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) {
@@ -7168,7 +6965,8 @@
                 putParcelable(
                         bundle,
                         createWithLocationInfoSanitizedIfNecessaryWhenParceled(
-                                nc, nri.mUid, nrForCallback.getRequestorPackageName()));
+                                nc, nri.mUid, nrForCallback.getRequestorPackageName(),
+                                nri.mCallingAttributionTag));
                 putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
                         networkAgent.linkProperties, nri.mPid, nri.mUid));
                 // For this notification, arg1 contains the blocked status.
@@ -7187,7 +6985,8 @@
                 putParcelable(
                         bundle,
                         createWithLocationInfoSanitizedIfNecessaryWhenParceled(
-                                netCap, nri.mUid, nrForCallback.getRequestorPackageName()));
+                                netCap, nri.mUid, nrForCallback.getRequestorPackageName(),
+                                nri.mCallingAttributionTag));
                 break;
             }
             case ConnectivityManager.CALLBACK_IP_CHANGED: {
@@ -7208,7 +7007,7 @@
                 String notification = ConnectivityManager.getCallbackName(notificationType);
                 log("sending notification " + notification + " for " + nrForCallback);
             }
-            nri.messenger.send(msg);
+            nri.mMessenger.send(msg);
         } catch (RemoteException e) {
             // may occur naturally in the race of binder death.
             loge("RemoteException caught trying to send a callback msg for " + nrForCallback);
@@ -7297,7 +7096,6 @@
             mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork);
         }
         mNetworkActivityTracker.updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork);
-        notifyLockdownVpn(newDefaultNetwork);
         handleApplyDefaultProxy(null != newDefaultNetwork
                 ? newDefaultNetwork.linkProperties.getHttpProxy() : null);
         updateTcpBufferSizes(null != newDefaultNetwork
@@ -7755,12 +7553,6 @@
                 mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0;
                 mLegacyTypeTracker.add(
                         newDefaultNetwork.networkInfo.getType(), newDefaultNetwork);
-                // If the legacy VPN is connected, notifyLockdownVpn may end up sending a broadcast
-                // to reflect the NetworkInfo of this new network. This broadcast has to be sent
-                // after the disconnect broadcasts above, but before the broadcasts sent by the
-                // legacy type tracker below.
-                // TODO : refactor this, it's too complex
-                notifyLockdownVpn(newDefaultNetwork);
             }
         }
 
@@ -7818,18 +7610,6 @@
         sendInetConditionBroadcast(nai.networkInfo);
     }
 
-    private void notifyLockdownVpn(NetworkAgentInfo nai) {
-        synchronized (mVpns) {
-            if (mLockdownTracker != null) {
-                if (nai != null && nai.isVPN()) {
-                    mLockdownTracker.onVpnStateChanged(nai.networkInfo);
-                } else {
-                    mLockdownTracker.onNetworkInfoChanged();
-                }
-            }
-        }
-    }
-
     @NonNull
     private NetworkInfo mixInInfo(@NonNull final NetworkAgentInfo nai, @NonNull NetworkInfo info) {
         final NetworkInfo newInfo = new NetworkInfo(info);
@@ -7868,7 +7648,6 @@
             oldInfo = networkAgent.networkInfo;
             networkAgent.networkInfo = newInfo;
         }
-        notifyLockdownVpn(networkAgent);
 
         if (DBG) {
             log(networkAgent.toShortString() + " EVENT_NETWORK_INFO_CHANGED, going from "
@@ -8169,34 +7948,6 @@
     }
 
     @Override
-    public boolean addVpnAddress(String address, int prefixLength) {
-        int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            return mVpns.get(user).addAddress(address, prefixLength);
-        }
-    }
-
-    @Override
-    public boolean removeVpnAddress(String address, int prefixLength) {
-        int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            return mVpns.get(user).removeAddress(address, prefixLength);
-        }
-    }
-
-    @Override
-    public boolean setUnderlyingNetworksForVpn(Network[] networks) {
-        int user = UserHandle.getUserId(mDeps.getCallingUid());
-        final boolean success;
-        synchronized (mVpns) {
-            success = mVpns.get(user).setUnderlyingNetworks(networks);
-        }
-        return success;
-    }
-
-    @Override
     public String getCaptivePortalServerUrl() {
         enforceNetworkStackOrSettingsPermission();
         String settingUrl = mContext.getResources().getString(
@@ -8275,8 +8026,6 @@
             return;
         }
 
-        final int userId = UserHandle.getCallingUserId();
-
         final long token = Binder.clearCallingIdentity();
         try {
             final IpMemoryStore ipMemoryStore = IpMemoryStore.getMemoryStore(mContext);
@@ -8288,44 +8037,6 @@
         // Turn airplane mode off
         setAirplaneMode(false);
 
-        if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
-            // Remove always-on package
-            synchronized (mVpns) {
-                final String alwaysOnPackage = getAlwaysOnVpnPackage(userId);
-                if (alwaysOnPackage != null) {
-                    setAlwaysOnVpnPackage(userId, null, false, null);
-                    setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE);
-                }
-
-                // Turn Always-on VPN off
-                if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
-                    final long ident = Binder.clearCallingIdentity();
-                    try {
-                        mKeyStore.delete(Credentials.LOCKDOWN_VPN);
-                        mLockdownEnabled = false;
-                        setLockdownTracker(null);
-                    } finally {
-                        Binder.restoreCallingIdentity(ident);
-                    }
-                }
-
-                // Turn VPN off
-                VpnConfig vpnConfig = getVpnConfig(userId);
-                if (vpnConfig != null) {
-                    if (vpnConfig.legacy) {
-                        prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId);
-                    } else {
-                        // Prevent this app (packagename = vpnConfig.user) from initiating
-                        // VPN connections in the future without user intervention.
-                        setVpnPackageAuthorization(
-                                vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE);
-
-                        prepareVpn(null, VpnConfig.LEGACY_VPN, userId);
-                    }
-                }
-            }
-        }
-
         // restore private DNS settings to default mode (opportunistic)
         if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_PRIVATE_DNS)) {
             Settings.Global.putString(mContext.getContentResolver(),
@@ -8417,41 +8128,11 @@
         }
     }
 
-    @GuardedBy("mVpns")
-    private Vpn getVpnIfOwner() {
-        return getVpnIfOwner(mDeps.getCallingUid());
-    }
-
-    // TODO: stop calling into Vpn.java and get this information from data in this class.
-    @GuardedBy("mVpns")
-    private Vpn getVpnIfOwner(int uid) {
-        final int user = UserHandle.getUserId(uid);
-
-        final Vpn vpn = mVpns.get(user);
-        if (vpn == null) {
-            return null;
-        } else {
-            final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo();
-            return (info == null || info.ownerUid != uid) ? null : vpn;
-        }
-    }
-
-    /**
-     * Caller either needs to be an active VPN, or hold the NETWORK_STACK permission
-     * for testing.
-     */
-    private Vpn enforceActiveVpnOrNetworkStackPermission() {
-        if (checkNetworkStackPermission()) {
-            return null;
-        }
-        synchronized (mVpns) {
-            Vpn vpn = getVpnIfOwner();
-            if (vpn != null) {
-                return vpn;
-            }
-        }
-        throw new SecurityException("App must either be an active VPN or have the NETWORK_STACK "
-                + "permission");
+    private @VpnManager.VpnType int getVpnType(@Nullable NetworkAgentInfo vpn) {
+        if (vpn == null) return VpnManager.TYPE_VPN_NONE;
+        final TransportInfo ti = vpn.networkCapabilities.getTransportInfo();
+        if (!(ti instanceof VpnTransportInfo)) return VpnManager.TYPE_VPN_NONE;
+        return ((VpnTransportInfo) ti).type;
     }
 
     /**
@@ -8461,14 +8142,6 @@
      * connection is not found.
      */
     public int getConnectionOwnerUid(ConnectionInfo connectionInfo) {
-        final Vpn vpn = enforceActiveVpnOrNetworkStackPermission();
-
-        // Only VpnService based VPNs should be able to get this information.
-        if (vpn != null && vpn.getActiveAppVpnType() != VpnManager.TYPE_VPN_SERVICE) {
-            throw new SecurityException(
-                    "getConnectionOwnerUid() not allowed for non-VpnService VPNs");
-        }
-
         if (connectionInfo.protocol != IPPROTO_TCP && connectionInfo.protocol != IPPROTO_UDP) {
             throw new IllegalArgumentException("Unsupported protocol " + connectionInfo.protocol);
         }
@@ -8476,30 +8149,21 @@
         final int uid = mDeps.getConnectionOwnerUid(connectionInfo.protocol,
                 connectionInfo.local, connectionInfo.remote);
 
-        /* Filter out Uids not associated with the VPN. */
-        if (vpn != null && !vpn.appliesToUid(uid)) {
+        if (uid == INVALID_UID) return uid;  // Not found.
+
+        // Connection owner UIDs are visible only to the network stack and to the VpnService-based
+        // VPN, if any, that applies to the UID that owns the connection.
+        if (checkNetworkStackPermission()) return uid;
+
+        final NetworkAgentInfo vpn = getVpnForUid(uid);
+        if (vpn == null || getVpnType(vpn) != VpnManager.TYPE_VPN_SERVICE
+                || vpn.networkCapabilities.getOwnerUid() != Binder.getCallingUid()) {
             return INVALID_UID;
         }
 
         return uid;
     }
 
-    @Override
-    public boolean isCallerCurrentAlwaysOnVpnApp() {
-        synchronized (mVpns) {
-            Vpn vpn = getVpnIfOwner();
-            return vpn != null && vpn.getAlwaysOn();
-        }
-    }
-
-    @Override
-    public boolean isCallerCurrentAlwaysOnVpnLockdownApp() {
-        synchronized (mVpns) {
-            Vpn vpn = getVpnIfOwner();
-            return vpn != null && vpn.getLockdown();
-        }
-    }
-
     /**
      * Returns a IBinder to a TestNetworkService. Will be lazily created as needed.
      *
@@ -8888,14 +8552,14 @@
         for (NetworkAgentInfo virtual : mNetworkAgentInfos) {
             if (virtual.supportsUnderlyingNetworks()
                     && virtual.networkCapabilities.getOwnerUid() == callbackUid
-                    && ArrayUtils.contains(virtual.declaredUnderlyingNetworks, nai.network)) {
+                    && CollectionUtils.contains(virtual.declaredUnderlyingNetworks, nai.network)) {
                 return true;
             }
         }
 
         // Administrator UIDs also contains the Owner UID
         final int[] administratorUids = nai.networkCapabilities.getAdministratorUids();
-        return ArrayUtils.contains(administratorUids, callbackUid);
+        return CollectionUtils.contains(administratorUids, callbackUid);
     }
 
     @Override
@@ -8991,13 +8655,35 @@
      * changes.
      */
     private static final class LegacyNetworkActivityTracker {
+        private static final int NO_UID = -1;
         private final Context mContext;
+        private final INetd mNetd;
         private final INetworkManagementService mNMS;
+        private final RemoteCallbackList<INetworkActivityListener> mNetworkActivityListeners =
+                new RemoteCallbackList<>();
+        // Indicate the current system default network activity is active or not.
+        @GuardedBy("mActiveIdleTimers")
+        private boolean mNetworkActive;
+        @GuardedBy("mActiveIdleTimers")
+        private final ArrayMap<String, IdleTimerParams> mActiveIdleTimers = new ArrayMap();
+        private final Handler mHandler;
 
-        LegacyNetworkActivityTracker(@NonNull Context context,
-                @NonNull INetworkManagementService nms) {
+        private class IdleTimerParams {
+            public final int timeout;
+            public final int transportType;
+
+            IdleTimerParams(int timeout, int transport) {
+                this.timeout = timeout;
+                this.transportType = transport;
+            }
+        }
+
+        LegacyNetworkActivityTracker(@NonNull Context context, @NonNull Handler handler,
+                @NonNull INetworkManagementService nms, @NonNull INetd netd) {
             mContext = context;
             mNMS = nms;
+            mNetd = netd;
+            mHandler = handler;
             try {
                 mNMS.registerObserver(mDataActivityObserver);
             } catch (RemoteException e) {
@@ -9013,9 +8699,50 @@
                             long tsNanos, int uid) {
                         sendDataActivityBroadcast(transportTypeToLegacyType(transportType), active,
                                 tsNanos);
+                        synchronized (mActiveIdleTimers) {
+                            mNetworkActive = active;
+                            // If there are no idle timers, it means that system is not monitoring
+                            // activity, so the system default network for those default network
+                            // unspecified apps is always considered active.
+                            //
+                            // TODO: If the mActiveIdleTimers is empty, netd will actually not send
+                            // any network activity change event. Whenever this event is received,
+                            // the mActiveIdleTimers should be always not empty. The legacy behavior
+                            // is no-op. Remove to refer to mNetworkActive only.
+                            if (mNetworkActive || mActiveIdleTimers.isEmpty()) {
+                                mHandler.sendMessage(
+                                        mHandler.obtainMessage(EVENT_REPORT_NETWORK_ACTIVITY));
+                            }
+                        }
                     }
                 };
 
+        // The network activity should only be updated from ConnectivityService handler thread
+        // when mActiveIdleTimers lock is held.
+        @GuardedBy("mActiveIdleTimers")
+        private void reportNetworkActive() {
+            final int length = mNetworkActivityListeners.beginBroadcast();
+            if (DDBG) log("reportNetworkActive, notify " + length + " listeners");
+            try {
+                for (int i = 0; i < length; i++) {
+                    try {
+                        mNetworkActivityListeners.getBroadcastItem(i).onNetworkActive();
+                    } catch (RemoteException | RuntimeException e) {
+                        loge("Fail to send network activie to listener " + e);
+                    }
+                }
+            } finally {
+                mNetworkActivityListeners.finishBroadcast();
+            }
+        }
+
+        @GuardedBy("mActiveIdleTimers")
+        public void handleReportNetworkActivity() {
+            synchronized (mActiveIdleTimers) {
+                reportNetworkActive();
+            }
+        }
+
         // This is deprecated and only to support legacy use cases.
         private int transportTypeToLegacyType(int type) {
             switch (type) {
@@ -9080,10 +8807,17 @@
                 return; // do not track any other networks
             }
 
+            updateRadioPowerState(true /* isActive */, type);
+
             if (timeout > 0 && iface != null) {
                 try {
-                    // TODO: Access INetd directly instead of NMS
-                    mNMS.addIdleTimer(iface, timeout, type);
+                    synchronized (mActiveIdleTimers) {
+                        // Networks start up.
+                        mNetworkActive = true;
+                        mActiveIdleTimers.put(iface, new IdleTimerParams(timeout, type));
+                        mNetd.idletimerAddInterface(iface, timeout, Integer.toString(type));
+                        reportNetworkActive();
+                    }
                 } catch (Exception e) {
                     // You shall not crash!
                     loge("Exception in setupDataActivityTracking " + e);
@@ -9098,16 +8832,28 @@
             final String iface = networkAgent.linkProperties.getInterfaceName();
             final NetworkCapabilities caps = networkAgent.networkCapabilities;
 
-            if (iface != null && (caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
-                    || caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))) {
-                try {
-                    // the call fails silently if no idle timer setup for this interface
-                    // TODO: Access INetd directly instead of NMS
-                    mNMS.removeIdleTimer(iface);
-                } catch (Exception e) {
-                    // You shall not crash!
-                    loge("Exception in removeDataActivityTracking " + e);
+            if (iface == null) return;
+
+            final int type;
+            if (caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+                type = NetworkCapabilities.TRANSPORT_CELLULAR;
+            } else if (caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+                type = NetworkCapabilities.TRANSPORT_WIFI;
+            } else {
+                return; // do not track any other networks
+            }
+
+            try {
+                updateRadioPowerState(false /* isActive */, type);
+                synchronized (mActiveIdleTimers) {
+                    final IdleTimerParams params = mActiveIdleTimers.remove(iface);
+                    // The call fails silently if no idle timer setup for this interface
+                    mNetd.idletimerRemoveInterface(iface, params.timeout,
+                            Integer.toString(params.transportType));
                 }
+            } catch (Exception e) {
+                // You shall not crash!
+                loge("Exception in removeDataActivityTracking " + e);
             }
         }
 
@@ -9123,6 +8869,53 @@
                 removeDataActivityTracking(oldNetwork);
             }
         }
+
+        private void updateRadioPowerState(boolean isActive, int transportType) {
+            final BatteryStatsManager bs = mContext.getSystemService(BatteryStatsManager.class);
+            switch (transportType) {
+                case NetworkCapabilities.TRANSPORT_CELLULAR:
+                    bs.reportMobileRadioPowerState(isActive, NO_UID);
+                    break;
+                case NetworkCapabilities.TRANSPORT_WIFI:
+                    bs.reportWifiRadioPowerState(isActive, NO_UID);
+                    break;
+                default:
+                    logw("Untracked transport type:" + transportType);
+            }
+        }
+
+        public boolean isDefaultNetworkActive() {
+            synchronized (mActiveIdleTimers) {
+                // If there are no idle timers, it means that system is not monitoring activity,
+                // so the default network is always considered active.
+                //
+                // TODO : Distinguish between the cases where mActiveIdleTimers is empty because
+                // tracking is disabled (negative idle timer value configured), or no active default
+                // network. In the latter case, this reports active but it should report inactive.
+                return mNetworkActive || mActiveIdleTimers.isEmpty();
+            }
+        }
+
+        public void registerNetworkActivityListener(@NonNull INetworkActivityListener l) {
+            mNetworkActivityListeners.register(l);
+        }
+
+        public void unregisterNetworkActivityListener(@NonNull INetworkActivityListener l) {
+            mNetworkActivityListeners.unregister(l);
+        }
+
+        public void dump(IndentingPrintWriter pw) {
+            synchronized (mActiveIdleTimers) {
+                pw.print("mNetworkActive="); pw.println(mNetworkActive);
+                pw.println("Idle timers:");
+                for (HashMap.Entry<String, IdleTimerParams> ent : mActiveIdleTimers.entrySet()) {
+                    pw.print("  "); pw.print(ent.getKey()); pw.println(":");
+                    final IdleTimerParams params = ent.getValue();
+                    pw.print("    timeout="); pw.print(params.timeout);
+                    pw.print(" type="); pw.println(params.transportType);
+                }
+            }
+        }
     }
 
     /**
@@ -9175,9 +8968,274 @@
         mQosCallbackTracker.unregisterCallback(callback);
     }
 
+    private void enforceAutomotiveDevice() {
+        final boolean isAutomotiveDevice =
+                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+        if (!isAutomotiveDevice) {
+            throw new UnsupportedOperationException(
+                    "setOemNetworkPreference() is only available on automotive devices.");
+        }
+    }
+
+    /**
+     * Used by automotive devices to set the network preferences used to direct traffic at an
+     * application level as per the given OemNetworkPreferences. An example use-case would be an
+     * automotive OEM wanting to provide connectivity for applications critical to the usage of a
+     * vehicle via a particular network.
+     *
+     * Calling this will overwrite the existing preference.
+     *
+     * @param preference {@link OemNetworkPreferences} The application network preference to be set.
+     * @param listener {@link ConnectivityManager.OnSetOemNetworkPreferenceListener} Listener used
+     * to communicate completion of setOemNetworkPreference();
+     */
     @Override
-    public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) {
-        // TODO http://b/176495594 track multiple default networks with networkPreferences
-        if (DBG) log("setOemNetworkPreference() called with: " + preference.toString());
+    public void setOemNetworkPreference(
+            @NonNull final OemNetworkPreferences preference,
+            @Nullable final IOnSetOemNetworkPreferenceListener listener) {
+
+        enforceAutomotiveDevice();
+        enforceOemNetworkPreferencesPermission();
+
+        Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
+        validateOemNetworkPreferences(preference);
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_OEM_NETWORK_PREFERENCE,
+                new Pair<>(preference, listener)));
+    }
+
+    private void validateOemNetworkPreferences(@NonNull OemNetworkPreferences preference) {
+        for (@OemNetworkPreferences.OemNetworkPreference final int pref
+                : preference.getNetworkPreferences().values()) {
+            if (OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED == pref) {
+                final String msg = "OEM_NETWORK_PREFERENCE_UNINITIALIZED is an invalid value.";
+                throw new IllegalArgumentException(msg);
+            }
+        }
+    }
+
+    private void handleSetOemNetworkPreference(
+            @NonNull final OemNetworkPreferences preference,
+            @NonNull final IOnSetOemNetworkPreferenceListener listener) throws RemoteException {
+        Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
+        if (DBG) {
+            log("set OEM network preferences :" + preference.toString());
+        }
+        final ArraySet<NetworkRequestInfo> nris =
+                new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference);
+        updateDefaultNetworksForOemNetworkPreference(nris);
+        mOemNetworkPreferences = preference;
+        // TODO http://b/176496396 persist data to shared preferences.
+
+        if (null != listener) {
+            listener.onComplete();
+        }
+    }
+
+    private void updateDefaultNetworksForOemNetworkPreference(
+            @NonNull final Set<NetworkRequestInfo> nris) {
+        // Pass in a defensive copy as this collection will be updated on remove.
+        handleRemoveNetworkRequests(new ArraySet<>(mDefaultNetworkRequests));
+        addPerAppDefaultNetworkRequests(nris);
+    }
+
+    private void addPerAppDefaultNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
+        ensureRunningOnConnectivityServiceThread();
+        mDefaultNetworkRequests.addAll(nris);
+        final ArraySet<NetworkRequestInfo> perAppCallbackRequestsToUpdate =
+                getPerAppCallbackRequestsToUpdate();
+        handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate);
+        final ArraySet<NetworkRequestInfo> nrisToRegister = new ArraySet<>(nris);
+        nrisToRegister.addAll(
+                createPerAppCallbackRequestsToRegister(perAppCallbackRequestsToUpdate));
+        handleRegisterNetworkRequests(nrisToRegister);
+    }
+
+    /**
+     * All current requests that are tracking the default network need to be assessed as to whether
+     * or not the current set of per-application default requests will be changing their default
+     * network. If so, those requests will need to be updated so that they will send callbacks for
+     * default network changes at the appropriate time. Additionally, those requests tracking the
+     * default that were previously updated by this flow will need to be reassessed.
+     * @return the nris which will need to be updated.
+     */
+    private ArraySet<NetworkRequestInfo> getPerAppCallbackRequestsToUpdate() {
+        final ArraySet<NetworkRequestInfo> defaultCallbackRequests = new ArraySet<>();
+        // Get the distinct nris to check since for multilayer requests, it is possible to have the
+        // same nri in the map's values for each of its NetworkRequest objects.
+        final ArraySet<NetworkRequestInfo> nris = new ArraySet<>(mNetworkRequests.values());
+        for (final NetworkRequestInfo nri : nris) {
+            // Include this nri if it is currently being tracked.
+            if (isPerAppTrackedNri(nri)) {
+                defaultCallbackRequests.add(nri);
+                continue;
+            }
+            // We only track callbacks for requests tracking the default.
+            if (NetworkRequest.Type.TRACK_DEFAULT != nri.mRequests.get(0).type) {
+                continue;
+            }
+            // Include this nri if it will be tracked by the new per-app default requests.
+            final boolean isNriGoingToBeTracked =
+                    getDefaultRequestTrackingUid(nri.mUid) != mDefaultRequest;
+            if (isNriGoingToBeTracked) {
+                defaultCallbackRequests.add(nri);
+            }
+        }
+        return defaultCallbackRequests;
+    }
+
+    /**
+     * Create nris for those network requests that are currently tracking the default network that
+     * are being controlled by a per-application default.
+     * @param perAppCallbackRequestsForUpdate the baseline network requests to be used as the
+     * foundation when creating the nri. Important items include the calling uid's original
+     * NetworkRequest to be used when mapping callbacks as well as the caller's uid and name. These
+     * requests are assumed to have already been validated as needing to be updated.
+     * @return the Set of nris to use when registering network requests.
+     */
+    private ArraySet<NetworkRequestInfo> createPerAppCallbackRequestsToRegister(
+            @NonNull final ArraySet<NetworkRequestInfo> perAppCallbackRequestsForUpdate) {
+        final ArraySet<NetworkRequestInfo> callbackRequestsToRegister = new ArraySet<>();
+        for (final NetworkRequestInfo callbackRequest : perAppCallbackRequestsForUpdate) {
+            final NetworkRequestInfo trackingNri =
+                    getDefaultRequestTrackingUid(callbackRequest.mUid);
+
+            // If this nri is not being tracked, the change it back to an untracked nri.
+            if (trackingNri == mDefaultRequest) {
+                callbackRequestsToRegister.add(new NetworkRequestInfo(
+                        callbackRequest,
+                        Collections.singletonList(callbackRequest.getNetworkRequestForCallback())));
+                continue;
+            }
+
+            final String requestorPackageName =
+                    callbackRequest.mRequests.get(0).getRequestorPackageName();
+            callbackRequestsToRegister.add(new NetworkRequestInfo(
+                    callbackRequest,
+                    copyNetworkRequestsForUid(
+                            trackingNri.mRequests, callbackRequest.mUid, requestorPackageName)));
+        }
+        return callbackRequestsToRegister;
+    }
+
+    /**
+     * Class used to generate {@link NetworkRequestInfo} based off of {@link OemNetworkPreferences}.
+     */
+    @VisibleForTesting
+    final class OemNetworkRequestFactory {
+        ArraySet<NetworkRequestInfo> createNrisFromOemNetworkPreferences(
+                @NonNull final OemNetworkPreferences preference) {
+            final ArraySet<NetworkRequestInfo> nris = new ArraySet<>();
+            final SparseArray<Set<Integer>> uids =
+                    createUidsFromOemNetworkPreferences(preference);
+            for (int i = 0; i < uids.size(); i++) {
+                final int key = uids.keyAt(i);
+                final Set<Integer> value = uids.valueAt(i);
+                final NetworkRequestInfo nri = createNriFromOemNetworkPreferences(key, value);
+                // No need to add an nri without any requests.
+                if (0 == nri.mRequests.size()) {
+                    continue;
+                }
+                nris.add(nri);
+            }
+
+            return nris;
+        }
+
+        private SparseArray<Set<Integer>> createUidsFromOemNetworkPreferences(
+                @NonNull final OemNetworkPreferences preference) {
+            final SparseArray<Set<Integer>> uids = new SparseArray<>();
+            final PackageManager pm = mContext.getPackageManager();
+            for (final Map.Entry<String, Integer> entry :
+                    preference.getNetworkPreferences().entrySet()) {
+                @OemNetworkPreferences.OemNetworkPreference final int pref = entry.getValue();
+                try {
+                    final int uid = pm.getApplicationInfo(entry.getKey(), 0).uid;
+                    if (!uids.contains(pref)) {
+                        uids.put(pref, new ArraySet<>());
+                    }
+                    uids.get(pref).add(uid);
+                } catch (PackageManager.NameNotFoundException e) {
+                    // Although this may seem like an error scenario, it is ok that uninstalled
+                    // 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;
+                }
+            }
+            return uids;
+        }
+
+        private NetworkRequestInfo createNriFromOemNetworkPreferences(
+                @OemNetworkPreferences.OemNetworkPreference final int preference,
+                @NonNull final Set<Integer> uids) {
+            final List<NetworkRequest> requests = new ArrayList<>();
+            // Requests will ultimately be evaluated by order of insertion therefore it matters.
+            switch (preference) {
+                case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID:
+                    requests.add(createUnmeteredNetworkRequest());
+                    requests.add(createOemPaidNetworkRequest());
+                    requests.add(createDefaultRequest());
+                    break;
+                case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK:
+                    requests.add(createUnmeteredNetworkRequest());
+                    requests.add(createOemPaidNetworkRequest());
+                    break;
+                case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY:
+                    requests.add(createOemPaidNetworkRequest());
+                    break;
+                case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY:
+                    requests.add(createOemPrivateNetworkRequest());
+                    break;
+                default:
+                    // This should never happen.
+                    throw new IllegalArgumentException("createNriFromOemNetworkPreferences()"
+                            + " called with invalid preference of " + preference);
+            }
+
+            setOemNetworkRequestUids(requests, uids);
+            return new NetworkRequestInfo(requests);
+        }
+
+        private NetworkRequest createUnmeteredNetworkRequest() {
+            final NetworkCapabilities netcap = createDefaultPerAppNetCap()
+                    .addCapability(NET_CAPABILITY_NOT_METERED)
+                    .addCapability(NET_CAPABILITY_VALIDATED);
+            return createNetworkRequest(NetworkRequest.Type.LISTEN, netcap);
+        }
+
+        private NetworkRequest createOemPaidNetworkRequest() {
+            // NET_CAPABILITY_OEM_PAID is a restricted capability.
+            final NetworkCapabilities netcap = createDefaultPerAppNetCap()
+                    .addCapability(NET_CAPABILITY_OEM_PAID)
+                    .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+            return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap);
+        }
+
+        private NetworkRequest createOemPrivateNetworkRequest() {
+            // NET_CAPABILITY_OEM_PRIVATE is a restricted capability.
+            final NetworkCapabilities netcap = createDefaultPerAppNetCap()
+                    .addCapability(NET_CAPABILITY_OEM_PRIVATE)
+                    .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+            return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap);
+        }
+
+        private NetworkCapabilities createDefaultPerAppNetCap() {
+            final NetworkCapabilities netCap = new NetworkCapabilities();
+            netCap.addCapability(NET_CAPABILITY_INTERNET);
+            netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
+            return netCap;
+        }
+
+        private void setOemNetworkRequestUids(@NonNull final List<NetworkRequest> requests,
+                @NonNull final Set<Integer> uids) {
+            final Set<UidRange> ranges = new ArraySet<>();
+            for (final int uid : uids) {
+                ranges.add(new UidRange(uid, uid));
+            }
+            for (final NetworkRequest req : requests) {
+                req.networkCapabilities.setUids(ranges);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index 46c49e7..641287f 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity;
 
+import static com.android.net.module.util.CollectionUtils.contains;
+
 import android.annotation.NonNull;
 import android.net.ConnectivityManager;
 import android.net.IDnsResolver;
@@ -33,7 +35,6 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 import com.android.net.module.util.NetworkStackConstants;
 import com.android.server.net.BaseNetworkObserver;
 
@@ -117,8 +118,8 @@
     @VisibleForTesting
     protected static boolean requiresClat(NetworkAgentInfo nai) {
         // TODO: migrate to NetworkCapabilities.TRANSPORT_*.
-        final boolean supported = ArrayUtils.contains(NETWORK_TYPES, nai.networkInfo.getType());
-        final boolean connected = ArrayUtils.contains(NETWORK_STATES, nai.networkInfo.getState());
+        final boolean supported = contains(NETWORK_TYPES, nai.networkInfo.getType());
+        final boolean connected = contains(NETWORK_STATES, nai.networkInfo.getState());
 
         // Only run clat on networks that have a global IPv6 address and don't have a native IPv4
         // address.
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index bff1a5c..4cf5274 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -717,8 +717,9 @@
                 mNumBackgroundNetworkRequests += delta;
                 break;
 
-            case TRACK_DEFAULT:
             case LISTEN:
+            case TRACK_DEFAULT:
+            case TRACK_SYSTEM_DEFAULT:
                 break;
 
             case NONE:
@@ -898,7 +899,7 @@
                     ? networkAgentConfig.subscriberId : null;
             return new NetworkState(new NetworkInfo(networkInfo),
                     new LinkProperties(linkProperties),
-                    new NetworkCapabilities(networkCapabilities), network, subscriberId, null);
+                    new NetworkCapabilities(networkCapabilities), network, subscriberId);
         }
     }
 
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 3d71b0a..508739f 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -79,7 +79,6 @@
     // server.
     public static final String NOTIFICATION_CHANNEL_NETWORK_STATUS = "NETWORK_STATUS";
     public static final String NOTIFICATION_CHANNEL_NETWORK_ALERTS = "NETWORK_ALERTS";
-    public static final String NOTIFICATION_CHANNEL_VPN = "VPN";
 
     // The context is for the current user (system server)
     private final Context mContext;
@@ -161,13 +160,20 @@
         if (nai != null) {
             transportType = approximateTransportType(nai);
             final String extraInfo = nai.networkInfo.getExtraInfo();
-            name = TextUtils.isEmpty(extraInfo) ? nai.networkCapabilities.getSsid() : extraInfo;
+            if (nai.linkProperties != null && nai.linkProperties.getCaptivePortalData() != null
+                    && !TextUtils.isEmpty(nai.linkProperties.getCaptivePortalData()
+                    .getVenueFriendlyName())) {
+                name = nai.linkProperties.getCaptivePortalData().getVenueFriendlyName();
+            } else {
+                name = TextUtils.isEmpty(extraInfo)
+                        ? WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()) : extraInfo;
+            }
             // Only notify for Internet-capable networks.
             if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return;
         } else {
             // Legacy notifications.
             transportType = TRANSPORT_CELLULAR;
-            name = null;
+            name = "";
         }
 
         // Clear any previous notification with lower priority, otherwise return. http://b/63676954.
@@ -193,35 +199,30 @@
         final CharSequence details;
         int icon = getIcon(transportType);
         if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
-            title = r.getString(R.string.wifi_no_internet,
-                    WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+            title = r.getString(R.string.wifi_no_internet, name);
             details = r.getString(R.string.wifi_no_internet_detailed);
         } else if (notifyType == NotificationType.PRIVATE_DNS_BROKEN) {
             if (transportType == TRANSPORT_CELLULAR) {
                 title = r.getString(R.string.mobile_no_internet);
             } else if (transportType == TRANSPORT_WIFI) {
-                title = r.getString(R.string.wifi_no_internet,
-                        WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+                title = r.getString(R.string.wifi_no_internet, name);
             } else {
                 title = r.getString(R.string.other_networks_no_internet);
             }
             details = r.getString(R.string.private_dns_broken_detailed);
         } else if (notifyType == NotificationType.PARTIAL_CONNECTIVITY
                 && transportType == TRANSPORT_WIFI) {
-            title = r.getString(R.string.network_partial_connectivity,
-                    WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+            title = r.getString(R.string.network_partial_connectivity, name);
             details = r.getString(R.string.network_partial_connectivity_detailed);
         } else if (notifyType == NotificationType.LOST_INTERNET &&
                 transportType == TRANSPORT_WIFI) {
-            title = r.getString(R.string.wifi_no_internet,
-                    WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+            title = r.getString(R.string.wifi_no_internet, name);
             details = r.getString(R.string.wifi_no_internet_detailed);
         } else if (notifyType == NotificationType.SIGN_IN) {
             switch (transportType) {
                 case TRANSPORT_WIFI:
                     title = r.getString(R.string.wifi_available_sign_in, 0);
-                    details = r.getString(R.string.network_available_sign_in_detailed,
-                            WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+                    details = r.getString(R.string.network_available_sign_in_detailed, name);
                     break;
                 case TRANSPORT_CELLULAR:
                     title = r.getString(R.string.network_available_sign_in, 0);
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index 8d21f6f..9411e33 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -28,6 +28,8 @@
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
 
+import static com.android.net.module.util.CollectionUtils.toIntArray;
+
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -40,23 +42,21 @@
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
+import android.os.SystemConfigManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.system.OsConstants;
-import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.CollectionUtils;
 import com.android.server.LocalServices;
-import com.android.server.SystemConfig;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -80,12 +80,12 @@
 
     private final PackageManager mPackageManager;
     private final UserManager mUserManager;
+    private final SystemConfigManager mSystemConfigManager;
     private final INetd mNetd;
     private final Dependencies mDeps;
 
-    // Values are User IDs.
     @GuardedBy("this")
-    private final Set<Integer> mUsers = new HashSet<>();
+    private final Set<UserHandle> mUsers = new HashSet<>();
 
     // Keys are app uids. Values are true for SYSTEM permission and false for NETWORK permission.
     @GuardedBy("this")
@@ -124,6 +124,7 @@
             @NonNull final Dependencies deps) {
         mPackageManager = context.getPackageManager();
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        mSystemConfigManager = context.getSystemService(SystemConfigManager.class);
         mNetd = netd;
         mDeps = deps;
     }
@@ -173,25 +174,20 @@
             netdPermsUids.put(uid, netdPermsUids.get(uid) | otherNetdPerms);
         }
 
-        final List<UserHandle> users = mUserManager.getUserHandles(true /* excludeDying */);
-        for (UserHandle user : users) {
-            mUsers.add(user.getIdentifier());
-        }
+        mUsers.addAll(mUserManager.getUserHandles(true /* excludeDying */));
 
-        final SparseArray<ArraySet<String>> systemPermission =
-                SystemConfig.getInstance().getSystemPermissions();
-        for (int i = 0; i < systemPermission.size(); i++) {
-            ArraySet<String> perms = systemPermission.valueAt(i);
-            int uid = systemPermission.keyAt(i);
-            int netdPermission = 0;
-            // Get the uids of native services that have UPDATE_DEVICE_STATS or INTERNET permission.
-            if (perms != null) {
-                netdPermission |= perms.contains(UPDATE_DEVICE_STATS)
-                        ? INetd.PERMISSION_UPDATE_DEVICE_STATS : 0;
-                netdPermission |= perms.contains(INTERNET)
-                        ? INetd.PERMISSION_INTERNET : 0;
+        final SparseArray<String> netdPermToSystemPerm = new SparseArray<>();
+        netdPermToSystemPerm.put(INetd.PERMISSION_INTERNET, INTERNET);
+        netdPermToSystemPerm.put(INetd.PERMISSION_UPDATE_DEVICE_STATS, UPDATE_DEVICE_STATS);
+        for (int i = 0; i < netdPermToSystemPerm.size(); i++) {
+            final int netdPermission = netdPermToSystemPerm.keyAt(i);
+            final String systemPermission = netdPermToSystemPerm.valueAt(i);
+            final int[] hasPermissionUids =
+                    mSystemConfigManager.getSystemPermissionUids(systemPermission);
+            for (int j = 0; j < hasPermissionUids.length; j++) {
+                final int uid = hasPermissionUids[j];
+                netdPermsUids.put(uid, netdPermsUids.get(uid) | netdPermission);
             }
-            netdPermsUids.put(uid, netdPermsUids.get(uid) | netdPermission);
         }
         log("Users: " + mUsers.size() + ", Apps: " + mApps.size());
         update(mUsers, mApps, true);
@@ -208,7 +204,7 @@
         if (app.requestedPermissions == null || app.requestedPermissionsFlags == null) {
             return false;
         }
-        final int index = ArrayUtils.indexOf(app.requestedPermissions, permission);
+        final int index = CollectionUtils.indexOf(app.requestedPermissions, permission);
         if (index < 0 || index >= app.requestedPermissionsFlags.length) return false;
         return (app.requestedPermissionsFlags[index] & REQUESTED_PERMISSION_GRANTED) != 0;
     }
@@ -250,25 +246,15 @@
         return mApps.containsKey(uid);
     }
 
-    private int[] toIntArray(Collection<Integer> list) {
-        int[] array = new int[list.size()];
-        int i = 0;
-        for (Integer item : list) {
-            array[i++] = item;
-        }
-        return array;
-    }
-
-    private void update(Set<Integer> users, Map<Integer, Boolean> apps, boolean add) {
+    private void update(Set<UserHandle> users, Map<Integer, Boolean> apps, boolean add) {
         List<Integer> network = new ArrayList<>();
         List<Integer> system = new ArrayList<>();
         for (Entry<Integer, Boolean> app : apps.entrySet()) {
             List<Integer> list = app.getValue() ? system : network;
-            for (int user : users) {
-                final UserHandle handle = UserHandle.of(user);
-                if (handle == null) continue;
+            for (UserHandle user : users) {
+                if (user == null) continue;
 
-                list.add(UserHandle.getUid(handle, app.getKey()));
+                list.add(UserHandle.getUid(user, app.getKey()));
             }
         }
         try {
@@ -291,14 +277,10 @@
      *
      * @hide
      */
-    public synchronized void onUserAdded(int user) {
-        if (user < 0) {
-            loge("Invalid user in onUserAdded: " + user);
-            return;
-        }
+    public synchronized void onUserAdded(@NonNull UserHandle user) {
         mUsers.add(user);
 
-        Set<Integer> users = new HashSet<>();
+        Set<UserHandle> users = new HashSet<>();
         users.add(user);
         update(users, mApps, true);
     }
@@ -310,14 +292,10 @@
      *
      * @hide
      */
-    public synchronized void onUserRemoved(int user) {
-        if (user < 0) {
-            loge("Invalid user in onUserRemoved: " + user);
-            return;
-        }
+    public synchronized void onUserRemoved(@NonNull UserHandle user) {
         mUsers.remove(user);
 
-        Set<Integer> users = new HashSet<>();
+        Set<UserHandle> users = new HashSet<>();
         users.add(user);
         update(users, mApps, false);
     }
@@ -675,23 +653,23 @@
             if (allPermissionAppIds.size() != 0) {
                 mNetd.trafficSetNetPermForUids(
                         INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS,
-                        ArrayUtils.convertToIntArray(allPermissionAppIds));
+                        toIntArray(allPermissionAppIds));
             }
             if (internetPermissionAppIds.size() != 0) {
                 mNetd.trafficSetNetPermForUids(INetd.PERMISSION_INTERNET,
-                        ArrayUtils.convertToIntArray(internetPermissionAppIds));
+                        toIntArray(internetPermissionAppIds));
             }
             if (updateStatsPermissionAppIds.size() != 0) {
                 mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UPDATE_DEVICE_STATS,
-                        ArrayUtils.convertToIntArray(updateStatsPermissionAppIds));
+                        toIntArray(updateStatsPermissionAppIds));
             }
             if (noPermissionAppIds.size() != 0) {
                 mNetd.trafficSetNetPermForUids(INetd.PERMISSION_NONE,
-                        ArrayUtils.convertToIntArray(noPermissionAppIds));
+                        toIntArray(noPermissionAppIds));
             }
             if (uninstalledAppIds.size() != 0) {
                 mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UNINSTALLED,
-                        ArrayUtils.convertToIntArray(uninstalledAppIds));
+                        toIntArray(uninstalledAppIds));
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Pass appId list of special permission failed." + e);
diff --git a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
index 87b4c16..7ef315c 100644
--- a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
+++ b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
@@ -27,7 +27,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.telephony.data.EpsBearerQosSessionAttributes;
-import android.util.Slog;
+import android.util.Log;
 
 import com.android.internal.util.CollectionUtils;
 import com.android.server.ConnectivityService;
@@ -260,18 +260,18 @@
     }
 
     private static void log(final String msg) {
-        Slog.d(TAG, msg);
+        Log.d(TAG, msg);
     }
 
     private static void logw(final String msg) {
-        Slog.w(TAG, msg);
+        Log.w(TAG, msg);
     }
 
     private static void loge(final String msg) {
-        Slog.e(TAG, msg);
+        Log.e(TAG, msg);
     }
 
     private static void logwtf(final String msg) {
-        Slog.wtf(TAG, msg);
+        Log.wtf(TAG, msg);
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
index b5f20d7..c480594 100644
--- a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
+++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
@@ -41,7 +41,6 @@
 import android.os.MessageQueue;
 import android.os.Messenger;
 import android.system.ErrnoException;
-import android.system.Int32Ref;
 import android.system.Os;
 import android.util.Log;
 import android.util.SparseArray;
@@ -306,9 +305,8 @@
 
     private static boolean isReceiveQueueEmpty(FileDescriptor fd)
             throws ErrnoException {
-        Int32Ref result = new Int32Ref(-1);
-        Os.ioctlInt(fd, SIOCINQ, result);
-        if (result.value != 0) {
+        final int result = Os.ioctlInt(fd, SIOCINQ);
+        if (result != 0) {
             Log.e(TAG, "Read queue has data");
             return false;
         }
@@ -317,9 +315,8 @@
 
     private static boolean isSendQueueEmpty(FileDescriptor fd)
             throws ErrnoException {
-        Int32Ref result = new Int32Ref(-1);
-        Os.ioctlInt(fd, SIOCOUTQ, result);
-        if (result.value != 0) {
+        final int result = Os.ioctlInt(fd, SIOCOUTQ);
+        if (result != 0) {
             Log.e(TAG, "Write queue has data");
             return false;
         }
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index ffde68e..8122495 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -1,6 +1,15 @@
 //########################################################################
 // Build FrameworksNetTests package
 //########################################################################
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
 java_defaults {
     name: "FrameworksNetTests-jni-defaults",
     jni_libs: [
diff --git a/tests/net/common/Android.bp b/tests/net/common/Android.bp
index c271f49..babb81c 100644
--- a/tests/net/common/Android.bp
+++ b/tests/net/common/Android.bp
@@ -16,6 +16,15 @@
 
 // Tests in this folder are included both in unit tests and CTS.
 // They must be fast and stable, and exercise public or test APIs.
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
 java_library {
     name: "FrameworksNetCommonTests",
     srcs: ["java/**/*.java", "java/**/*.kt"],
diff --git a/tests/net/common/java/android/net/CaptivePortalDataTest.kt b/tests/net/common/java/android/net/CaptivePortalDataTest.kt
index b2bcfeb..ad5bbf2 100644
--- a/tests/net/common/java/android/net/CaptivePortalDataTest.kt
+++ b/tests/net/common/java/android/net/CaptivePortalDataTest.kt
@@ -54,12 +54,26 @@
             }
             .build()
 
+    private val dataFromPasspoint = CaptivePortalData.Builder()
+            .setUserPortalUrl(Uri.parse("https://tc.example.com/passpoint"),
+                    CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+            .setVenueInfoUrl(Uri.parse("https://venue.example.com/passpoint"),
+                    CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+            .setCaptive(true)
+            .apply {
+                if (SdkLevel.isAtLeastS()) {
+                    setVenueFriendlyName("venue friendly name")
+                }
+            }
+            .build()
+
     private fun makeBuilder() = CaptivePortalData.Builder(data)
 
     @Test
     fun testParcelUnparcel() {
-        val fieldCount = if (SdkLevel.isAtLeastS()) 8 else 7
+        val fieldCount = if (SdkLevel.isAtLeastS()) 10 else 7
         assertParcelSane(data, fieldCount)
+        assertParcelSane(dataFromPasspoint, fieldCount)
 
         assertParcelingIsLossless(makeBuilder().setUserPortalUrl(null).build())
         assertParcelingIsLossless(makeBuilder().setVenueInfoUrl(null).build())
@@ -83,6 +97,27 @@
             assertNotEqualsAfterChange { it.setVenueFriendlyName("another friendly name") }
             assertNotEqualsAfterChange { it.setVenueFriendlyName(null) }
         }
+
+        assertEquals(dataFromPasspoint, CaptivePortalData.Builder(dataFromPasspoint).build())
+        assertNotEqualsAfterChange { it.setUserPortalUrl(
+                Uri.parse("https://tc.example.com/passpoint")) }
+        assertNotEqualsAfterChange { it.setUserPortalUrl(
+                Uri.parse("https://tc.example.com/passpoint"),
+                CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) }
+        assertNotEqualsAfterChange { it.setUserPortalUrl(
+                Uri.parse("https://tc.example.com/other"),
+                CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) }
+        assertNotEqualsAfterChange { it.setUserPortalUrl(
+                Uri.parse("https://tc.example.com/passpoint"),
+                CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) }
+        assertNotEqualsAfterChange { it.setVenueInfoUrl(
+                Uri.parse("https://venue.example.com/passpoint")) }
+        assertNotEqualsAfterChange { it.setVenueInfoUrl(
+                Uri.parse("https://venue.example.com/other"),
+                CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) }
+        assertNotEqualsAfterChange { it.setVenueInfoUrl(
+                Uri.parse("https://venue.example.com/passpoint"),
+                CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) }
     }
 
     @Test
@@ -130,6 +165,22 @@
         assertEquals("venue friendly name", data.venueFriendlyName)
     }
 
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    fun testGetVenueInfoUrlSource() {
+        assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER,
+                data.venueInfoUrlSource)
+        assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT,
+                dataFromPasspoint.venueInfoUrlSource)
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    fun testGetUserPortalUrlSource() {
+        assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER,
+                data.userPortalUrlSource)
+        assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT,
+                dataFromPasspoint.userPortalUrlSource)
+    }
+
     private fun CaptivePortalData.mutate(mutator: (CaptivePortalData.Builder) -> Unit) =
             CaptivePortalData.Builder(this).apply { mutator(this) }.build()
 
diff --git a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
index d232a50..fd29a95 100644
--- a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
+++ b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
@@ -40,7 +40,7 @@
 @SmallTest
 public class OemNetworkPreferencesTest {
 
-    private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_DEFAULT;
+    private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED;
     private static final String TEST_PACKAGE = "com.google.apps.contacts";
 
     private final OemNetworkPreferences.Builder mBuilder = new OemNetworkPreferences.Builder();
@@ -54,7 +54,7 @@
     @Test
     public void testBuilderRemoveNetworkPreferenceRequiresNonNullPackageName() {
         assertThrows(NullPointerException.class,
-                () -> mBuilder.removeNetworkPreference(null));
+                () -> mBuilder.clearNetworkPreference(null));
     }
 
     @Test
@@ -129,7 +129,7 @@
 
         assertTrue(networkPreferences.containsKey(TEST_PACKAGE));
 
-        mBuilder.removeNetworkPreference(TEST_PACKAGE);
+        mBuilder.clearNetworkPreference(TEST_PACKAGE);
         networkPreferences = mBuilder.build().getNetworkPreferences();
 
         assertFalse(networkPreferences.containsKey(TEST_PACKAGE));
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 083c8c8..9ed55f0 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -38,6 +38,7 @@
 import android.os.ConditionVariable
 import android.os.IBinder
 import android.os.INetworkManagementService
+import android.os.SystemConfigManager
 import android.os.UserHandle
 import android.testing.TestableContext
 import android.util.Log
@@ -57,6 +58,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.AdditionalAnswers
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
@@ -94,6 +96,8 @@
     private lateinit var netd: INetd
     @Mock
     private lateinit var dnsResolver: IDnsResolver
+    @Mock
+    private lateinit var systemConfigManager: SystemConfigManager
     @Spy
     private var context = TestableContext(realContext)
 
@@ -151,6 +155,11 @@
         doReturn(UserHandle.ALL).`when`(asUserCtx).user
         doReturn(asUserCtx).`when`(context).createContextAsUser(eq(UserHandle.ALL), anyInt())
         doNothing().`when`(context).sendStickyBroadcast(any(), any())
+        doReturn(Context.SYSTEM_CONFIG_SERVICE).`when`(context)
+                .getSystemServiceName(SystemConfigManager::class.java)
+        doReturn(systemConfigManager).`when`(context)
+                .getSystemService(Context.SYSTEM_CONFIG_SERVICE)
+        doReturn(IntArray(0)).`when`(systemConfigManager).getSystemPermissionUids(anyString())
 
         networkStackClient = TestNetworkStackClient(realContext)
         networkStackClient.init()
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index c2fddf3..6a09b02 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -35,6 +35,7 @@
 import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST;
 import static android.net.NetworkRequest.Type.REQUEST;
 import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
+import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -329,6 +330,9 @@
         mustFail(() -> { manager.registerDefaultNetworkCallback(null, handler); });
         mustFail(() -> { manager.registerDefaultNetworkCallback(callback, null); });
 
+        mustFail(() -> { manager.registerSystemDefaultNetworkCallback(null, handler); });
+        mustFail(() -> { manager.registerSystemDefaultNetworkCallback(callback, null); });
+
         mustFail(() -> { manager.unregisterNetworkCallback(nullCallback); });
         mustFail(() -> { manager.unregisterNetworkCallback(nullIntent); });
         mustFail(() -> { manager.releaseNetworkRequest(nullIntent); });
@@ -345,15 +349,17 @@
     @Test
     public void testRequestType() throws Exception {
         final String testPkgName = "MyPackage";
+        final String testAttributionTag = "MyTag";
         final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
         when(mCtx.getOpPackageName()).thenReturn(testPkgName);
+        when(mCtx.getAttributionTag()).thenReturn(testAttributionTag);
         final NetworkRequest request = makeRequest(1);
         final NetworkCallback callback = new ConnectivityManager.NetworkCallback();
 
         manager.requestNetwork(request, callback);
         verify(mService).requestNetwork(eq(request.networkCapabilities),
                 eq(REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE),
-                eq(testPkgName), eq(null));
+                eq(testPkgName), eq(testAttributionTag));
         reset(mService);
 
         // Verify that register network callback does not calls requestNetwork at all.
@@ -361,19 +367,26 @@
         verify(mService, never()).requestNetwork(any(), anyInt(), any(), anyInt(), any(),
                 anyInt(), any(), any());
         verify(mService).listenForNetwork(eq(request.networkCapabilities), any(), any(),
-                eq(testPkgName));
+                eq(testPkgName), eq(testAttributionTag));
         reset(mService);
 
         manager.registerDefaultNetworkCallback(callback);
         verify(mService).requestNetwork(eq(null),
                 eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE),
-                eq(testPkgName), eq(null));
+                eq(testPkgName), eq(testAttributionTag));
         reset(mService);
 
         manager.requestBackgroundNetwork(request, null, callback);
         verify(mService).requestNetwork(eq(request.networkCapabilities),
                 eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE),
-                eq(testPkgName), eq(null));
+                eq(testPkgName), eq(testAttributionTag));
+        reset(mService);
+
+        Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+        manager.registerSystemDefaultNetworkCallback(callback, handler);
+        verify(mService).requestNetwork(eq(null),
+                eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE),
+                eq(testPkgName), eq(testAttributionTag));
         reset(mService);
     }
 
diff --git a/tests/net/java/android/net/NetworkTemplateTest.kt b/tests/net/java/android/net/NetworkTemplateTest.kt
index 91fcbc0..b39555d 100644
--- a/tests/net/java/android/net/NetworkTemplateTest.kt
+++ b/tests/net/java/android/net/NetworkTemplateTest.kt
@@ -35,7 +35,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
 import org.mockito.MockitoAnnotations
 import kotlin.test.assertFalse
@@ -60,16 +59,13 @@
         subscriberId: String? = null,
         ssid: String? = null
     ): NetworkState {
-        val info = mock(NetworkInfo::class.java)
-        doReturn(type).`when`(info).type
-        doReturn(NetworkInfo.State.CONNECTED).`when`(info).state
         val lp = LinkProperties()
         val caps = NetworkCapabilities().apply {
             setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false)
             setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true)
             setSSID(ssid)
         }
-        return NetworkState(info, lp, caps, mock(Network::class.java), subscriberId, ssid)
+        return NetworkState(type, lp, caps, mock(Network::class.java), subscriberId)
     }
 
     private fun NetworkTemplate.assertMatches(ident: NetworkIdentity) =
diff --git a/tests/net/java/android/net/VpnManagerTest.java b/tests/net/java/android/net/VpnManagerTest.java
index 95a7942..c548e30 100644
--- a/tests/net/java/android/net/VpnManagerTest.java
+++ b/tests/net/java/android/net/VpnManagerTest.java
@@ -49,7 +49,7 @@
     private static final String IDENTITY_STRING = "Identity";
     private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
 
-    private IConnectivityManager mMockCs;
+    private IVpnManager mMockService;
     private VpnManager mVpnManager;
     private final MockContext mMockContext =
             new MockContext() {
@@ -61,24 +61,26 @@
 
     @Before
     public void setUp() throws Exception {
-        mMockCs = mock(IConnectivityManager.class);
-        mVpnManager = new VpnManager(mMockContext, mMockCs);
+        mMockService = mock(IVpnManager.class);
+        mVpnManager = new VpnManager(mMockContext, mMockService);
     }
 
     @Test
     public void testProvisionVpnProfilePreconsented() throws Exception {
         final PlatformVpnProfile profile = getPlatformVpnProfile();
-        when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(true);
+        when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+                .thenReturn(true);
 
         // Expect there to be no intent returned, as consent has already been granted.
         assertNull(mVpnManager.provisionVpnProfile(profile));
-        verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+        verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
     }
 
     @Test
     public void testProvisionVpnProfileNeedsConsent() throws Exception {
         final PlatformVpnProfile profile = getPlatformVpnProfile();
-        when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(false);
+        when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+                .thenReturn(false);
 
         // Expect intent to be returned, as consent has not already been granted.
         final Intent intent = mVpnManager.provisionVpnProfile(profile);
@@ -88,25 +90,25 @@
                 ComponentName.unflattenFromString(
                         "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog");
         assertEquals(expectedComponentName, intent.getComponent());
-        verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+        verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
     }
 
     @Test
     public void testDeleteProvisionedVpnProfile() throws Exception {
         mVpnManager.deleteProvisionedVpnProfile();
-        verify(mMockCs).deleteVpnProfile(eq(PKG_NAME));
+        verify(mMockService).deleteVpnProfile(eq(PKG_NAME));
     }
 
     @Test
     public void testStartProvisionedVpnProfile() throws Exception {
         mVpnManager.startProvisionedVpnProfile();
-        verify(mMockCs).startVpnProfile(eq(PKG_NAME));
+        verify(mMockService).startVpnProfile(eq(PKG_NAME));
     }
 
     @Test
     public void testStopProvisionedVpnProfile() throws Exception {
         mVpnManager.stopProvisionedVpnProfile();
-        verify(mMockCs).stopVpnProfile(eq(PKG_NAME));
+        verify(mMockService).stopVpnProfile(eq(PKG_NAME));
     }
 
     private Ikev2VpnProfile getPlatformVpnProfile() throws Exception {
diff --git a/tests/net/java/android/net/VpnTransportInfoTest.java b/tests/net/java/android/net/VpnTransportInfoTest.java
new file mode 100644
index 0000000..866f38c
--- /dev/null
+++ b/tests/net/java/android/net/VpnTransportInfoTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VpnTransportInfoTest {
+
+    @Test
+    public void testParceling() {
+        VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
+        assertParcelSane(v, 1 /* 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);
+        assertNotEquals(v1, v2);
+        assertEquals(v1, v3);
+        assertEquals(v1.hashCode(), v3.hashCode());
+    }
+}
\ No newline at end of file
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 446c731..88ccf8e 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -66,6 +66,8 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
@@ -82,6 +84,11 @@
 import static android.net.NetworkPolicyManager.RULE_NONE;
 import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
+import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 import static android.os.Process.INVALID_UID;
 import static android.system.OsConstants.IPPROTO_TCP;
@@ -169,6 +176,7 @@
 import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkPolicyListener;
 import android.net.INetworkStatsService;
+import android.net.IOnSetOemNetworkPreferenceListener;
 import android.net.IQosCallback;
 import android.net.InetAddresses;
 import android.net.InterfaceConfigurationParcel;
@@ -192,6 +200,7 @@
 import android.net.NetworkStackClient;
 import android.net.NetworkState;
 import android.net.NetworkTestResultParcelable;
+import android.net.OemNetworkPreferences;
 import android.net.ProxyInfo;
 import android.net.QosCallbackException;
 import android.net.QosFilter;
@@ -200,11 +209,13 @@
 import android.net.RouteInfo;
 import android.net.RouteInfoParcel;
 import android.net.SocketKeepalive;
+import android.net.TransportInfo;
 import android.net.UidRange;
 import android.net.UidRangeParcel;
 import android.net.UnderlyingNetworkInfo;
 import android.net.Uri;
 import android.net.VpnManager;
+import android.net.VpnTransportInfo;
 import android.net.metrics.IpConnectivityLog;
 import android.net.shared.NetworkMonitorUtils;
 import android.net.shared.PrivateDnsConfig;
@@ -227,6 +238,7 @@
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.os.SystemClock;
+import android.os.SystemConfigManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -298,6 +310,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -348,6 +361,7 @@
     private static final long TIMESTAMP = 1234L;
 
     private static final int NET_ID = 110;
+    private static final int OEM_PREF_ANY_NET_ID = -1;
     // Set a non-zero value to verify the flow to set tcp init rwnd value.
     private static final int TEST_TCP_INIT_RWND = 60;
 
@@ -357,17 +371,26 @@
     private static final String WIFI_WOL_IFNAME = "test_wlan_wol";
     private static final String VPN_IFNAME = "tun10042";
     private static final String TEST_PACKAGE_NAME = "com.android.test.package";
+    private static final int TEST_PACKAGE_UID = 123;
     private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn";
 
     private static final String INTERFACE_NAME = "interface";
 
-    private static final String TEST_VENUE_URL_NA = "https://android.com/";
+    private static final String TEST_VENUE_URL_NA_PASSPOINT = "https://android.com/";
+    private static final String TEST_VENUE_URL_NA_OTHER = "https://example.com/";
+    private static final String TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT =
+            "https://android.com/terms/";
+    private static final String TEST_TERMS_AND_CONDITIONS_URL_NA_OTHER =
+            "https://example.com/terms/";
     private static final String TEST_VENUE_URL_CAPPORT = "https://android.com/capport/";
+    private static final String TEST_USER_PORTAL_API_URL_CAPPORT =
+            "https://android.com/user/api/capport/";
     private static final String TEST_FRIENDLY_NAME = "Network friendly name";
     private static final String TEST_REDIRECT_URL = "http://example.com/firstPath";
 
     private MockContext mServiceContext;
     private HandlerThread mCsHandlerThread;
+    private HandlerThread mVMSHandlerThread;
     private ConnectivityService.Dependencies mDeps;
     private ConnectivityService mService;
     private WrappedConnectivityManager mCm;
@@ -382,6 +405,7 @@
     private TestNetIdManager mNetIdManager;
     private QosCallbackMockHelper mQosCallbackMockHelper;
     private QosCallbackTracker mQosCallbackTracker;
+    private VpnManagerService mVpnManagerService;
 
     // State variables required to emulate NetworkPolicyManagerService behaviour.
     private int mUidRules = RULE_NONE;
@@ -407,6 +431,7 @@
     @Mock EthernetManager mEthernetManager;
     @Mock NetworkPolicyManager mNetworkPolicyManager;
     @Mock KeyStore mKeyStore;
+    @Mock SystemConfigManager mSystemConfigManager;
 
     private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
             ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -503,6 +528,7 @@
             if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager;
             if (Context.ETHERNET_SERVICE.equals(name)) return mEthernetManager;
             if (Context.NETWORK_POLICY_SERVICE.equals(name)) return mNetworkPolicyManager;
+            if (Context.SYSTEM_CONFIG_SERVICE.equals(name)) return mSystemConfigManager;
             return super.getSystemService(name);
         }
 
@@ -976,10 +1002,12 @@
         // Used to collect the networks requests managed by this factory. This is a duplicate of
         // the internal information stored in the NetworkFactory (which is private).
         private SparseArray<NetworkRequest> mNetworkRequests = new SparseArray<>();
+        private final HandlerThread mHandlerSendingRequests;
 
         public MockNetworkFactory(Looper looper, Context context, String logTag,
-                NetworkCapabilities filter) {
+                NetworkCapabilities filter, HandlerThread threadSendingRequests) {
             super(looper, context, logTag, filter);
+            mHandlerSendingRequests = threadSendingRequests;
         }
 
         public int getMyRequestCount() {
@@ -1033,7 +1061,8 @@
         public void terminate() {
             super.terminate();
             // Make sure there are no remaining requests unaccounted for.
-            assertNull(mRequestHistory.poll(TIMEOUT_MS, r -> true));
+            HandlerUtils.waitForIdle(mHandlerSendingRequests, TIMEOUT_MS);
+            assertNull(mRequestHistory.poll(0, r -> true));
         }
 
         // Trigger releasing the request as unfulfillable
@@ -1110,7 +1139,7 @@
         }
 
         @Override
-        public int getActiveAppVpnType() {
+        public int getActiveVpnType() {
             return mVpnType;
         }
 
@@ -1123,10 +1152,12 @@
         private void registerAgent(boolean isAlwaysMetered, Set<UidRange> uids, LinkProperties lp)
                 throws Exception {
             if (mAgentRegistered) throw new IllegalStateException("already registered");
+            updateState(NetworkInfo.DetailedState.CONNECTING, "registerAgent");
             mConfig = new VpnConfig();
             setUids(uids);
             if (!isAlwaysMetered) mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
             mInterface = VPN_IFNAME;
+            mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType()));
             mMockNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp,
                     mNetworkCapabilities);
             mMockNetworkAgent.waitForIdle(TIMEOUT_MS);
@@ -1252,14 +1283,52 @@
                 r -> new UidRangeParcel(r.start, r.stop)).toArray(UidRangeParcel[]::new);
     }
 
+    private VpnManagerService makeVpnManagerService() {
+        final VpnManagerService.Dependencies deps = new VpnManagerService.Dependencies() {
+            public int getCallingUid() {
+                return mDeps.getCallingUid();
+            }
+
+            public HandlerThread makeHandlerThread() {
+                return mVMSHandlerThread;
+            }
+
+            public KeyStore getKeyStore() {
+                return mKeyStore;
+            }
+
+            public INetd getNetd() {
+                return mMockNetd;
+            }
+
+            public INetworkManagementService getINetworkManagementService() {
+                return mNetworkManagementService;
+            }
+        };
+        return new VpnManagerService(mServiceContext, deps);
+    }
+
+    private void assertVpnTransportInfo(NetworkCapabilities nc, int type) {
+        assertNotNull(nc);
+        final TransportInfo ti = nc.getTransportInfo();
+        assertTrue("VPN TransportInfo is not a VpnTransportInfo: " + ti,
+                ti instanceof VpnTransportInfo);
+        assertEquals(type, ((VpnTransportInfo) ti).type);
+
+    }
+
+    private void processBroadcastForVpn(Intent intent) {
+        mServiceContext.sendBroadcast(intent);
+        HandlerUtils.waitForIdle(mVMSHandlerThread, TIMEOUT_MS);
+        waitForIdle();
+    }
+
     private void mockVpn(int uid) {
-        synchronized (mService.mVpns) {
+        synchronized (mVpnManagerService.mVpns) {
             int userId = UserHandle.getUserId(uid);
             mMockVpn = new MockVpn(userId);
-            // This has no effect unless the VPN is actually connected, because things like
-            // getActiveNetworkForUidInternal call getNetworkAgentInfoForNetId on the VPN
-            // netId, and check if that network is actually connected.
-            mService.mVpns.put(userId, mMockVpn);
+            // Every running user always has a Vpn in the mVpns array, even if no VPN is running.
+            mVpnManagerService.mVpns.put(userId, mMockVpn);
         }
     }
 
@@ -1366,6 +1435,7 @@
         applicationInfo.targetSdkVersion = Build.VERSION_CODES.Q;
         when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any()))
                 .thenReturn(applicationInfo);
+        when(mSystemConfigManager.getSystemPermissionUids(anyString())).thenReturn(new int[0]);
 
         // InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
         // http://b/25897652 .
@@ -1377,6 +1447,7 @@
         FakeSettingsProvider.clearSettingsProvider();
         mServiceContext = new MockContext(InstrumentationRegistry.getContext(),
                 new FakeSettingsProvider());
+        mServiceContext.setUseRegisteredHandlers(true);
         LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
         LocalServices.addService(
                 NetworkPolicyManagerInternal.class, mock(NetworkPolicyManagerInternal.class));
@@ -1386,6 +1457,7 @@
         initAlarmManager(mAlarmManager, mAlarmManagerThread.getThreadHandler());
 
         mCsHandlerThread = new HandlerThread("TestConnectivityService");
+        mVMSHandlerThread = new HandlerThread("TestVpnManagerService");
         mDeps = makeDependencies();
         returnRealCallingUid();
         mService = new ConnectivityService(mServiceContext,
@@ -1408,6 +1480,8 @@
         // getSystemService() correctly.
         mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService);
         mService.systemReadyInternal();
+        mVpnManagerService = makeVpnManagerService();
+        mVpnManagerService.systemReady();
         mockVpn(Process.myUid());
         mCm.bindProcessToNetwork(null);
         mQosCallbackTracker = mock(QosCallbackTracker.class);
@@ -1435,7 +1509,6 @@
         doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any());
         doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt());
         doReturn(mBatteryStatsService).when(deps).getBatteryStatsService();
-        doReturn(mKeyStore).when(deps).getKeyStore();
         doAnswer(inv -> {
             mPolicyTracker = new WrappedMultinetworkPolicyTracker(
                     inv.getArgument(0), inv.getArgument(1), inv.getArgument(2));
@@ -1548,10 +1621,13 @@
         }
         switch (transport) {
             case TRANSPORT_WIFI:
-                assertEquals(mCm.getActiveNetwork(), mWiFiNetworkAgent.getNetwork());
+                assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
                 break;
             case TRANSPORT_CELLULAR:
-                assertEquals(mCm.getActiveNetwork(), mCellNetworkAgent.getNetwork());
+                assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+                break;
+            case TRANSPORT_ETHERNET:
+                assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
                 break;
             default:
                 break;
@@ -1560,6 +1636,7 @@
         assertNotNull(mCm.getNetworkInfo(mCm.getActiveNetwork()));
         assertEquals(transportToLegacyType(transport),
                 mCm.getNetworkInfo(mCm.getActiveNetwork()).getType());
+        assertNotNull(mCm.getActiveNetworkInfoForUid(Process.myUid()));
         // Test getNetworkCapabilities(Network)
         assertNotNull(mCm.getNetworkCapabilities(mCm.getActiveNetwork()));
         assertTrue(mCm.getNetworkCapabilities(mCm.getActiveNetwork()).hasTransport(transport));
@@ -2097,6 +2174,24 @@
         }
     }
 
+    static void expectOnLost(TestNetworkAgentWrapper network, TestNetworkCallback ... callbacks) {
+        for (TestNetworkCallback c : callbacks) {
+            c.expectCallback(CallbackEntry.LOST, network);
+        }
+    }
+
+    static void expectAvailableCallbacksUnvalidatedWithSpecifier(TestNetworkAgentWrapper network,
+            NetworkSpecifier specifier, TestNetworkCallback ... callbacks) {
+        for (TestNetworkCallback c : callbacks) {
+            c.expectCallback(CallbackEntry.AVAILABLE, network);
+            c.expectCapabilitiesThat(network, (nc) ->
+                    !nc.hasCapability(NET_CAPABILITY_VALIDATED)
+                            && Objects.equals(specifier, nc.getNetworkSpecifier()));
+            c.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, network);
+            c.expectCallback(CallbackEntry.BLOCKED_STATUS, network);
+        }
+    }
+
     @Test
     public void testStateChangeNetworkCallbacks() throws Exception {
         final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback();
@@ -2703,7 +2798,7 @@
         final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
         handlerThread.start();
         final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
-                mServiceContext, "testFactory", filter);
+                mServiceContext, "testFactory", filter, mCsHandlerThread);
         testFactory.setScoreFilter(40);
         ConditionVariable cv = testFactory.getNetworkStartedCV();
         testFactory.register();
@@ -2811,7 +2906,7 @@
         // does not crash.
         for (int i = 0; i < 100; i++) {
             final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
-                    mServiceContext, "testFactory", filter);
+                    mServiceContext, "testFactory", filter, mCsHandlerThread);
             // Register the factory and don't be surprised when the default request arrives.
             testFactory.register();
             testFactory.expectRequestAdd();
@@ -3275,39 +3370,68 @@
     }
 
     private class CaptivePortalTestData {
-        CaptivePortalTestData(CaptivePortalData naData, CaptivePortalData capportData,
-                CaptivePortalData expectedMergedData) {
-            mNaData = naData;
+        CaptivePortalTestData(CaptivePortalData naPasspointData, CaptivePortalData capportData,
+                CaptivePortalData naOtherData, CaptivePortalData expectedMergedPasspointData,
+                CaptivePortalData expectedMergedOtherData) {
+            mNaPasspointData = naPasspointData;
             mCapportData = capportData;
-            mExpectedMergedData = expectedMergedData;
+            mNaOtherData = naOtherData;
+            mExpectedMergedPasspointData = expectedMergedPasspointData;
+            mExpectedMergedOtherData = expectedMergedOtherData;
         }
 
-        public final CaptivePortalData mNaData;
+        public final CaptivePortalData mNaPasspointData;
         public final CaptivePortalData mCapportData;
-        public final CaptivePortalData mExpectedMergedData;
+        public final CaptivePortalData mNaOtherData;
+        public final CaptivePortalData mExpectedMergedPasspointData;
+        public final CaptivePortalData mExpectedMergedOtherData;
+
     }
 
     private CaptivePortalTestData setupCaptivePortalData() {
         final CaptivePortalData capportData = new CaptivePortalData.Builder()
                 .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL))
                 .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_CAPPORT))
+                .setUserPortalUrl(Uri.parse(TEST_USER_PORTAL_API_URL_CAPPORT))
                 .setExpiryTime(1000000L)
                 .setBytesRemaining(12345L)
                 .build();
 
-        final CaptivePortalData naData = new CaptivePortalData.Builder()
+        final CaptivePortalData naPasspointData = new CaptivePortalData.Builder()
                 .setBytesRemaining(80802L)
-                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA))
+                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_PASSPOINT),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+                .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
                 .setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
 
-        final CaptivePortalData expectedMergedData = new CaptivePortalData.Builder()
+        final CaptivePortalData naOtherData = new CaptivePortalData.Builder()
+                .setBytesRemaining(80802L)
+                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_OTHER),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER)
+                .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_OTHER),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER)
+                .setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
+
+        final CaptivePortalData expectedMergedPasspointData = new CaptivePortalData.Builder()
                 .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL))
                 .setBytesRemaining(12345L)
                 .setExpiryTime(1000000L)
-                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA))
+                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_PASSPOINT),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+                .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
                 .setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
 
-        return new CaptivePortalTestData(naData, capportData, expectedMergedData);
+        final CaptivePortalData expectedMergedOtherData = new CaptivePortalData.Builder()
+                .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL))
+                .setBytesRemaining(12345L)
+                .setExpiryTime(1000000L)
+                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_CAPPORT))
+                .setUserPortalUrl(Uri.parse(TEST_USER_PORTAL_API_URL_CAPPORT))
+                .setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
+        return new CaptivePortalTestData(naPasspointData, capportData, naOtherData,
+                expectedMergedPasspointData, expectedMergedOtherData);
     }
 
     @Test
@@ -3321,15 +3445,26 @@
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
                 lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData()));
 
-        // Venue URL and friendly name from Network agent, confirm that API data gets precedence
-        // on the bytes remaining.
+        // Venue URL, T&C URL and friendly name from Network agent with Passpoint source, confirm
+        // that API data gets precedence on the bytes remaining.
         final LinkProperties linkProperties = new LinkProperties();
-        linkProperties.setCaptivePortalData(captivePortalTestData.mNaData);
+        linkProperties.setCaptivePortalData(captivePortalTestData.mNaPasspointData);
         mWiFiNetworkAgent.sendLinkProperties(linkProperties);
 
         // Make sure that the capport data is merged
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mExpectedMergedData.equals(lp.getCaptivePortalData()));
+                lp -> captivePortalTestData.mExpectedMergedPasspointData
+                        .equals(lp.getCaptivePortalData()));
+
+        // Now send this information from non-Passpoint source, confirm that Capport data takes
+        // precedence
+        linkProperties.setCaptivePortalData(captivePortalTestData.mNaOtherData);
+        mWiFiNetworkAgent.sendLinkProperties(linkProperties);
+
+        // Make sure that the capport data is merged
+        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
+                lp -> captivePortalTestData.mExpectedMergedOtherData
+                        .equals(lp.getCaptivePortalData()));
 
         // Create a new LP with no Network agent capport data
         final LinkProperties newLps = new LinkProperties();
@@ -3346,12 +3481,12 @@
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
                 lp -> lp.getCaptivePortalData() == null);
 
-        newLps.setCaptivePortalData(captivePortalTestData.mNaData);
+        newLps.setCaptivePortalData(captivePortalTestData.mNaPasspointData);
         mWiFiNetworkAgent.sendLinkProperties(newLps);
 
         // Make sure that only the network agent capport data is available
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mNaData.equals(lp.getCaptivePortalData()));
+                lp -> captivePortalTestData.mNaPasspointData.equals(lp.getCaptivePortalData()));
     }
 
     @Test
@@ -3362,12 +3497,12 @@
         // Venue URL and friendly name from Network agent, confirm that API data gets precedence
         // on the bytes remaining.
         final LinkProperties linkProperties = new LinkProperties();
-        linkProperties.setCaptivePortalData(captivePortalTestData.mNaData);
+        linkProperties.setCaptivePortalData(captivePortalTestData.mNaPasspointData);
         mWiFiNetworkAgent.sendLinkProperties(linkProperties);
 
         // Make sure that the data is saved correctly
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mNaData.equals(lp.getCaptivePortalData()));
+                lp -> captivePortalTestData.mNaPasspointData.equals(lp.getCaptivePortalData()));
 
         // Expected merged data: Network agent data is preferred, and values that are not used by
         // it are merged from capport data
@@ -3375,7 +3510,8 @@
 
         // Make sure that the Capport data is merged correctly
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mExpectedMergedData.equals(lp.getCaptivePortalData()));
+                lp -> captivePortalTestData.mExpectedMergedPasspointData.equals(
+                        lp.getCaptivePortalData()));
 
         // Now set the naData to null
         linkProperties.setCaptivePortalData(null);
@@ -3386,6 +3522,32 @@
                 lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData()));
     }
 
+    @Test
+    public void testMergeCaptivePortalDataFromNetworkAgentOtherSourceFirstThenCapport()
+            throws Exception {
+        final TestNetworkCallback captivePortalCallback = setupNetworkCallbackAndConnectToWifi();
+        final CaptivePortalTestData captivePortalTestData = setupCaptivePortalData();
+
+        // Venue URL and friendly name from Network agent, confirm that API data gets precedence
+        // on the bytes remaining.
+        final LinkProperties linkProperties = new LinkProperties();
+        linkProperties.setCaptivePortalData(captivePortalTestData.mNaOtherData);
+        mWiFiNetworkAgent.sendLinkProperties(linkProperties);
+
+        // Make sure that the data is saved correctly
+        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
+                lp -> captivePortalTestData.mNaOtherData.equals(lp.getCaptivePortalData()));
+
+        // Expected merged data: Network agent data is preferred, and values that are not used by
+        // it are merged from capport data
+        mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData);
+
+        // Make sure that the Capport data is merged correctly
+        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
+                lp -> captivePortalTestData.mExpectedMergedOtherData.equals(
+                        lp.getCaptivePortalData()));
+    }
+
     private NetworkRequest.Builder newWifiRequestBuilder() {
         return new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI);
     }
@@ -3393,11 +3555,9 @@
     /**
      * Verify request matching behavior with network specifiers.
      *
-     * Note: this test is somewhat problematic since it involves removing capabilities from
-     * agents - i.e. agents rejecting requests which they previously accepted. This is flagged
-     * as a WTF bug in
-     * {@link ConnectivityService#mixInCapabilities(NetworkAgentInfo, NetworkCapabilities)} but
-     * does work.
+     * This test does not check updating the specifier on a live network because the specifier is
+     * immutable and this triggers a WTF in
+     * {@link ConnectivityService#mixInCapabilities(NetworkAgentInfo, NetworkCapabilities)}.
      */
     @Test
     public void testNetworkSpecifier() throws Exception {
@@ -3482,60 +3642,49 @@
 
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
-        cEmpty1.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        cEmpty2.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        cEmpty3.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        cEmpty4.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiNetworkAgent, null /* specifier */,
+                cEmpty1, cEmpty2, cEmpty3, cEmpty4);
         assertNoCallbacks(cFoo, cBar);
 
+        mWiFiNetworkAgent.disconnect();
+        expectOnLost(mWiFiNetworkAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4);
+
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.setNetworkSpecifier(nsFoo);
-        cFoo.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        for (TestNetworkCallback c: emptyCallbacks) {
-            c.expectCapabilitiesThat(mWiFiNetworkAgent,
-                    (caps) -> caps.getNetworkSpecifier().equals(nsFoo));
-        }
-        cFoo.expectCapabilitiesThat(mWiFiNetworkAgent,
-                (caps) -> caps.getNetworkSpecifier().equals(nsFoo));
+        mWiFiNetworkAgent.connect(false);
+        expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiNetworkAgent, nsFoo,
+                cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo);
+        cBar.assertNoCallback();
         assertEquals(nsFoo,
                 mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier());
-        cFoo.assertNoCallback();
+        assertNoCallbacks(cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo);
 
+        mWiFiNetworkAgent.disconnect();
+        expectOnLost(mWiFiNetworkAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo);
+
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.setNetworkSpecifier(nsBar);
-        cFoo.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
-        cBar.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        for (TestNetworkCallback c: emptyCallbacks) {
-            c.expectCapabilitiesThat(mWiFiNetworkAgent,
-                    (caps) -> caps.getNetworkSpecifier().equals(nsBar));
-        }
-        cBar.expectCapabilitiesThat(mWiFiNetworkAgent,
-                (caps) -> caps.getNetworkSpecifier().equals(nsBar));
+        mWiFiNetworkAgent.connect(false);
+        expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiNetworkAgent, nsBar,
+                cEmpty1, cEmpty2, cEmpty3, cEmpty4, cBar);
+        cFoo.assertNoCallback();
         assertEquals(nsBar,
                 mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier());
-        cBar.assertNoCallback();
 
+        mWiFiNetworkAgent.disconnect();
+        expectOnLost(mWiFiNetworkAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4, cBar);
+        cFoo.assertNoCallback();
+
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.setNetworkSpecifier(new ConfidentialMatchAllNetworkSpecifier());
-        cFoo.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        for (TestNetworkCallback c : emptyCallbacks) {
-            c.expectCapabilitiesThat(mWiFiNetworkAgent,
-                    (caps) -> caps.getNetworkSpecifier() == null);
-        }
-        cFoo.expectCapabilitiesThat(mWiFiNetworkAgent,
-                (caps) -> caps.getNetworkSpecifier() == null);
-        cBar.expectCapabilitiesThat(mWiFiNetworkAgent,
-                (caps) -> caps.getNetworkSpecifier() == null);
+        mWiFiNetworkAgent.connect(false);
+        expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiNetworkAgent, null /* specifier */,
+                cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo, cBar);
         assertNull(
                 mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier());
-        cFoo.assertNoCallback();
-        cBar.assertNoCallback();
 
-        mWiFiNetworkAgent.setNetworkSpecifier(null);
-        cFoo.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
-        cBar.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
-        for (TestNetworkCallback c: emptyCallbacks) {
-            c.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mWiFiNetworkAgent);
-        }
-
-        assertNoCallbacks(cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo, cBar);
+        mWiFiNetworkAgent.disconnect();
+        expectOnLost(mWiFiNetworkAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo, cBar);
     }
 
     /**
@@ -3638,10 +3787,19 @@
 
     @Test
     public void testRegisterDefaultNetworkCallback() throws Exception {
+        // NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback.
+        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
+                PERMISSION_GRANTED);
+
         final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
         defaultNetworkCallback.assertNoCallback();
 
+        final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+        final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback();
+        mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback, handler);
+        systemDefaultCallback.assertNoCallback();
+
         // Create a TRANSPORT_CELLULAR request to keep the mobile interface up
         // whenever Wi-Fi is up. Without this, the mobile network agent is
         // reaped before any other activity can take place.
@@ -3656,27 +3814,35 @@
         mCellNetworkAgent.connect(true);
         cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        systemDefaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi and expect CALLBACK_AVAILABLE.
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         cellNetworkCallback.assertNoCallback();
         defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        systemDefaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring down cell. Expect no default network callback, since it wasn't the default.
         mCellNetworkAgent.disconnect();
         cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         defaultNetworkCallback.assertNoCallback();
+        systemDefaultCallback.assertNoCallback();
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring up cell. Expect no default network callback, since it won't be the default.
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
         cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         defaultNetworkCallback.assertNoCallback();
+        systemDefaultCallback.assertNoCallback();
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring down wifi. Expect the default network callback to notified of LOST wifi
         // followed by AVAILABLE cell.
@@ -3684,19 +3850,25 @@
         cellNetworkCallback.assertNoCallback();
         defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
         defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+        systemDefaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
         cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+        systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
 
         mMockVpn.establishForMyUid();
         assertUidRangesUpdatedForMyUid(true);
         defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(null, systemDefaultCallback.getLastAvailableNetwork());
 
         mMockVpn.disconnect();
         defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
     }
@@ -3762,6 +3934,24 @@
         mCm.unregisterNetworkCallback(cellNetworkCallback);
     }
 
+    @Test
+    public void testRegisterSystemDefaultCallbackRequiresNetworkSettings() throws Exception {
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(false /* validated */);
+
+        final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        assertThrows(SecurityException.class,
+                () -> mCm.registerSystemDefaultNetworkCallback(callback, handler));
+        callback.assertNoCallback();
+
+        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
+                PERMISSION_GRANTED);
+        mCm.registerSystemDefaultNetworkCallback(callback, handler);
+        callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        mCm.unregisterNetworkCallback(callback);
+    }
+
     private void setCaptivePortalMode(int mode) {
         ContentResolver cr = mServiceContext.getContentResolver();
         Settings.Global.putInt(cr, Settings.Global.CAPTIVE_PORTAL_MODE, mode);
@@ -3953,7 +4143,7 @@
                 .addTransportType(TRANSPORT_CELLULAR)
                 .addCapability(NET_CAPABILITY_INTERNET);
         final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
-                mServiceContext, "testFactory", filter);
+                mServiceContext, "testFactory", filter, mCsHandlerThread);
         testFactory.setScoreFilter(40);
 
         // Register the factory and expect it to start looking for a network.
@@ -4001,6 +4191,7 @@
             // ...  and cell data to be torn down after nascent network timeout.
             cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent,
                     mService.mNascentDelayMs + TEST_CALLBACK_TIMEOUT_MS);
+            waitForIdle();
             assertLength(1, mCm.getAllNetworks());
         } finally {
             testFactory.terminate();
@@ -4300,7 +4491,7 @@
                 .addTransportType(TRANSPORT_WIFI)
                 .addCapability(NET_CAPABILITY_INTERNET);
         final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
-                mServiceContext, "testFactory", filter);
+                mServiceContext, "testFactory", filter, mCsHandlerThread);
         testFactory.setScoreFilter(40);
 
         // Register the factory and expect it to receive the default request.
@@ -5405,20 +5596,20 @@
         // MOBILE_IFNAME even though the default network is wifi.
         // TODO: fix this to pass in the actual default network interface. Whether or not the VPN
         // applies to the system server UID should not have any bearing on network stats.
-        mService.setUnderlyingNetworksForVpn(onlyCell);
+        mMockVpn.setUnderlyingNetworks(onlyCell);
         waitForIdle();
         expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME,
                 new String[]{MOBILE_IFNAME});
         reset(mStatsService);
 
-        mService.setUnderlyingNetworksForVpn(cellAndWifi);
+        mMockVpn.setUnderlyingNetworks(cellAndWifi);
         waitForIdle();
         expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME,
                 new String[]{MOBILE_IFNAME, WIFI_IFNAME});
         reset(mStatsService);
 
         // Null underlying networks are ignored.
-        mService.setUnderlyingNetworksForVpn(cellNullAndWifi);
+        mMockVpn.setUnderlyingNetworks(cellNullAndWifi);
         waitForIdle();
         expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME,
                 new String[]{MOBILE_IFNAME, WIFI_IFNAME});
@@ -5467,25 +5658,25 @@
         // is probably a performance improvement (though it's very unlikely that a VPN would declare
         // no underlying networks).
         // Also, for the same reason as above, the active interface passed in is null.
-        mService.setUnderlyingNetworksForVpn(new Network[0]);
+        mMockVpn.setUnderlyingNetworks(new Network[0]);
         waitForIdle();
         expectForceUpdateIfaces(wifiAndVpn, null);
         reset(mStatsService);
 
         // Specifying only a null underlying network is the same as no networks.
-        mService.setUnderlyingNetworksForVpn(onlyNull);
+        mMockVpn.setUnderlyingNetworks(onlyNull);
         waitForIdle();
         expectForceUpdateIfaces(wifiAndVpn, null);
         reset(mStatsService);
 
         // Specifying networks that are all disconnected is the same as specifying no networks.
-        mService.setUnderlyingNetworksForVpn(onlyCell);
+        mMockVpn.setUnderlyingNetworks(onlyCell);
         waitForIdle();
         expectForceUpdateIfaces(wifiAndVpn, null);
         reset(mStatsService);
 
         // Passing in null again means follow the default network again.
-        mService.setUnderlyingNetworksForVpn(null);
+        mMockVpn.setUnderlyingNetworks(null);
         waitForIdle();
         expectForceUpdateIfaces(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME,
                 new String[]{WIFI_IFNAME});
@@ -5960,7 +6151,7 @@
         mMockVpn.establishForMyUid(false, true, false);
         assertUidRangesUpdatedForMyUid(true);
         final Network wifiNetwork = new Network(mNetIdManager.peekNextNetId());
-        mService.setUnderlyingNetworksForVpn(new Network[]{wifiNetwork});
+        mMockVpn.setUnderlyingNetworks(new Network[]{wifiNetwork});
         callback.expectAvailableCallbacksUnvalidated(mMockVpn);
         assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
                 .hasTransport(TRANSPORT_VPN));
@@ -6120,6 +6311,10 @@
 
     @Test
     public void testVpnNetworkActive() throws Exception {
+        // NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback.
+        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
+                PERMISSION_GRANTED);
+
         final int uid = Process.myUid();
 
         final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback();
@@ -6127,6 +6322,7 @@
         final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
         final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback();
         final TestNetworkCallback defaultCallback = new TestNetworkCallback();
+        final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback();
         final NetworkRequest genericNotVpnRequest = new NetworkRequest.Builder().build();
         final NetworkRequest genericRequest = new NetworkRequest.Builder()
                 .removeCapability(NET_CAPABILITY_NOT_VPN).build();
@@ -6140,6 +6336,8 @@
         mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
         mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback);
         mCm.registerDefaultNetworkCallback(defaultCallback);
+        mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback,
+                new Handler(ConnectivityThread.getInstanceLooper()));
         defaultCallback.assertNoCallback();
 
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -6149,12 +6347,13 @@
         genericNotVpnNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         vpnNetworkCallback.assertNoCallback();
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         final Set<UidRange> ranges = uidRangesForUid(uid);
         mMockVpn.registerAgent(ranges);
-        mService.setUnderlyingNetworksForVpn(new Network[0]);
+        mMockVpn.setUnderlyingNetworks(new Network[0]);
 
         // VPN networks do not satisfy the default request and are automatically validated
         // by NetworkMonitor
@@ -6169,7 +6368,10 @@
         wifiNetworkCallback.assertNoCallback();
         vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(mWiFiNetworkAgent.getNetwork(),
+                systemDefaultCallback.getLastAvailableNetwork());
 
         ranges.clear();
         mMockVpn.setUids(ranges);
@@ -6186,6 +6388,7 @@
         // much, but that is the reason the test here has to check for an update to the
         // capabilities instead of the expected LOST then AVAILABLE.
         defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
+        systemDefaultCallback.assertNoCallback();
 
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setUids(ranges);
@@ -6197,6 +6400,7 @@
         // TODO : Here like above, AVAILABLE would be correct, but because this can't actually
         // happen outside of the test, ConnectivityService does not rematch callbacks.
         defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
+        systemDefaultCallback.assertNoCallback();
 
         mWiFiNetworkAgent.disconnect();
 
@@ -6205,6 +6409,7 @@
         wifiNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
         vpnNetworkCallback.assertNoCallback();
         defaultCallback.assertNoCallback();
+        systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
 
         mMockVpn.disconnect();
 
@@ -6213,12 +6418,14 @@
         wifiNetworkCallback.assertNoCallback();
         vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
         defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         assertEquals(null, mCm.getActiveNetwork());
 
         mCm.unregisterNetworkCallback(genericNetworkCallback);
         mCm.unregisterNetworkCallback(wifiNetworkCallback);
         mCm.unregisterNetworkCallback(vpnNetworkCallback);
         mCm.unregisterNetworkCallback(defaultCallback);
+        mCm.unregisterNetworkCallback(systemDefaultCallback);
     }
 
     @Test
@@ -6354,11 +6561,13 @@
         assertTrue(nc.hasCapability(NET_CAPABILITY_VALIDATED));
         assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
         assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+
+        assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
     }
 
     private void assertDefaultNetworkCapabilities(int userId, NetworkAgentWrapper... networks) {
         final NetworkCapabilities[] defaultCaps = mService.getDefaultNetworkCapabilitiesForUser(
-                userId, "com.android.calling.package");
+                userId, "com.android.calling.package", "com.test");
         final String defaultCapsString = Arrays.toString(defaultCaps);
         assertEquals(defaultCapsString, defaultCaps.length, networks.length);
         final Set<NetworkCapabilities> defaultCapsSet = new ArraySet<>(defaultCaps);
@@ -6393,6 +6602,7 @@
         assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
         // A VPN without underlying networks is not suspended.
         assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+        assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
 
         final int userId = UserHandle.getUserId(Process.myUid());
         assertDefaultNetworkCapabilities(userId /* no networks */);
@@ -6402,7 +6612,7 @@
         mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
         mCellNetworkAgent.connect(true);
 
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mCellNetworkAgent.getNetwork() });
 
         vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
@@ -6417,7 +6627,7 @@
         mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
         mWiFiNetworkAgent.connect(true);
 
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
 
         vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
@@ -6428,7 +6638,7 @@
         assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent);
 
         // Don't disconnect, but note the VPN is not using wifi any more.
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mCellNetworkAgent.getNetwork() });
 
         vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
@@ -6459,7 +6669,7 @@
         vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, mMockVpn);
 
         // Use Wifi but not cell. Note the VPN is now unmetered and not suspended.
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mWiFiNetworkAgent.getNetwork() });
 
         vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
@@ -6470,7 +6680,7 @@
         assertDefaultNetworkCapabilities(userId, mWiFiNetworkAgent);
 
         // Use both again.
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
 
         vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
@@ -6485,7 +6695,7 @@
         vpnNetworkCallback.assertNoCallback();
 
         // Stop using WiFi. The VPN is suspended again.
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mCellNetworkAgent.getNetwork() });
         vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
                 (caps) -> caps.hasTransport(TRANSPORT_VPN)
@@ -6496,7 +6706,7 @@
         assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent);
 
         // Use both again.
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
 
         vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
@@ -6556,6 +6766,7 @@
         // By default, VPN is set to track default network (i.e. its underlying networks is null).
         // In case of no default network, VPN is considered metered.
         assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
 
         // Connect to Cell; Cell is the default network.
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
@@ -6613,6 +6824,7 @@
         NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
         assertNotNull("nc=" + nc, nc.getUids());
         assertEquals(nc.getUids(), uidRangesForUid(uid));
+        assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
 
         // Set an underlying network and expect to see the VPN transports change.
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -6628,12 +6840,11 @@
                 .thenReturn(UserHandle.getUid(RESTRICTED_USER, VPN_UID));
 
         final Intent addedIntent = new Intent(ACTION_USER_ADDED);
+        addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER));
         addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER);
 
         // Send a USER_ADDED broadcast for it.
-        // The BroadcastReceiver for this broadcast checks that is being run on the handler thread.
-        final Handler handler = new Handler(mCsHandlerThread.getLooper());
-        handler.post(() -> mServiceContext.sendBroadcast(addedIntent));
+        processBroadcastForVpn(addedIntent);
 
         // Expect that the VPN UID ranges contain both |uid| and the UID range for the newly-added
         // restricted user.
@@ -6656,8 +6867,9 @@
 
         // Send a USER_REMOVED broadcast and expect to lose the UID range for the restricted user.
         final Intent removedIntent = new Intent(ACTION_USER_REMOVED);
+        removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER));
         removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER);
-        handler.post(() -> mServiceContext.sendBroadcast(removedIntent));
+        processBroadcastForVpn(removedIntent);
 
         // Expect that the VPN gains the UID range for the restricted user, and that the capability
         // change made just before that (i.e., loss of TRANSPORT_WIFI) is preserved.
@@ -6697,8 +6909,8 @@
 
         // Enable always-on VPN lockdown. The main user loses network access because no VPN is up.
         final ArrayList<String> allowList = new ArrayList<>();
-        mService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE, true /* lockdown */,
-                allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE,
+                true /* lockdown */, allowList);
         waitForIdle();
         assertNull(mCm.getActiveNetworkForUid(uid));
         // This is arguably overspecified: a UID that is not running doesn't have an active network.
@@ -6713,10 +6925,9 @@
                 RESTRICTED_USER_INFO));
         // TODO: check that VPN app within restricted profile still has access, etc.
         final Intent addedIntent = new Intent(ACTION_USER_ADDED);
+        addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER));
         addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER);
-        final Handler handler = new Handler(mCsHandlerThread.getLooper());
-        handler.post(() -> mServiceContext.sendBroadcast(addedIntent));
-        waitForIdle();
+        processBroadcastForVpn(addedIntent);
         assertNull(mCm.getActiveNetworkForUid(uid));
         assertNull(mCm.getActiveNetworkForUid(restrictedUid));
 
@@ -6725,13 +6936,14 @@
 
         // Send a USER_REMOVED broadcast and expect to lose the UID range for the restricted user.
         final Intent removedIntent = new Intent(ACTION_USER_REMOVED);
+        removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER));
         removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER);
-        handler.post(() -> mServiceContext.sendBroadcast(removedIntent));
-        waitForIdle();
+        processBroadcastForVpn(removedIntent);
         assertNull(mCm.getActiveNetworkForUid(uid));
         assertNotNull(mCm.getActiveNetworkForUid(restrictedUid));
 
-        mService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */,
+                allowList);
         waitForIdle();
     }
 
@@ -6829,7 +7041,7 @@
         // Ensure VPN is now the active network.
         assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
         // VPN is using Cell
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mCellNetworkAgent.getNetwork() });
         waitForIdle();
 
@@ -6837,7 +7049,7 @@
         assertTrue(mCm.isActiveNetworkMetered());
 
         // VPN is now using WiFi
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mWiFiNetworkAgent.getNetwork() });
         waitForIdle();
 
@@ -6845,7 +7057,7 @@
         assertFalse(mCm.isActiveNetworkMetered());
 
         // VPN is using Cell | WiFi.
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
         waitForIdle();
 
@@ -6853,7 +7065,7 @@
         assertTrue(mCm.isActiveNetworkMetered());
 
         // VPN is using WiFi | Cell.
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mWiFiNetworkAgent.getNetwork(), mCellNetworkAgent.getNetwork() });
         waitForIdle();
 
@@ -6861,7 +7073,7 @@
         assertTrue(mCm.isActiveNetworkMetered());
 
         // VPN is not using any underlying networks.
-        mService.setUnderlyingNetworksForVpn(new Network[0]);
+        mMockVpn.setUnderlyingNetworks(new Network[0]);
         waitForIdle();
 
         // VPN without underlying networks is treated as metered.
@@ -6888,7 +7100,7 @@
         assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
 
         // VPN is tracking current platform default (WiFi).
-        mService.setUnderlyingNetworksForVpn(null);
+        mMockVpn.setUnderlyingNetworks(null);
         waitForIdle();
 
         // Despite VPN using WiFi (which is unmetered), VPN itself is marked as always metered.
@@ -6896,7 +7108,7 @@
 
 
         // VPN explicitly declares WiFi as its underlying network.
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mWiFiNetworkAgent.getNetwork() });
         waitForIdle();
 
@@ -7107,7 +7319,8 @@
         final int uid = Process.myUid();
         final int userId = UserHandle.getUserId(uid);
         final ArrayList<String> allowList = new ArrayList<>();
-        mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
+                allowList);
         waitForIdle();
 
         UidRangeParcel firstHalf = new UidRangeParcel(1, VPN_UID - 1);
@@ -7129,7 +7342,7 @@
         assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
 
         // Disable lockdown, expect to see the network unblocked.
-        mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
         callback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
         defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
         vpnUidCallback.assertNoCallback();
@@ -7142,7 +7355,8 @@
 
         // Add our UID to the allowlist and re-enable lockdown, expect network is not blocked.
         allowList.add(TEST_PACKAGE_NAME);
-        mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
+                allowList);
         callback.assertNoCallback();
         defaultCallback.assertNoCallback();
         vpnUidCallback.assertNoCallback();
@@ -7175,11 +7389,12 @@
 
         // Disable lockdown, remove our UID from the allowlist, and re-enable lockdown.
         // Everything should now be blocked.
-        mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
         waitForIdle();
         expectNetworkRejectNonSecureVpn(inOrder, false, piece1, piece2, piece3);
         allowList.clear();
-        mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
+                allowList);
         waitForIdle();
         expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
         defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
@@ -7192,7 +7407,7 @@
         assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
 
         // Disable lockdown. Everything is unblocked.
-        mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
         defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
         assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
@@ -7204,7 +7419,8 @@
 
         // Enable and disable an always-on VPN package without lockdown. Expect no changes.
         reset(mMockNetd);
-        mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */,
+                allowList);
         inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any());
         callback.assertNoCallback();
         defaultCallback.assertNoCallback();
@@ -7215,7 +7431,7 @@
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
 
-        mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
         inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any());
         callback.assertNoCallback();
         defaultCallback.assertNoCallback();
@@ -7227,7 +7443,8 @@
         assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
 
         // Enable lockdown and connect a VPN. The VPN is not blocked.
-        mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
+                allowList);
         defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
         assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
@@ -7273,10 +7490,14 @@
         when(mKeyStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile);
     }
 
-    private void establishLegacyLockdownVpn() throws Exception {
+    private void establishLegacyLockdownVpn(Network underlying) throws Exception {
+        // The legacy lockdown VPN only supports userId 0, and must have an underlying network.
+        assertNotNull(underlying);
+        mMockVpn.setVpnType(VpnManager.TYPE_VPN_LEGACY);
         // The legacy lockdown VPN only supports userId 0.
         final Set<UidRange> ranges = Collections.singleton(UidRange.createForUser(PRIMARY_USER));
         mMockVpn.registerAgent(ranges);
+        mMockVpn.setUnderlyingNetworks(new Network[]{underlying});
         mMockVpn.connect(true);
     }
 
@@ -7284,6 +7505,9 @@
     public void testLegacyLockdownVpn() throws Exception {
         mServiceContext.setPermission(
                 Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
+        // For LockdownVpnTracker to call registerSystemDefaultNetworkCallback.
+        mServiceContext.setPermission(
+                Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
 
         final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
         final TestNetworkCallback callback = new TestNetworkCallback();
@@ -7292,6 +7516,10 @@
         final TestNetworkCallback defaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultCallback);
 
+        final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback();
+        mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback,
+                new Handler(ConnectivityThread.getInstanceLooper()));
+
         // Pretend lockdown VPN was configured.
         setupLegacyLockdownVpn();
 
@@ -7303,10 +7531,9 @@
         // Send a USER_UNLOCKED broadcast so CS starts LockdownVpnTracker.
         final int userId = UserHandle.getUserId(Process.myUid());
         final Intent addedIntent = new Intent(ACTION_USER_UNLOCKED);
+        addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
         addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-        final Handler handler = new Handler(mCsHandlerThread.getLooper());
-        handler.post(() -> mServiceContext.sendBroadcast(addedIntent));
-        waitForIdle();
+        processBroadcastForVpn(addedIntent);
 
         // Lockdown VPN disables teardown and enables lockdown.
         assertFalse(mMockVpn.getEnableTeardown());
@@ -7323,6 +7550,7 @@
         mCellNetworkAgent.connect(false /* validated */);
         callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
+        systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
         waitForIdle();
         assertNull(mMockVpn.getAgent());
 
@@ -7334,6 +7562,8 @@
         mCellNetworkAgent.sendLinkProperties(cellLp);
         callback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         defaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+        systemDefaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED,
+                mCellNetworkAgent);
         waitForIdle();
         assertNull(mMockVpn.getAgent());
 
@@ -7343,6 +7573,7 @@
         mCellNetworkAgent.disconnect();
         callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+        systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         b1.expectBroadcast();
 
         // When lockdown VPN is active, the NetworkInfo state in CONNECTIVITY_ACTION is overwritten
@@ -7352,6 +7583,7 @@
         mCellNetworkAgent.connect(false /* validated */);
         callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
+        systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
         b1.expectBroadcast();
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
@@ -7374,9 +7606,10 @@
         mMockVpn.expectStartLegacyVpnRunner();
         b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED);
         ExpectedBroadcast b2 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED);
-        establishLegacyLockdownVpn();
+        establishLegacyLockdownVpn(mCellNetworkAgent.getNetwork());
         callback.expectAvailableThenValidatedCallbacks(mMockVpn);
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         NetworkCapabilities vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
         b1.expectBroadcast();
         b2.expectBroadcast();
@@ -7388,6 +7621,7 @@
         assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR));
         assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI));
         assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertVpnTransportInfo(vpnNc, VpnManager.TYPE_VPN_LEGACY);
 
         // Switch default network from cell to wifi. Expect VPN to disconnect and reconnect.
         final LinkProperties wifiLp = new LinkProperties();
@@ -7415,11 +7649,10 @@
         // fact that a VPN is connected should only result in the VPN itself being unblocked, not
         // any other network. Bug in isUidBlockedByVpn?
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasTransport(TRANSPORT_WIFI));
         callback.expectCallback(CallbackEntry.LOST, mMockVpn);
-        defaultCallback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasTransport(TRANSPORT_WIFI));
         defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
         defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
+        systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
 
         // While the VPN is reconnecting on the new network, everything is blocked.
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7430,9 +7663,10 @@
         // The VPN comes up again on wifi.
         b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED);
         b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
-        establishLegacyLockdownVpn();
+        establishLegacyLockdownVpn(mWiFiNetworkAgent.getNetwork());
         callback.expectAvailableThenValidatedCallbacks(mMockVpn);
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         b1.expectBroadcast();
         b2.expectBroadcast();
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7446,14 +7680,10 @@
         assertTrue(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Disconnect cell. Nothing much happens since it's not the default network.
-        // Whenever LockdownVpnTracker is connected, it will send a connected broadcast any time any
-        // NetworkInfo is updated. This is probably a bug.
-        // TODO: consider fixing this.
-        b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
         mCellNetworkAgent.disconnect();
-        b1.expectBroadcast();
         callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         defaultCallback.assertNoCallback();
+        systemDefaultCallback.assertNoCallback();
 
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
@@ -7463,6 +7693,7 @@
         b1 = expectConnectivityAction(TYPE_WIFI, 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);
@@ -8001,8 +8232,8 @@
         reset(mNetworkManagementService);
         mCellNetworkAgent.connect(true);
         networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        verify(mNetworkManagementService, times(1)).addIdleTimer(eq(MOBILE_IFNAME), anyInt(),
-                eq(NetworkCapabilities.TRANSPORT_CELLULAR));
+        verify(mMockNetd, times(1)).idletimerAddInterface(eq(MOBILE_IFNAME), anyInt(),
+                eq(Integer.toString(TRANSPORT_CELLULAR)));
 
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         final LinkProperties wifiLp = new LinkProperties();
@@ -8010,25 +8241,27 @@
         mWiFiNetworkAgent.sendLinkProperties(wifiLp);
 
         // Network switch
-        reset(mNetworkManagementService);
         mWiFiNetworkAgent.connect(true);
         networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         networkCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent);
         networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        verify(mNetworkManagementService, times(1)).addIdleTimer(eq(WIFI_IFNAME), anyInt(),
-                eq(NetworkCapabilities.TRANSPORT_WIFI));
-        verify(mNetworkManagementService, times(1)).removeIdleTimer(eq(MOBILE_IFNAME));
+        verify(mMockNetd, times(1)).idletimerAddInterface(eq(WIFI_IFNAME), anyInt(),
+                eq(Integer.toString(TRANSPORT_WIFI)));
+        verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
+                eq(Integer.toString(TRANSPORT_CELLULAR)));
 
         // Disconnect wifi and switch back to cell
-        reset(mNetworkManagementService);
+        reset(mMockNetd);
         mWiFiNetworkAgent.disconnect();
         networkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
         assertNoCallbacks(networkCallback);
-        verify(mNetworkManagementService, times(1)).removeIdleTimer(eq(WIFI_IFNAME));
-        verify(mNetworkManagementService, times(1)).addIdleTimer(eq(MOBILE_IFNAME), anyInt(),
-                eq(NetworkCapabilities.TRANSPORT_CELLULAR));
+        verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(WIFI_IFNAME), anyInt(),
+                eq(Integer.toString(TRANSPORT_WIFI)));
+        verify(mMockNetd, times(1)).idletimerAddInterface(eq(MOBILE_IFNAME), anyInt(),
+                eq(Integer.toString(TRANSPORT_CELLULAR)));
 
         // reconnect wifi
+        reset(mMockNetd);
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         wifiLp.setInterfaceName(WIFI_IFNAME);
         mWiFiNetworkAgent.sendLinkProperties(wifiLp);
@@ -8036,9 +8269,12 @@
         networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         networkCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent);
         networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        verify(mMockNetd, times(1)).idletimerAddInterface(eq(WIFI_IFNAME), anyInt(),
+                eq(Integer.toString(TRANSPORT_WIFI)));
+        verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
+                eq(Integer.toString(TRANSPORT_CELLULAR)));
 
         // Disconnect cell
-        reset(mNetworkManagementService);
         reset(mMockNetd);
         mCellNetworkAgent.disconnect();
         networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
@@ -8046,17 +8282,18 @@
         // sent as network being switched. Ensure rule removal for cell will not be triggered
         // unexpectedly before network being removed.
         waitForIdle();
-        verify(mNetworkManagementService, times(0)).removeIdleTimer(eq(MOBILE_IFNAME));
+        verify(mMockNetd, times(0)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
+                eq(Integer.toString(TRANSPORT_CELLULAR)));
         verify(mMockNetd, times(1)).networkDestroy(eq(mCellNetworkAgent.getNetwork().netId));
         verify(mMockDnsResolver, times(1))
                 .destroyNetworkCache(eq(mCellNetworkAgent.getNetwork().netId));
 
         // Disconnect wifi
         ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED);
-        reset(mNetworkManagementService);
         mWiFiNetworkAgent.disconnect();
         b.expectBroadcast();
-        verify(mNetworkManagementService, times(1)).removeIdleTimer(eq(WIFI_IFNAME));
+        verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(WIFI_IFNAME), anyInt(),
+                eq(Integer.toString(TRANSPORT_WIFI)));
 
         // Clean up
         mCm.unregisterNetworkCallback(networkCallback);
@@ -8377,7 +8614,8 @@
         when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(locationToggle);
 
         if (op != null) {
-            when(mAppOpsManager.noteOp(eq(op), eq(Process.myUid()), eq(mContext.getPackageName())))
+            when(mAppOpsManager.noteOp(eq(op), eq(Process.myUid()),
+                    eq(mContext.getPackageName()), eq(getAttributionTag()), anyString()))
                 .thenReturn(AppOpsManager.MODE_ALLOWED);
         }
 
@@ -8390,7 +8628,7 @@
         final NetworkCapabilities netCap = new NetworkCapabilities().setOwnerUid(ownerUid);
 
         return mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled(
-                netCap, callerUid, mContext.getPackageName()).getOwnerUid();
+                netCap, callerUid, mContext.getPackageName(), getAttributionTag()).getOwnerUid();
     }
 
     private void verifyWifiInfoCopyNetCapsForCallerPermission(
@@ -8400,7 +8638,7 @@
         final NetworkCapabilities netCap = new NetworkCapabilities().setTransportInfo(wifiInfo);
 
         mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled(
-                netCap, callerUid, mContext.getPackageName());
+                netCap, callerUid, mContext.getPackageName(), getAttributionTag());
         verify(wifiInfo).makeCopy(eq(shouldMakeCopyWithLocationSensitiveFieldsParcelable));
     }
 
@@ -8519,11 +8757,7 @@
         final int myUid = Process.myUid();
         setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_PLATFORM);
 
-        try {
-            mService.getConnectionOwnerUid(getTestConnectionInfo());
-            fail("Expected SecurityException for non-VpnService app");
-        } catch (SecurityException expected) {
-        }
+        assertEquals(INVALID_UID, mService.getConnectionOwnerUid(getTestConnectionInfo()));
     }
 
     @Test
@@ -8531,11 +8765,7 @@
         final int myUid = Process.myUid();
         setupConnectionOwnerUidAsVpnApp(myUid + 1, VpnManager.TYPE_VPN_SERVICE);
 
-        try {
-            mService.getConnectionOwnerUid(getTestConnectionInfo());
-            fail("Expected SecurityException for non-VpnService app");
-        } catch (SecurityException expected) {
-        }
+        assertEquals(INVALID_UID, mService.getConnectionOwnerUid(getTestConnectionInfo()));
     }
 
     @Test
@@ -8748,7 +8978,7 @@
         setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
                 Manifest.permission.ACCESS_FINE_LOCATION);
 
-        assertTrue(mService.setUnderlyingNetworksForVpn(new Network[] {naiWithoutUid.network}));
+        assertTrue(mMockVpn.setUnderlyingNetworks(new Network[] {naiWithoutUid.network}));
         waitForIdle();
         assertTrue(
                 "Active VPN permission not applied",
@@ -8756,7 +8986,7 @@
                         Process.myPid(), Process.myUid(), naiWithoutUid,
                         mContext.getOpPackageName()));
 
-        assertTrue(mService.setUnderlyingNetworksForVpn(null));
+        assertTrue(mMockVpn.setUnderlyingNetworks(null));
         waitForIdle();
         assertFalse(
                 "VPN shouldn't receive callback on non-underlying network",
@@ -9232,4 +9462,884 @@
         }
         fail("TOO_MANY_REQUESTS never thrown");
     }
+
+    private void mockGetApplicationInfo(@NonNull final String packageName, @NonNull final int uid)
+            throws Exception {
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = uid;
+        when(mPackageManager.getApplicationInfo(eq(packageName), anyInt()))
+                .thenReturn(applicationInfo);
+    }
+
+    private void mockHasSystemFeature(@NonNull final String featureName,
+            @NonNull final boolean hasFeature) {
+        when(mPackageManager.hasSystemFeature(eq(featureName)))
+                .thenReturn(hasFeature);
+    }
+
+    private UidRange getNriFirstUidRange(
+            @NonNull final ConnectivityService.NetworkRequestInfo nri) {
+        return nri.mRequests.get(0).networkCapabilities.getUids().iterator().next();
+    }
+
+    private OemNetworkPreferences createDefaultOemNetworkPreferences(
+            @OemNetworkPreferences.OemNetworkPreference final int preference)
+            throws Exception {
+        // Arrange PackageManager mocks
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+
+        // Build OemNetworkPreferences object
+        return new OemNetworkPreferences.Builder()
+                .addNetworkPreference(TEST_PACKAGE_NAME, preference)
+                .build();
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceUninitializedThrowsError()
+            throws PackageManager.NameNotFoundException {
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OEM_NETWORK_PREFERENCE_UNINITIALIZED;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        assertThrows(IllegalArgumentException.class,
+                () -> mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest)));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceOemPaid()
+            throws Exception {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfRequests = 3;
+
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OEM_NETWORK_PREFERENCE_OEM_PAID;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest));
+
+        final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfRequests, mRequests.size());
+        assertTrue(mRequests.get(0).isListen());
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_VALIDATED));
+        assertTrue(mRequests.get(1).isRequest());
+        assertTrue(mRequests.get(1).hasCapability(NET_CAPABILITY_OEM_PAID));
+        assertTrue(mRequests.get(2).isRequest());
+        assertTrue(mService.getDefaultRequest().networkCapabilities.equalsNetCapabilities(
+                mRequests.get(2).networkCapabilities));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceOemPaidNoFallback()
+            throws Exception {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfRequests = 2;
+
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest));
+
+        final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfRequests, mRequests.size());
+        assertTrue(mRequests.get(0).isListen());
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_VALIDATED));
+        assertTrue(mRequests.get(1).isRequest());
+        assertTrue(mRequests.get(1).hasCapability(NET_CAPABILITY_OEM_PAID));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceOemPaidOnly()
+            throws Exception {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfRequests = 1;
+
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest));
+
+        final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfRequests, mRequests.size());
+        assertTrue(mRequests.get(0).isRequest());
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PAID));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryPreferenceOemPrivateOnly()
+            throws Exception {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfRequests = 1;
+
+        @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+                OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory()
+                        .createNrisFromOemNetworkPreferences(
+                                createDefaultOemNetworkPreferences(prefToTest));
+
+        final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfRequests, mRequests.size());
+        assertTrue(mRequests.get(0).isRequest());
+        assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PRIVATE));
+        assertFalse(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PAID));
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryCreatesCorrectNumOfNris()
+            throws Exception {
+        // Expectations
+        final int expectedNumOfNris = 2;
+
+        // Arrange PackageManager mocks
+        final String testPackageName2 = "com.google.apps.dialer";
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+        mockGetApplicationInfo(testPackageName2, TEST_PACKAGE_UID);
+
+        // Build OemNetworkPreferences object
+        final int testOemPref = OEM_NETWORK_PREFERENCE_OEM_PAID;
+        final int testOemPref2 = OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+        final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
+                .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref)
+                .addNetworkPreference(testPackageName2, testOemPref2)
+                .build();
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref);
+
+        assertNotNull(nris);
+        assertEquals(expectedNumOfNris, nris.size());
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryCorrectlySetsUids()
+            throws Exception {
+        // Arrange PackageManager mocks
+        final String testPackageName2 = "com.google.apps.dialer";
+        final int testPackageNameUid2 = 456;
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+        mockGetApplicationInfo(testPackageName2, testPackageNameUid2);
+
+        // Build OemNetworkPreferences object
+        final int testOemPref = OEM_NETWORK_PREFERENCE_OEM_PAID;
+        final int testOemPref2 = OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+        final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
+                .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref)
+                .addNetworkPreference(testPackageName2, testOemPref2)
+                .build();
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final List<ConnectivityService.NetworkRequestInfo> nris =
+                new ArrayList<>(
+                        mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(
+                                pref));
+
+        // Sort by uid to access nris by index
+        nris.sort(Comparator.comparingInt(nri -> getNriFirstUidRange(nri).start));
+        assertEquals(TEST_PACKAGE_UID, getNriFirstUidRange(nris.get(0)).start);
+        assertEquals(TEST_PACKAGE_UID, getNriFirstUidRange(nris.get(0)).stop);
+        assertEquals(testPackageNameUid2, getNriFirstUidRange(nris.get(1)).start);
+        assertEquals(testPackageNameUid2, getNriFirstUidRange(nris.get(1)).stop);
+    }
+
+    @Test
+    public void testOemNetworkRequestFactoryAddsPackagesToCorrectPreference()
+            throws Exception {
+        // Expectations
+        final int expectedNumOfNris = 1;
+        final int expectedNumOfAppUids = 2;
+
+        // Arrange PackageManager mocks
+        final String testPackageName2 = "com.google.apps.dialer";
+        final int testPackageNameUid2 = 456;
+        mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+        mockGetApplicationInfo(testPackageName2, testPackageNameUid2);
+
+        // Build OemNetworkPreferences object
+        final int testOemPref = OEM_NETWORK_PREFERENCE_OEM_PAID;
+        final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
+                .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref)
+                .addNetworkPreference(testPackageName2, testOemPref)
+                .build();
+
+        // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+        final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+                mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref);
+
+        assertEquals(expectedNumOfNris, nris.size());
+        assertEquals(expectedNumOfAppUids,
+                nris.iterator().next().mRequests.get(0).networkCapabilities.getUids().size());
+    }
+
+    @Test
+    public void testSetOemNetworkPreferenceNullListenerAndPrefParamThrowsNpe() {
+        mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true);
+
+        // Act on ConnectivityService.setOemNetworkPreference()
+        assertThrows(NullPointerException.class,
+                () -> mService.setOemNetworkPreference(
+                        null,
+                        null));
+    }
+
+    @Test
+    public void testSetOemNetworkPreferenceFailsForNonAutomotive()
+            throws Exception {
+        mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false);
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+
+        // Act on ConnectivityService.setOemNetworkPreference()
+        assertThrows(UnsupportedOperationException.class,
+                () -> mService.setOemNetworkPreference(
+                        createDefaultOemNetworkPreferences(networkPref),
+                        new TestOemListenerCallback()));
+    }
+
+    private void setOemNetworkPreferenceAgentConnected(final int transportType,
+            final boolean connectAgent) throws Exception {
+        switch(transportType) {
+            // Corresponds to a metered cellular network. Will be used for the default network.
+            case TRANSPORT_CELLULAR:
+                if (!connectAgent) {
+                    mCellNetworkAgent.disconnect();
+                    break;
+                }
+                mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+                mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+                mCellNetworkAgent.connect(true);
+                break;
+            // Corresponds to a restricted ethernet network with OEM_PAID/OEM_PRIVATE.
+            case TRANSPORT_ETHERNET:
+                if (!connectAgent) {
+                    stopOemManagedNetwork();
+                    break;
+                }
+                startOemManagedNetwork(true);
+                break;
+            // Corresponds to unmetered Wi-Fi.
+            case TRANSPORT_WIFI:
+                if (!connectAgent) {
+                    mWiFiNetworkAgent.disconnect();
+                    break;
+                }
+                mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+                mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+                mWiFiNetworkAgent.connect(true);
+                break;
+            default:
+                throw new AssertionError("Unsupported transport type passed in.");
+
+        }
+        waitForIdle();
+    }
+
+    private void startOemManagedNetwork(final boolean isOemPaid) throws Exception {
+        mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
+        mEthernetNetworkAgent.addCapability(
+                isOemPaid ? NET_CAPABILITY_OEM_PAID : NET_CAPABILITY_OEM_PRIVATE);
+        mEthernetNetworkAgent.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+        mEthernetNetworkAgent.connect(true);
+    }
+
+    private void stopOemManagedNetwork() {
+        mEthernetNetworkAgent.disconnect();
+        waitForIdle();
+    }
+
+    private void verifyMultipleDefaultNetworksTracksCorrectly(
+            final int expectedOemRequestsSize,
+            @NonNull final Network expectedDefaultNetwork,
+            @NonNull final Network expectedPerAppNetwork) {
+        // The current test setup assumes two tracked default network requests; one for the default
+        // network and the other for the OEM network preference being tested. This will be validated
+        // each time to confirm it doesn't change under test.
+        final int expectedDefaultNetworkRequestsSize = 2;
+        assertEquals(expectedDefaultNetworkRequestsSize, mService.mDefaultNetworkRequests.size());
+        for (final ConnectivityService.NetworkRequestInfo defaultRequest
+                : mService.mDefaultNetworkRequests) {
+            final Network defaultNetwork = defaultRequest.getSatisfier() == null
+                    ? null : defaultRequest.getSatisfier().network();
+            // If this is the default request.
+            if (defaultRequest == mService.mDefaultRequest) {
+                assertEquals(
+                        expectedDefaultNetwork,
+                        defaultNetwork);
+                // Make sure this value doesn't change.
+                assertEquals(1, defaultRequest.mRequests.size());
+                continue;
+            }
+            assertEquals(expectedPerAppNetwork, defaultNetwork);
+            assertEquals(expectedOemRequestsSize, defaultRequest.mRequests.size());
+        }
+    }
+
+    private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(
+            @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup)
+            throws Exception {
+        final int testPackageNameUid = 123;
+        final String testPackageName = "per.app.defaults.package";
+        setupMultipleDefaultNetworksForOemNetworkPreferenceTest(
+                networkPrefToSetup, testPackageNameUid, testPackageName);
+    }
+
+    private void setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(
+            @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup)
+            throws Exception {
+        final int testPackageNameUid = Process.myUid();
+        final String testPackageName = "per.app.defaults.package";
+        setupMultipleDefaultNetworksForOemNetworkPreferenceTest(
+                networkPrefToSetup, testPackageNameUid, testPackageName);
+    }
+
+    private void setupMultipleDefaultNetworksForOemNetworkPreferenceTest(
+            @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup,
+            final int testPackageUid, @NonNull final String testPackageName) throws Exception {
+        // Only the default request should be included at start.
+        assertEquals(1, mService.mDefaultNetworkRequests.size());
+
+        final UidRangeParcel[] uidRanges =
+                toUidRangeStableParcels(uidRangesForUid(testPackageUid));
+        setupSetOemNetworkPreferenceForPreferenceTest(
+                networkPrefToSetup, uidRanges, testPackageName);
+    }
+
+    private void setupSetOemNetworkPreferenceForPreferenceTest(
+            @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup,
+            @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);
+
+        // Build OemNetworkPreferences object
+        final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
+                .addNetworkPreference(testPackageName, networkPrefToSetup)
+                .build();
+
+        // Act on ConnectivityService.setOemNetworkPreference()
+        final TestOemListenerCallback mOnSetOemNetworkPreferenceTestListener =
+                new TestOemListenerCallback();
+        mService.setOemNetworkPreference(pref, mOnSetOemNetworkPreferenceTestListener);
+
+        // Verify call returned successfully
+        mOnSetOemNetworkPreferenceTestListener.expectOnComplete();
+    }
+
+    private static class TestOemListenerCallback implements IOnSetOemNetworkPreferenceListener {
+        final CompletableFuture<Object> mDone = new CompletableFuture<>();
+
+        @Override
+        public void onComplete() {
+            mDone.complete(new Object());
+        }
+
+        void expectOnComplete() throws Exception {
+            try {
+                mDone.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            } catch (TimeoutException e) {
+                fail("Expected onComplete() not received after " + TIMEOUT_MS + " ms");
+            }
+        }
+
+        @Override
+        public IBinder asBinder() {
+            return null;
+        }
+    }
+
+    @Test
+    public void testMultiDefaultGetActiveNetworkIsCorrect() throws Exception {
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
+        final int expectedOemPrefRequestSize = 1;
+
+        // Setup the test process to use networkPref for their default network.
+        setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
+
+        // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
+        // The active network for the default should be null at this point as this is a retricted
+        // network.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
+        verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+                null,
+                mEthernetNetworkAgent.getNetwork());
+
+        // Verify that the active network is correct
+        verifyActiveNetwork(TRANSPORT_ETHERNET);
+    }
+
+    @Test
+    public void testMultiDefaultIsActiveNetworkMeteredIsCorrect() throws Exception {
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
+        final int expectedOemPrefRequestSize = 1;
+
+        // Setup the test process to use networkPref for their default network.
+        setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
+
+        // Returns true by default when no network is available.
+        assertTrue(mCm.isActiveNetworkMetered());
+
+        // Connect to an unmetered restricted network that will only be available to the OEM pref.
+        mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
+        mEthernetNetworkAgent.addCapability(NET_CAPABILITY_OEM_PAID);
+        mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mEthernetNetworkAgent.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+        mEthernetNetworkAgent.connect(true);
+        waitForIdle();
+
+        verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+                null,
+                mEthernetNetworkAgent.getNetwork());
+
+        assertFalse(mCm.isActiveNetworkMetered());
+    }
+
+    @Test
+    public void testPerAppDefaultRegisterDefaultNetworkCallback() throws Exception {
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
+        final int expectedOemPrefRequestSize = 1;
+        final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
+
+        // Register the default network callback before the pref is already set. This means that
+        // the policy will be applied to the callback on setOemNetworkPreference().
+        mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
+        defaultNetworkCallback.assertNoCallback();
+
+        // Setup the test process to use networkPref for their default network.
+        setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
+
+        // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
+        // The active nai for the default is null at this point as this is a restricted network.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
+        verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+                null,
+                mEthernetNetworkAgent.getNetwork());
+
+        // At this point with a restricted network used, the available callback should trigger
+        defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
+        assertEquals(defaultNetworkCallback.getLastAvailableNetwork(),
+                mEthernetNetworkAgent.getNetwork());
+
+        // Now bring down the default network which should trigger a LOST callback.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
+
+        // At this point, with no network is available, the lost callback should trigger
+        defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent);
+
+        // Confirm we can unregister without issues.
+        mCm.unregisterNetworkCallback(defaultNetworkCallback);
+    }
+
+    @Test
+    public void testPerAppDefaultRegisterDefaultNetworkCallbackAfterPrefSet() throws Exception {
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
+        final int expectedOemPrefRequestSize = 1;
+        final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
+
+        // Setup the test process to use networkPref for their default network.
+        setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
+
+        // Register the default network callback after the pref is already set. This means that
+        // the policy will be applied to the callback on requestNetwork().
+        mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
+        defaultNetworkCallback.assertNoCallback();
+
+        // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
+        // The active nai for the default is null at this point as this is a restricted network.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
+        verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+                null,
+                mEthernetNetworkAgent.getNetwork());
+
+        // At this point with a restricted network used, the available callback should trigger
+        defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
+        assertEquals(defaultNetworkCallback.getLastAvailableNetwork(),
+                mEthernetNetworkAgent.getNetwork());
+
+        // Now bring down the default network which should trigger a LOST callback.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
+
+        // At this point, with no network is available, the lost callback should trigger
+        defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent);
+
+        // Confirm we can unregister without issues.
+        mCm.unregisterNetworkCallback(defaultNetworkCallback);
+    }
+
+    @Test
+    public void testPerAppDefaultRegisterDefaultNetworkCallbackDoesNotFire() throws Exception {
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
+        final int expectedOemPrefRequestSize = 1;
+        final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
+        final int userId = UserHandle.getUserId(Process.myUid());
+
+        mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
+        defaultNetworkCallback.assertNoCallback();
+
+        // Setup a process different than the test process to use the default network. This means
+        // that the defaultNetworkCallback won't be tracked by the per-app policy.
+        setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(networkPref);
+
+        // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
+        // The active nai for the default is null at this point as this is a restricted network.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
+        verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+                null,
+                mEthernetNetworkAgent.getNetwork());
+
+        // As this callback does not have access to the OEM_PAID network, it will not fire.
+        defaultNetworkCallback.assertNoCallback();
+        assertDefaultNetworkCapabilities(userId /* no networks */);
+
+        // Bring up unrestricted cellular. This should now satisfy the default network.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
+        verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+                mCellNetworkAgent.getNetwork(),
+                mEthernetNetworkAgent.getNetwork());
+
+        // At this point with an unrestricted network used, the available callback should trigger
+        defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        assertEquals(defaultNetworkCallback.getLastAvailableNetwork(),
+                mCellNetworkAgent.getNetwork());
+        assertDefaultNetworkCapabilities(userId, mCellNetworkAgent);
+
+        // Now bring down the per-app network.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
+
+        // Since the callback didn't use the per-app network, no callback should fire.
+        defaultNetworkCallback.assertNoCallback();
+
+        // Now bring down the default network.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
+
+        // As this callback was tracking the default, this should now trigger.
+        defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+
+        // Confirm we can unregister without issues.
+        mCm.unregisterNetworkCallback(defaultNetworkCallback);
+    }
+
+    private void verifySetOemNetworkPreferenceForPreference(
+            @NonNull final UidRangeParcel[] uidRanges,
+            final int addUidRangesNetId,
+            final int addUidRangesTimes,
+            final int removeUidRangesNetId,
+            final int removeUidRangesTimes,
+            final boolean shouldDestroyNetwork) throws RemoteException {
+        final boolean useAnyIdForAdd = OEM_PREF_ANY_NET_ID == addUidRangesNetId;
+        final boolean useAnyIdForRemove = OEM_PREF_ANY_NET_ID == removeUidRangesNetId;
+
+        // Validate netd.
+        verify(mMockNetd, times(addUidRangesTimes))
+                .networkAddUidRanges(
+                        (useAnyIdForAdd ? anyInt() : eq(addUidRangesNetId)), eq(uidRanges));
+        verify(mMockNetd, times(removeUidRangesTimes))
+                .networkRemoveUidRanges(
+                        (useAnyIdForRemove ? anyInt() : eq(removeUidRangesNetId)), eq(uidRanges));
+        if (shouldDestroyNetwork) {
+            verify(mMockNetd, times(1))
+                    .networkDestroy((useAnyIdForRemove ? anyInt() : eq(removeUidRangesNetId)));
+        }
+        reset(mMockNetd);
+    }
+
+    /**
+     * Test the tracked default requests clear previous OEM requests on setOemNetworkPreference().
+     * @throws Exception
+     */
+    @Test
+    public void testSetOemNetworkPreferenceClearPreviousOemValues() throws Exception {
+        @OemNetworkPreferences.OemNetworkPreference int networkPref =
+                OEM_NETWORK_PREFERENCE_OEM_PAID;
+        final int testPackageUid = 123;
+        final String testPackageName = "com.google.apps.contacts";
+        final UidRangeParcel[] uidRanges =
+                toUidRangeStableParcels(uidRangesForUid(testPackageUid));
+
+        // Validate the starting requests only includes the fallback request.
+        assertEquals(1, mService.mDefaultNetworkRequests.size());
+
+        // Add an OEM default network request to track.
+        setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, testPackageName);
+
+        // Two requests should exist, one for the fallback and one for the pref.
+        assertEquals(2, mService.mDefaultNetworkRequests.size());
+
+        networkPref = OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+        setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, testPackageName);
+
+        // Two requests should still exist validating the previous per-app request was replaced.
+        assertEquals(2, mService.mDefaultNetworkRequests.size());
+    }
+
+    /**
+     * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID following in order:
+     * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID -> fallback
+     * @throws Exception
+     */
+    @Test
+    public void testMultilayerForPreferenceOemPaidEvaluatesCorrectly()
+            throws Exception {
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OEM_NETWORK_PREFERENCE_OEM_PAID;
+
+        // Arrange PackageManager mocks
+        final int testPackageNameUid = 123;
+        final UidRangeParcel[] uidRanges =
+                toUidRangeStableParcels(uidRangesForUid(testPackageNameUid));
+        setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME);
+
+        // Verify the starting state. No networks should be connected.
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Test lowest to highest priority requests.
+        // Bring up metered cellular. This will satisfy the fallback network.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                mEthernetNetworkAgent.getNetwork().netId, 1 /* times */,
+                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                mWiFiNetworkAgent.getNetwork().netId, 1 /* times */,
+                mEthernetNetworkAgent.getNetwork().netId, 1 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Disconnecting OEM_PAID should have no effect as it is lower in priority then unmetered.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
+        // netd should not be called as default networks haven't changed.
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Disconnecting unmetered should put PANS on lowest priority fallback request.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                mWiFiNetworkAgent.getNetwork().netId, 0 /* times */,
+                true /* shouldDestroyNetwork */);
+
+        // Disconnecting the fallback network should result in no connectivity.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                mCellNetworkAgent.getNetwork().netId, 0 /* times */,
+                true /* shouldDestroyNetwork */);
+    }
+
+    /**
+     * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK following in order:
+     * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID
+     * @throws Exception
+     */
+    @Test
+    public void testMultilayerForPreferenceOemPaidNoFallbackEvaluatesCorrectly()
+            throws Exception {
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+
+        // Arrange PackageManager mocks
+        final int testPackageNameUid = 123;
+        final UidRangeParcel[] uidRanges =
+                toUidRangeStableParcels(uidRangesForUid(testPackageNameUid));
+        setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME);
+
+        // Verify the starting state. This preference doesn't support using the fallback network
+        // therefore should be on the disconnected network as it has no networks to connect to.
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                mService.mNoServiceNetwork.network.getNetId(), 1 /* times */,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Test lowest to highest priority requests.
+        // Bring up metered cellular. This will satisfy the fallback network.
+        // This preference should not use this network as it doesn't support fallback usage.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                mEthernetNetworkAgent.getNetwork().netId, 1 /* times */,
+                mService.mNoServiceNetwork.network.getNetId(), 1 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                mWiFiNetworkAgent.getNetwork().netId, 1 /* times */,
+                mEthernetNetworkAgent.getNetwork().netId, 1 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Disconnecting unmetered should put PANS on OEM_PAID.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                mEthernetNetworkAgent.getNetwork().netId, 1 /* times */,
+                mWiFiNetworkAgent.getNetwork().netId, 0 /* times */,
+                true /* shouldDestroyNetwork */);
+
+        // Disconnecting OEM_PAID should result in no connectivity.
+        // OEM_PAID_NO_FALLBACK not supporting a fallback now uses the disconnected network.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                mService.mNoServiceNetwork.network.getNetId(), 1 /* times */,
+                mEthernetNetworkAgent.getNetwork().netId, 0 /* times */,
+                true /* shouldDestroyNetwork */);
+    }
+
+    /**
+     * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY following in order:
+     * NET_CAPABILITY_OEM_PAID
+     * This preference should only apply to OEM_PAID networks.
+     * @throws Exception
+     */
+    @Test
+    public void testMultilayerForPreferenceOemPaidOnlyEvaluatesCorrectly()
+            throws Exception {
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
+
+        // Arrange PackageManager mocks
+        final int testPackageNameUid = 123;
+        final UidRangeParcel[] uidRanges =
+                toUidRangeStableParcels(uidRangesForUid(testPackageNameUid));
+        setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME);
+
+        // Verify the starting state. This preference doesn't support using the fallback network
+        // therefore should be on the disconnected network as it has no networks to connect to.
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                mService.mNoServiceNetwork.network.getNetId(), 1 /* times */,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Bring up metered cellular. This should not apply to this preference.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Bring up unmetered Wi-Fi. This should not apply to this preference.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                mEthernetNetworkAgent.getNetwork().netId, 1 /* times */,
+                mService.mNoServiceNetwork.network.getNetId(), 1 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Disconnecting OEM_PAID should result in no connectivity.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                mService.mNoServiceNetwork.network.getNetId(), 1 /* times */,
+                mEthernetNetworkAgent.getNetwork().netId, 0 /* times */,
+                true /* shouldDestroyNetwork */);
+    }
+
+    /**
+     * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY following in order:
+     * NET_CAPABILITY_OEM_PRIVATE
+     * This preference should only apply to OEM_PRIVATE networks.
+     * @throws Exception
+     */
+    @Test
+    public void testMultilayerForPreferenceOemPrivateOnlyEvaluatesCorrectly()
+            throws Exception {
+        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+                OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+
+        // Arrange PackageManager mocks
+        final int testPackageNameUid = 123;
+        final UidRangeParcel[] uidRanges =
+                toUidRangeStableParcels(uidRangesForUid(testPackageNameUid));
+        setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME);
+
+        // Verify the starting state. This preference doesn't support using the fallback network
+        // therefore should be on the disconnected network as it has no networks to connect to.
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                mService.mNoServiceNetwork.network.getNetId(), 1 /* times */,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Bring up metered cellular. This should not apply to this preference.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Bring up unmetered Wi-Fi. This should not apply to this preference.
+        setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                OEM_PREF_ANY_NET_ID, 0 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Bring up ethernet with OEM_PRIVATE. This will satisfy NET_CAPABILITY_OEM_PRIVATE.
+        startOemManagedNetwork(false);
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                mEthernetNetworkAgent.getNetwork().netId, 1 /* times */,
+                mService.mNoServiceNetwork.network.getNetId(), 1 /* times */,
+                false /* shouldDestroyNetwork */);
+
+        // Disconnecting OEM_PRIVATE should result in no connectivity.
+        stopOemManagedNetwork();
+        verifySetOemNetworkPreferenceForPreference(uidRanges,
+                mService.mNoServiceNetwork.network.getNetId(), 1 /* times */,
+                mEthernetNetworkAgent.getNetwork().netId, 0 /* times */,
+                true /* shouldDestroyNetwork */);
+    }
 }
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 89871fa..32c95f1 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -37,6 +37,7 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.InetAddresses;
 import android.net.InterfaceConfigurationParcel;
@@ -49,6 +50,7 @@
 import android.net.IpSecTunnelInterfaceResponse;
 import android.net.IpSecUdpEncapResponse;
 import android.net.LinkAddress;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
@@ -57,6 +59,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.IpSecService.TunnelInterfaceRecord;
+
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -113,6 +117,7 @@
     };
 
     AppOpsManager mMockAppOps = mock(AppOpsManager.class);
+    ConnectivityManager mMockConnectivityMgr = mock(ConnectivityManager.class);
 
     MockContext mMockContext = new MockContext() {
         @Override
@@ -120,12 +125,22 @@
             switch(name) {
                 case Context.APP_OPS_SERVICE:
                     return mMockAppOps;
+                case Context.CONNECTIVITY_SERVICE:
+                    return mMockConnectivityMgr;
                 default:
                     return null;
             }
         }
 
         @Override
+        public String getSystemServiceName(Class<?> serviceClass) {
+            if (ConnectivityManager.class == serviceClass) {
+                return Context.CONNECTIVITY_SERVICE;
+            }
+            return null;
+        }
+
+        @Override
         public PackageManager getPackageManager() {
             return mMockPkgMgr;
         }
@@ -162,6 +177,10 @@
             new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
     private static final int REMOTE_ENCAP_PORT = 4500;
 
+    private static final String BLESSED_PACKAGE = "blessedPackage";
+    private static final String SYSTEM_PACKAGE = "systemPackage";
+    private static final String BAD_PACKAGE = "badPackage";
+
     public IpSecServiceParameterizedTest(
             String sourceAddr, String destAddr, String localInnerAddr, int family) {
         mSourceAddr = sourceAddr;
@@ -184,15 +203,15 @@
         when(mMockPkgMgr.hasSystemFeature(anyString())).thenReturn(true);
 
         // A package granted the AppOp for MANAGE_IPSEC_TUNNELS will be MODE_ALLOWED.
-        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("blessedPackage")))
-            .thenReturn(AppOpsManager.MODE_ALLOWED);
+        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BLESSED_PACKAGE)))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
         // A system package will not be granted the app op, so this should fall back to
         // a permissions check, which should pass.
-        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("systemPackage")))
-            .thenReturn(AppOpsManager.MODE_DEFAULT);
+        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(SYSTEM_PACKAGE)))
+                .thenReturn(AppOpsManager.MODE_DEFAULT);
         // A mismatch between the package name and the UID will return MODE_IGNORED.
-        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("badPackage")))
-            .thenReturn(AppOpsManager.MODE_IGNORED);
+        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BAD_PACKAGE)))
+                .thenReturn(AppOpsManager.MODE_IGNORED);
     }
 
     //TODO: Add a test to verify SPI.
@@ -348,7 +367,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         assertEquals(IpSecManager.Status.OK, createTransformResp.status);
 
         verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
@@ -362,7 +381,7 @@
         ipSecConfig.setAuthenticatedEncryption(AEAD_ALGO);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         assertEquals(IpSecManager.Status.OK, createTransformResp.status);
 
         verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
@@ -380,14 +399,14 @@
 
         if (mFamily == AF_INET) {
             IpSecTransformResponse createTransformResp =
-                    mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                    mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
             assertEquals(IpSecManager.Status.OK, createTransformResp.status);
 
             verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port);
         } else {
             try {
                 IpSecTransformResponse createTransformResp =
-                        mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                        mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
                 fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6");
             } catch (IllegalArgumentException expected) {
             }
@@ -406,14 +425,14 @@
 
         if (mFamily == AF_INET) {
             IpSecTransformResponse createTransformResp =
-                    mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                    mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
             assertEquals(IpSecManager.Status.OK, createTransformResp.status);
 
             verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port);
         } else {
             try {
                 IpSecTransformResponse createTransformResp =
-                        mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                        mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
                 fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6");
             } catch (IllegalArgumentException expected) {
             }
@@ -427,12 +446,12 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         assertEquals(IpSecManager.Status.OK, createTransformResp.status);
 
         // Attempting to create transform a second time with the same SPIs should throw an error...
         try {
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+            mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
                 fail("IpSecService should have thrown an error for reuse of SPI");
         } catch (IllegalStateException expected) {
         }
@@ -440,7 +459,7 @@
         // ... even if the transform is deleted
         mIpSecService.deleteTransform(createTransformResp.resourceId);
         try {
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+            mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
                 fail("IpSecService should have thrown an error for reuse of SPI");
         } catch (IllegalStateException expected) {
         }
@@ -453,7 +472,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
         assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent);
         mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -477,7 +496,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         mIpSecService.deleteTransform(createTransformResp.resourceId);
 
         verify(mMockNetd, times(1))
@@ -525,7 +544,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
 
         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
         IpSecService.RefcountedResource refcountedRecord =
@@ -572,7 +591,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
 
         if (closeSpiBeforeApply) {
             mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -602,7 +621,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
 
         // Close SPI record
         mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -651,7 +670,7 @@
     @Test
     public void testCreateTunnelInterface() throws Exception {
         IpSecTunnelInterfaceResponse createTunnelResp =
-                createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
 
         // Check that we have stored the tracking object, and retrieve it
         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
@@ -675,11 +694,11 @@
     @Test
     public void testDeleteTunnelInterface() throws Exception {
         IpSecTunnelInterfaceResponse createTunnelResp =
-                createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
 
         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
 
-        mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, "blessedPackage");
+        mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, BLESSED_PACKAGE);
 
         // Verify quota and RefcountedResource objects cleaned up
         assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent);
@@ -692,10 +711,73 @@
         }
     }
 
+    private Network createFakeUnderlyingNetwork(String interfaceName) {
+        final Network fakeNetwork = new Network(1000);
+        final LinkProperties fakeLp = new LinkProperties();
+        fakeLp.setInterfaceName(interfaceName);
+        when(mMockConnectivityMgr.getLinkProperties(eq(fakeNetwork))).thenReturn(fakeLp);
+        return fakeNetwork;
+    }
+
+    @Test
+    public void testSetNetworkForTunnelInterface() throws Exception {
+        final IpSecTunnelInterfaceResponse createTunnelResp =
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+        final Network newFakeNetwork = createFakeUnderlyingNetwork("newFakeNetworkInterface");
+        final int tunnelIfaceResourceId = createTunnelResp.resourceId;
+        mIpSecService.setNetworkForTunnelInterface(
+                tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
+
+        final IpSecService.UserRecord userRecord =
+                mIpSecService.mUserResourceTracker.getUserRecord(mUid);
+        assertEquals(1, userRecord.mTunnelQuotaTracker.mCurrent);
+
+        final TunnelInterfaceRecord tunnelInterfaceInfo =
+                userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId);
+        assertEquals(newFakeNetwork, tunnelInterfaceInfo.getUnderlyingNetwork());
+    }
+
+    @Test
+    public void testSetNetworkForTunnelInterfaceFailsForInvalidResourceId() throws Exception {
+        final IpSecTunnelInterfaceResponse createTunnelResp =
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+        final Network newFakeNetwork = new Network(1000);
+
+        try {
+            mIpSecService.setNetworkForTunnelInterface(
+                    IpSecManager.INVALID_RESOURCE_ID, newFakeNetwork, BLESSED_PACKAGE);
+            fail("Expected an IllegalArgumentException for invalid resource ID.");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testSetNetworkForTunnelInterfaceFailsWhenSettingTunnelNetwork() throws Exception {
+        final IpSecTunnelInterfaceResponse createTunnelResp =
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+        final int tunnelIfaceResourceId = createTunnelResp.resourceId;
+        final IpSecService.UserRecord userRecord =
+                mIpSecService.mUserResourceTracker.getUserRecord(mUid);
+        final TunnelInterfaceRecord tunnelInterfaceInfo =
+                userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId);
+
+        final Network newFakeNetwork =
+                createFakeUnderlyingNetwork(tunnelInterfaceInfo.getInterfaceName());
+
+        try {
+            mIpSecService.setNetworkForTunnelInterface(
+                    tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
+            fail(
+                    "Expected an IllegalArgumentException because the underlying network is the"
+                            + " network being exposed by this tunnel.");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
     @Test
     public void testTunnelInterfaceBinderDeath() throws Exception {
         IpSecTunnelInterfaceResponse createTunnelResp =
-                createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
 
         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
         IpSecService.RefcountedResource refcountedRecord =
@@ -732,9 +814,9 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         IpSecTunnelInterfaceResponse createTunnelResp =
-                createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
 
         if (closeSpiBeforeApply) {
             mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -742,8 +824,8 @@
 
         int transformResourceId = createTransformResp.resourceId;
         int tunnelResourceId = createTunnelResp.resourceId;
-        mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT,
-                transformResourceId, "blessedPackage");
+        mIpSecService.applyTunnelModeTransform(
+                tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE);
 
         for (int selAddrFamily : ADDRESS_FAMILIES) {
             verify(mMockNetd)
@@ -772,17 +854,17 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         IpSecTunnelInterfaceResponse createTunnelResp =
-                createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
 
         // Close SPI record
         mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
 
         int transformResourceId = createTransformResp.resourceId;
         int tunnelResourceId = createTunnelResp.resourceId;
-        mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT,
-                transformResourceId, "blessedPackage");
+        mIpSecService.applyTunnelModeTransform(
+                tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE);
 
         for (int selAddrFamily : ADDRESS_FAMILIES) {
             verify(mMockNetd)
@@ -804,7 +886,7 @@
 
     @Test
     public void testAddRemoveAddressFromTunnelInterface() throws Exception {
-        for (String pkgName : new String[]{"blessedPackage", "systemPackage"}) {
+        for (String pkgName : new String[] {BLESSED_PACKAGE, SYSTEM_PACKAGE}) {
             IpSecTunnelInterfaceResponse createTunnelResp =
                     createAndValidateTunnel(mSourceAddr, mDestinationAddr, pkgName);
             mIpSecService.addAddressToTunnelInterface(
@@ -830,7 +912,7 @@
     public void testAddTunnelFailsForBadPackageName() throws Exception {
         try {
             IpSecTunnelInterfaceResponse createTunnelResp =
-                    createAndValidateTunnel(mSourceAddr, mDestinationAddr, "badPackage");
+                    createAndValidateTunnel(mSourceAddr, mDestinationAddr, BAD_PACKAGE);
             fail("Expected a SecurityException for badPackage.");
         } catch (SecurityException expected) {
         }
@@ -844,7 +926,7 @@
         try {
             String addr = Inet4Address.getLoopbackAddress().getHostAddress();
             mIpSecService.createTunnelInterface(
-                    addr, addr, new Network(0), new Binder(), "blessedPackage");
+                    addr, addr, new Network(0), new Binder(), BLESSED_PACKAGE);
             fail("Expected UnsupportedOperationException for disabled feature");
         } catch (UnsupportedOperationException expected) {
         }
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index b47be97..ff8c632 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -46,7 +46,10 @@
 import com.android.internal.R;
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.AdditionalAnswers;
@@ -63,6 +66,8 @@
 @SmallTest
 public class NetworkNotificationManagerTest {
 
+    private static final String TEST_SSID = "Test SSID";
+    private static final String TEST_EXTRA_INFO = "extra";
     static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
     static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
     static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities();
@@ -72,6 +77,7 @@
 
         WIFI_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
         WIFI_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        WIFI_CAPABILITIES.setSSID(TEST_SSID);
 
         // Set the underyling network to wifi.
         VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
@@ -112,7 +118,7 @@
         when(mCtx.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx);
         when(mCtx.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
                 .thenReturn(mNotificationManager);
-        when(mNetworkInfo.getExtraInfo()).thenReturn("extra");
+        when(mNetworkInfo.getExtraInfo()).thenReturn(TEST_EXTRA_INFO);
         when(mResources.getColor(anyInt(), any())).thenReturn(0xFF607D8B);
 
         mManager = new NetworkNotificationManager(mCtx, mTelephonyManager);
@@ -125,11 +131,11 @@
                 .notify(eq(tag), eq(PRIVATE_DNS_BROKEN.eventId), any());
         final int transportType = NetworkNotificationManager.approximateTransportType(nai);
         if (transportType == NetworkCapabilities.TRANSPORT_WIFI) {
-            verify(mResources, times(1)).getString(title, eq(any()));
+            verify(mResources, times(1)).getString(eq(title), eq(TEST_EXTRA_INFO));
         } else {
             verify(mResources, times(1)).getString(title);
         }
-        verify(mResources, times(1)).getString(R.string.private_dns_broken_detailed);
+        verify(mResources, times(1)).getString(eq(R.string.private_dns_broken_detailed));
     }
 
     @Test
@@ -178,6 +184,9 @@
     }
 
     @Test
+    @Ignore
+    // Ignored because the code under test calls Log.wtf, which crashes the tests on eng builds.
+    // TODO: re-enable after fixing this (e.g., turn Log.wtf into exceptions that this test catches)
     public void testNoInternetNotificationsNotShownForCellular() {
         mManager.showNotification(100, NO_INTERNET, mCellNai, mWifiNai, null, false);
         mManager.showNotification(101, LOST_INTERNET, mCellNai, mWifiNai, null, false);
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index 3556c72..e4e24b4 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -61,6 +61,7 @@
 import android.net.INetd;
 import android.net.UidRange;
 import android.os.Build;
+import android.os.SystemConfigManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.SparseIntArray;
@@ -89,8 +90,8 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class PermissionMonitorTest {
-    private static final int MOCK_USER1 = 0;
-    private static final int MOCK_USER2 = 1;
+    private static final UserHandle MOCK_USER1 = UserHandle.of(0);
+    private static final UserHandle MOCK_USER2 = UserHandle.of(1);
     private static final int MOCK_UID1 = 10001;
     private static final int MOCK_UID2 = 10086;
     private static final int SYSTEM_UID1 = 1000;
@@ -114,6 +115,7 @@
     @Mock private PackageManagerInternal mMockPmi;
     @Mock private UserManager mUserManager;
     @Mock private PermissionMonitor.Dependencies mDeps;
+    @Mock private SystemConfigManager mSystemConfigManager;
 
     private PermissionMonitor mPermissionMonitor;
 
@@ -123,10 +125,12 @@
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
         when(mUserManager.getUserHandles(eq(true))).thenReturn(
-                Arrays.asList(new UserHandle[] {
-                        new UserHandle(MOCK_USER1),
-                        new UserHandle(MOCK_USER2),
-                }));
+                Arrays.asList(new UserHandle[] { MOCK_USER1, MOCK_USER2 }));
+        when(mContext.getSystemServiceName(SystemConfigManager.class))
+                .thenReturn(Context.SYSTEM_CONFIG_SERVICE);
+        when(mContext.getSystemService(Context.SYSTEM_CONFIG_SERVICE))
+                .thenReturn(mSystemConfigManager);
+        when(mSystemConfigManager.getSystemPermissionUids(anyString())).thenReturn(new int[0]);
 
         mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps));
 
@@ -184,7 +188,8 @@
         return packageInfo;
     }
 
-    private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, int userId) {
+    private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid,
+            UserHandle user) {
         final PackageInfo pkgInfo;
         if (hasSystemPermission) {
             pkgInfo = systemPackageInfoWithPermissions(
@@ -192,7 +197,7 @@
         } else {
             pkgInfo = packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, new String[] {}, "");
         }
-        pkgInfo.applicationInfo.uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
+        pkgInfo.applicationInfo.uid = UserHandle.getUid(user, UserHandle.getAppId(uid));
         return pkgInfo;
     }
 
@@ -382,8 +387,8 @@
             }).when(mockNetd).networkClearPermissionForUser(any(int[].class));
         }
 
-        public void expectPermission(Boolean permission, int[] users, int[] apps) {
-            for (final int user : users) {
+        public void expectPermission(Boolean permission, UserHandle[] users, int[] apps) {
+            for (final UserHandle user : users) {
                 for (final int app : apps) {
                     final int uid = UserHandle.getUid(user, app);
                     if (!mApps.containsKey(uid)) {
@@ -396,8 +401,8 @@
             }
         }
 
-        public void expectNoPermission(int[] users, int[] apps) {
-            for (final int user : users) {
+        public void expectNoPermission(UserHandle[] users, int[] apps) {
+            for (final UserHandle user : users) {
                 for (final int app : apps) {
                     final int uid = UserHandle.getUid(user, app);
                     if (mApps.containsKey(uid)) {
@@ -425,46 +430,48 @@
 
         // Add SYSTEM_PACKAGE2, expect only have network permission.
         mPermissionMonitor.onUserAdded(MOCK_USER1);
-        addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID);
-        mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
+        addPackageForUsers(new UserHandle[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID);
+        mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{SYSTEM_UID});
 
         // Add SYSTEM_PACKAGE1, expect permission escalate.
-        addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID);
-        mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
+        addPackageForUsers(new UserHandle[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID);
+        mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{SYSTEM_UID});
 
         mPermissionMonitor.onUserAdded(MOCK_USER2);
-        mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
+        mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2},
                 new int[]{SYSTEM_UID});
 
-        addPackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
-        mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
+        addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
+        mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2},
                 new int[]{SYSTEM_UID});
-        mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
+        mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1, MOCK_USER2},
                 new int[]{MOCK_UID1});
 
         // Remove MOCK_UID1, expect no permission left for all user.
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
-        removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
-        mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1});
+        removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
+        mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2},
+                new int[]{MOCK_UID1});
 
         // Remove SYSTEM_PACKAGE1, expect permission downgrade.
         when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{SYSTEM_PACKAGE2});
-        removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, SYSTEM_PACKAGE1, SYSTEM_UID);
-        mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
+        removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2},
+                SYSTEM_PACKAGE1, SYSTEM_UID);
+        mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1, MOCK_USER2},
                 new int[]{SYSTEM_UID});
 
         mPermissionMonitor.onUserRemoved(MOCK_USER1);
-        mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER2}, new int[]{SYSTEM_UID});
+        mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER2}, new int[]{SYSTEM_UID});
 
         // Remove all packages, expect no permission left.
         when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{});
-        removePackageForUsers(new int[]{MOCK_USER2}, SYSTEM_PACKAGE2, SYSTEM_UID);
-        mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
+        removePackageForUsers(new UserHandle[]{MOCK_USER2}, SYSTEM_PACKAGE2, SYSTEM_UID);
+        mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2},
                 new int[]{SYSTEM_UID, MOCK_UID1});
 
         // Remove last user, expect no redundant clearPermission is invoked.
         mPermissionMonitor.onUserRemoved(MOCK_USER2);
-        mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
+        mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2},
                 new int[]{SYSTEM_UID, MOCK_UID1});
     }
 
@@ -548,14 +555,14 @@
     // Normal package add/remove operations will trigger multiple intent for uids corresponding to
     // each user. To simulate generic package operations, the onPackageAdded/Removed will need to be
     // called multiple times with the uid corresponding to each user.
-    private void addPackageForUsers(int[] users, String packageName, int uid) {
-        for (final int user : users) {
+    private void addPackageForUsers(UserHandle[] users, String packageName, int uid) {
+        for (final UserHandle user : users) {
             mPermissionMonitor.onPackageAdded(packageName, UserHandle.getUid(user, uid));
         }
     }
 
-    private void removePackageForUsers(int[] users, String packageName, int uid) {
-        for (final int user : users) {
+    private void removePackageForUsers(UserHandle[] users, String packageName, int uid) {
+        for (final UserHandle user : users) {
             mPermissionMonitor.onPackageRemoved(packageName, UserHandle.getUid(user, uid));
         }
     }
@@ -747,4 +754,20 @@
                 GET_PERMISSIONS | MATCH_ANY_USER);
         assertTrue(monitor.hasPermission(systemInfo, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
     }
+
+    @Test
+    public void testUpdateUidPermissionsFromSystemConfig() throws Exception {
+        final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
+        when(mPackageManager.getInstalledPackages(anyInt())).thenReturn(new ArrayList<>());
+        when(mSystemConfigManager.getSystemPermissionUids(eq(INTERNET)))
+                .thenReturn(new int[]{ MOCK_UID1, MOCK_UID2 });
+        when(mSystemConfigManager.getSystemPermissionUids(eq(UPDATE_DEVICE_STATS)))
+                .thenReturn(new int[]{ MOCK_UID2 });
+
+        mPermissionMonitor.startMonitoring();
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{ MOCK_UID1 });
+        mNetdServiceMonitor.expectPermission(
+                INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS,
+                new int[]{ MOCK_UID2 });
+    }
 }
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 9373da3..6576daf 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -27,6 +27,7 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -77,6 +78,7 @@
 import android.net.UidRangeParcel;
 import android.net.VpnManager;
 import android.net.VpnService;
+import android.net.VpnTransportInfo;
 import android.net.ipsec.ike.IkeSessionCallback;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.os.Build.VERSION_CODES;
@@ -971,14 +973,7 @@
     }
 
     private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception {
-        // TODO(b/175883995): once these tests have been updated for the changes to the UserManager
-        // API, remove this ad-hoc setup code and use setMockedUsers(primaryUser) again.
-        // setMockedUsers(primaryUser);
-        final ArrayList<UserInfo> users = new ArrayList<>();
-        users.add(primaryUser);
-        when(mUserManager.getAliveUsers()).thenReturn(users);
-        when(mUserManager.getUserInfo(primaryUser.id)).thenReturn(primaryUser);
-        when(mUserManager.canHaveRestrictedProfile()).thenReturn(false);
+        setMockedUsers(primaryUser);
 
         // Dummy egress interface
         final LinkProperties lp = new LinkProperties();
@@ -1009,6 +1004,13 @@
         startRacoon("hostname", "5.6.7.8"); // address returned by deps.resolve
     }
 
+    private void assertTransportInfoMatches(NetworkCapabilities nc, int type) {
+        assertNotNull(nc);
+        VpnTransportInfo ti = (VpnTransportInfo) nc.getTransportInfo();
+        assertNotNull(ti);
+        assertEquals(type, ti.type);
+    }
+
     public void startRacoon(final String serverAddr, final String expectedAddr)
             throws Exception {
         final ConditionVariable legacyRunnerReady = new ConditionVariable();
@@ -1045,8 +1047,10 @@
 
             // Now wait for the runner to be ready before testing for the route.
             ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class);
+            ArgumentCaptor<NetworkCapabilities> ncCaptor =
+                    ArgumentCaptor.forClass(NetworkCapabilities.class);
             verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(),
-                    lpCaptor.capture(), any(), anyInt(), any(), anyInt());
+                    lpCaptor.capture(), ncCaptor.capture(), anyInt(), any(), anyInt());
 
             // In this test the expected address is always v4 so /32.
             // Note that the interface needs to be specified because RouteInfo objects stored in
@@ -1056,6 +1060,8 @@
             final List<RouteInfo> actualRoutes = lpCaptor.getValue().getRoutes();
             assertTrue("Expected throw route (" + expectedRoute + ") not found in " + actualRoutes,
                     actualRoutes.contains(expectedRoute));
+
+            assertTransportInfoMatches(ncCaptor.getValue(), VpnManager.TYPE_VPN_LEGACY);
         } finally {
             // Now interrupt the thread, unblock the runner and clean up.
             vpn.mVpnRunner.exitVpnRunner();
@@ -1177,10 +1183,6 @@
         doReturn(UserHandle.of(userId)).when(asUserContext).getUser();
         when(mContext.createContextAsUser(eq(UserHandle.of(userId)), anyInt()))
                 .thenReturn(asUserContext);
-        when(asUserContext.getSystemServiceName(UserManager.class))
-                .thenReturn(Context.USER_SERVICE);
-        when(asUserContext.getSystemService(UserManager.class))
-                .thenReturn(mUserManager);
         final TestLooper testLooper = new TestLooper();
         final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, new TestDeps(), mNetService,
                 mNetd, userId, mKeyStore, mSystemServices, mIkev2SessionCreator);
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index dde78aa..d644739 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -80,8 +80,6 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkState;
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
@@ -1456,8 +1454,6 @@
     }
 
     private static NetworkState buildWifiState(boolean isMetered, @NonNull String iface) {
-        final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null);
-        info.setDetailedState(DetailedState.CONNECTED, null, null);
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(iface);
         final NetworkCapabilities capabilities = new NetworkCapabilities();
@@ -1465,7 +1461,7 @@
         capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true);
         capabilities.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
         capabilities.setSSID(TEST_SSID);
-        return new NetworkState(info, prop, capabilities, WIFI_NETWORK, null, TEST_SSID);
+        return new NetworkState(TYPE_WIFI, prop, capabilities, WIFI_NETWORK, null);
     }
 
     private static NetworkState buildMobile3gState(String subscriberId) {
@@ -1473,17 +1469,13 @@
     }
 
     private static NetworkState buildMobile3gState(String subscriberId, boolean isRoaming) {
-        final NetworkInfo info = new NetworkInfo(
-                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UMTS, null, null);
-        info.setDetailedState(DetailedState.CONNECTED, null, null);
-        info.setRoaming(isRoaming);
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(TEST_IFACE);
         final NetworkCapabilities capabilities = new NetworkCapabilities();
         capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false);
         capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, !isRoaming);
         capabilities.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-        return new NetworkState(info, prop, capabilities, MOBILE_NETWORK, subscriberId, null);
+        return new NetworkState(TYPE_MOBILE, prop, capabilities, MOBILE_NETWORK, subscriberId);
     }
 
     private NetworkStats buildEmptyStats() {
@@ -1491,11 +1483,9 @@
     }
 
     private static NetworkState buildVpnState() {
-        final NetworkInfo info = new NetworkInfo(TYPE_VPN, 0, null, null);
-        info.setDetailedState(DetailedState.CONNECTED, null, null);
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(TUN_IFACE);
-        return new NetworkState(info, prop, new NetworkCapabilities(), VPN_NETWORK, null, null);
+        return new NetworkState(TYPE_VPN, prop, new NetworkCapabilities(), VPN_NETWORK, null);
     }
 
     private long getElapsedRealtime() {
diff --git a/tests/net/smoketest/Android.bp b/tests/net/smoketest/Android.bp
index 84ae2b5..1535f3d 100644
--- a/tests/net/smoketest/Android.bp
+++ b/tests/net/smoketest/Android.bp
@@ -9,6 +9,15 @@
 //
 // TODO: remove this hack when there is a better solution for jni_libs that includes
 // dependent libraries.
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
 android_test {
     name: "FrameworksNetSmokeTests",
     defaults: ["FrameworksNetTests-jni-defaults"],
@@ -19,4 +28,4 @@
         "mockito-target-minus-junit4",
         "services.core",
     ],
-}
\ No newline at end of file
+}