Merge "CaptivePortalData: use CharSequence in VenueFriendlyName API"
diff --git a/framework/Android.bp b/framework/Android.bp
index 9da27d2..86433e1 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -23,7 +23,6 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-// TODO: use a java_library in the bootclasspath instead
 filegroup {
     name: "framework-connectivity-internal-sources",
     srcs: [
@@ -84,3 +83,38 @@
     ],
     permitted_packages: ["android.net", "com.android.connectivity.aidl"],
 }
+
+java_library {
+    name: "framework-connectivity.impl",
+    // Instead of building against private API (framework.jar),
+    // build against core_platform + framework-minus-apex + module
+    // stub libs. This allows framework.jar to depend on this library,
+    // so it can be part of the private API until all clients have been migrated.
+    // TODO: just build against module_api, and remove this jar from
+    // the private API.
+    sdk_version: "core_platform",
+    srcs: [
+        ":framework-connectivity-sources",
+    ],
+    aidl: {
+        include_dirs: [
+            "frameworks/base/core/java", // For framework parcelables
+            "frameworks/native/aidl/binder", // For PersistableBundle.aidl
+        ],
+    },
+    libs: [
+        "framework-minus-apex",
+        // TODO: just framework-tethering, framework-wifi when building against module_api
+        "framework-tethering.stubs.module_lib",
+        "framework-wifi.stubs.module_lib",
+        "unsupportedappusage",
+        "ServiceConnectivityResources",
+    ],
+    static_libs: [
+        "net-utils-device-common",
+    ],
+    jarjar_rules: "jarjar-rules.txt",
+    apex_available: ["com.android.tethering"],
+    installable: true,
+    permitted_packages: ["android.net", "com.android.connectivity.aidl"],
+}
diff --git a/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl b/framework/aidl-export/android/net/NetworkScore.aidl
similarity index 79%
copy from framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl
copy to framework/aidl-export/android/net/NetworkScore.aidl
index 7979afc..af12dcf 100644
--- a/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl
+++ b/framework/aidl-export/android/net/NetworkScore.aidl
@@ -1,6 +1,5 @@
 /**
- *
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (c) 2021, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,7 +16,5 @@
 
 package android.net;
 
-/** @hide */
-oneway interface IOnSetOemNetworkPreferenceListener {
-    void onComplete();
-}
+parcelable NetworkScore;
+
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index c3b1800..7a91f64 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -6,15 +6,25 @@
   }
 
   public class ConnectivityManager {
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void factoryReset();
     method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List<android.net.NetworkStateSnapshot> getAllNetworkStateSnapshot();
+    method @Nullable public android.net.ProxyInfo getGlobalProxy();
     method @NonNull public static android.util.Range<java.lang.Integer> getIpSecNetIdRange();
-    method @NonNull public static String getPrivateDnsMode(@NonNull android.content.ContentResolver);
+    method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
+    method public void systemReady();
     field public static final String PRIVATE_DNS_MODE_OFF = "off";
     field public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
     field public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
+    field public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; // 0x0
+    field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1
   }
 
   public final class NetworkAgentConfig implements android.os.Parcelable {
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index 54b3f56..8845225 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -56,7 +56,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
     method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
-    method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener);
+    method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi();
     method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle);
     method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
@@ -78,10 +78,6 @@
     field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd
   }
 
-  public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener {
-    method public void onComplete();
-  }
-
   @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback {
     ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback();
     method @Deprecated public void onTetheringFailed();
diff --git a/framework/jarjar-rules.txt b/framework/jarjar-rules.txt
new file mode 100644
index 0000000..381a4ac
--- /dev/null
+++ b/framework/jarjar-rules.txt
@@ -0,0 +1,7 @@
+rule com.android.net.module.util.** android.net.connectivity.framework.util.@1
+
+# TODO (b/149403767): remove the annotations from net-utils-device-common instead of here
+zap android.annotation.**
+zap com.android.net.module.annotation.**
+zap com.android.internal.annotations.**
+
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index d7cae2f..f748159 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -16,6 +16,8 @@
 package android.net;
 
 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE;
 import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST;
 import static android.net.NetworkRequest.Type.LISTEN;
 import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST;
@@ -23,8 +25,6 @@
 import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
 import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
 import static android.net.QosCallback.QosCallbackRegistrationException;
-import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
-import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
 
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
@@ -64,6 +64,7 @@
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -76,7 +77,6 @@
 import com.android.connectivity.aidl.INetworkAgent;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
-import com.android.internal.util.Protocol;
 
 import libcore.net.event.NetworkEventDispatcher;
 
@@ -915,8 +915,8 @@
 
     /**
      * @hide
-     * TODO: Expose for SystemServer when becomes a module.
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public void systemReady() {
         try {
             mService.systemReady();
@@ -971,6 +971,33 @@
     }
 
     /**
+     * Preference for {@link #setNetworkPreferenceForUser(UserHandle, int, Executor, Runnable)}.
+     * Specify that the traffic for this user should by follow the default rules.
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0;
+
+    /**
+     * Preference for {@link #setNetworkPreferenceForUser(UserHandle, int, Executor, Runnable)}.
+     * Specify that the traffic for this user should by default go on a network with
+     * {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE}, and on the system default network
+     * if no such network is available.
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            PROFILE_NETWORK_PREFERENCE_DEFAULT,
+            PROFILE_NETWORK_PREFERENCE_ENTERPRISE
+    })
+    public @interface ProfileNetworkPreference {
+    }
+
+    /**
      * Specifies the preferred network type.  When the device has more
      * than one type available the preferred network type will be used.
      *
@@ -3011,8 +3038,9 @@
      *        HTTP proxy.  A {@code null} value will clear the global HTTP proxy.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
-    public void setGlobalProxy(ProxyInfo p) {
+    public void setGlobalProxy(@Nullable ProxyInfo p) {
         try {
             mService.setGlobalProxy(p);
         } catch (RemoteException e) {
@@ -3027,6 +3055,8 @@
      *        if no global HTTP proxy is set.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
+    @Nullable
     public ProxyInfo getGlobalProxy() {
         try {
             return mService.getGlobalProxy();
@@ -3523,29 +3553,28 @@
         }
     }
 
-    private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER;
     /** @hide */
-    public static final int CALLBACK_PRECHECK            = BASE + 1;
+    public static final int CALLBACK_PRECHECK            = 1;
     /** @hide */
-    public static final int CALLBACK_AVAILABLE           = BASE + 2;
+    public static final int CALLBACK_AVAILABLE           = 2;
     /** @hide arg1 = TTL */
-    public static final int CALLBACK_LOSING              = BASE + 3;
+    public static final int CALLBACK_LOSING              = 3;
     /** @hide */
-    public static final int CALLBACK_LOST                = BASE + 4;
+    public static final int CALLBACK_LOST                = 4;
     /** @hide */
-    public static final int CALLBACK_UNAVAIL             = BASE + 5;
+    public static final int CALLBACK_UNAVAIL             = 5;
     /** @hide */
-    public static final int CALLBACK_CAP_CHANGED         = BASE + 6;
+    public static final int CALLBACK_CAP_CHANGED         = 6;
     /** @hide */
-    public static final int CALLBACK_IP_CHANGED          = BASE + 7;
+    public static final int CALLBACK_IP_CHANGED          = 7;
     /** @hide obj = NetworkCapabilities, arg1 = seq number */
-    private static final int EXPIRE_LEGACY_REQUEST       = BASE + 8;
+    private static final int EXPIRE_LEGACY_REQUEST       = 8;
     /** @hide */
-    public static final int CALLBACK_SUSPENDED           = BASE + 9;
+    public static final int CALLBACK_SUSPENDED           = 9;
     /** @hide */
-    public static final int CALLBACK_RESUMED             = BASE + 10;
+    public static final int CALLBACK_RESUMED             = 10;
     /** @hide */
-    public static final int CALLBACK_BLK_CHANGED         = BASE + 11;
+    public static final int CALLBACK_BLK_CHANGED         = 11;
 
     /** @hide */
     public static String getCallbackName(int whichCallback) {
@@ -4362,8 +4391,13 @@
      *
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
-    public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
+    @SystemApi(client = MODULE_LIBRARIES)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD,
+            android.Manifest.permission.NETWORK_STACK,
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK})
+    public void setAcceptUnvalidated(@NonNull Network network, boolean accept, boolean always) {
         try {
             mService.setAcceptUnvalidated(network, accept, always);
         } catch (RemoteException e) {
@@ -4385,8 +4419,14 @@
      *
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
-    public void setAcceptPartialConnectivity(Network network, boolean accept, boolean always) {
+    @SystemApi(client = MODULE_LIBRARIES)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD,
+            android.Manifest.permission.NETWORK_STACK,
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK})
+    public void setAcceptPartialConnectivity(@NonNull Network network, boolean accept,
+            boolean always) {
         try {
             mService.setAcceptPartialConnectivity(network, accept, always);
         } catch (RemoteException e) {
@@ -4404,8 +4444,13 @@
      *
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
-    public void setAvoidUnvalidated(Network network) {
+    @SystemApi(client = MODULE_LIBRARIES)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD,
+            android.Manifest.permission.NETWORK_STACK,
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK})
+    public void setAvoidUnvalidated(@NonNull Network network) {
         try {
             mService.setAvoidUnvalidated(network);
         } catch (RemoteException e) {
@@ -4535,7 +4580,10 @@
      * Resets all connectivity manager settings back to factory defaults.
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    @SystemApi(client = MODULE_LIBRARIES)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK})
     public void factoryReset() {
         try {
             mService.factoryReset();
@@ -4613,7 +4661,7 @@
                 Log.e(TAG, "Can't set proxy properties", e);
             }
             // Must flush DNS cache as new network may have different DNS resolutions.
-            InetAddress.clearDnsCache();
+            InetAddressCompat.clearDnsCache();
             // Must flush socket pool as idle sockets will be bound to previous network and may
             // cause subsequent fetches to be performed on old network.
             NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged();
@@ -5067,19 +5115,6 @@
     }
 
     /**
-     * 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
@@ -5101,16 +5136,16 @@
     @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) {
+            @Nullable final Runnable 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() {
+        final IOnCompleteListener listenerInternal = listener == null ? null :
+                new IOnCompleteListener.Stub() {
                     @Override
                     public void onComplete() {
-                        executor.execute(listener::onComplete);
+                        executor.execute(listener::run);
                     }
         };
 
@@ -5122,6 +5157,52 @@
         }
     }
 
+    /**
+     * Request that a user profile is put by default on a network matching a given preference.
+     *
+     * See the documentation for the individual preferences for a description of the supported
+     * behaviors.
+     *
+     * @param profile the profile concerned.
+     * @param preference the preference for this profile.
+     * @param executor an executor to execute the listener on. Optional if listener is null.
+     * @param listener an optional listener to listen for completion of the operation.
+     * @throws IllegalArgumentException if {@code profile} is not a valid user profile.
+     * @throws SecurityException if missing the appropriate permissions.
+     * @hide
+     */
+    // This function is for establishing per-profile default networking and can only be called by
+    // the device policy manager, running as the system server. It would make no sense to call it
+    // on a context for a user because it does not establish a setting on behalf of a user, rather
+    // it establishes a setting for a user on behalf of the DPM.
+    @SuppressLint({"UserHandle"})
+    @SystemApi(client = MODULE_LIBRARIES)
+    @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+    public void setProfileNetworkPreference(@NonNull final UserHandle profile,
+            @ProfileNetworkPreference final int preference,
+            @Nullable @CallbackExecutor final Executor executor,
+            @Nullable final Runnable listener) {
+        if (null != listener) {
+            Objects.requireNonNull(executor, "Pass a non-null executor, or a null listener");
+        }
+        final IOnCompleteListener proxy;
+        if (null == listener) {
+            proxy = null;
+        } else {
+            proxy = new IOnCompleteListener.Stub() {
+                @Override
+                public void onComplete() {
+                    executor.execute(listener::run);
+                }
+            };
+        }
+        try {
+            mService.setProfileNetworkPreference(profile, preference, proxy);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     // The first network ID of IPSec tunnel interface.
     private static final int TUN_INTF_NETID_START = 0xFC00; // 0xFC00 = 64512
     // The network ID range of IPSec tunnel interface.
@@ -5142,7 +5223,7 @@
     /**
      * Get private DNS mode from settings.
      *
-     * @param cr The ContentResolver to query private DNS mode from settings.
+     * @param context The Context to query the private DNS mode from settings.
      * @return A string of private DNS mode as one of the PRIVATE_DNS_MODE_* constants.
      *
      * @hide
@@ -5150,7 +5231,8 @@
     @SystemApi(client = MODULE_LIBRARIES)
     @NonNull
     @PrivateDnsMode
-    public static String getPrivateDnsMode(@NonNull ContentResolver cr) {
+    public static String getPrivateDnsMode(@NonNull Context context) {
+        final ContentResolver cr = context.getContentResolver();
         String mode = Settings.Global.getString(cr, PRIVATE_DNS_MODE);
         if (TextUtils.isEmpty(mode)) mode = Settings.Global.getString(cr, PRIVATE_DNS_DEFAULT_MODE);
         // If both PRIVATE_DNS_MODE and PRIVATE_DNS_DEFAULT_MODE are not set, choose
diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java
new file mode 100644
index 0000000..bbd8393
--- /dev/null
+++ b/framework/src/android/net/ConnectivitySettingsManager.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A manager class for connectivity module settings.
+ *
+ * @hide
+ */
+public class ConnectivitySettingsManager {
+
+    private ConnectivitySettingsManager() {}
+
+    /** Data activity timeout settings */
+
+    /**
+     * Inactivity timeout to track mobile data activity.
+     *
+     * If set to a positive integer, it indicates the inactivity timeout value in seconds to
+     * infer the data activity of mobile network. After a period of no activity on mobile
+     * networks with length specified by the timeout, an {@code ACTION_DATA_ACTIVITY_CHANGE}
+     * intent is fired to indicate a transition of network status from "active" to "idle". Any
+     * subsequent activity on mobile networks triggers the firing of {@code
+     * ACTION_DATA_ACTIVITY_CHANGE} intent indicating transition from "idle" to "active".
+     *
+     * Network activity refers to transmitting or receiving data on the network interfaces.
+     *
+     * Tracking is disabled if set to zero or negative value.
+     */
+    public static final String DATA_ACTIVITY_TIMEOUT_MOBILE = "data_activity_timeout_mobile";
+
+    /**
+     * Timeout to tracking Wifi data activity. Same as {@code DATA_ACTIVITY_TIMEOUT_MOBILE}
+     * but for Wifi network.
+     */
+    public static final String DATA_ACTIVITY_TIMEOUT_WIFI = "data_activity_timeout_wifi";
+
+    /** Dns resolver settings */
+
+    /**
+     * Sample validity in seconds to configure for the system DNS resolver.
+     */
+    public static final String DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS =
+            "dns_resolver_sample_validity_seconds";
+
+    /**
+     * Success threshold in percent for use with the system DNS resolver.
+     */
+    public static final String DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT =
+            "dns_resolver_success_threshold_percent";
+
+    /**
+     * Minimum number of samples needed for statistics to be considered meaningful in the
+     * system DNS resolver.
+     */
+    public static final String DNS_RESOLVER_MIN_SAMPLES = "dns_resolver_min_samples";
+
+    /**
+     * Maximum number taken into account for statistics purposes in the system DNS resolver.
+     */
+    public static final String DNS_RESOLVER_MAX_SAMPLES = "dns_resolver_max_samples";
+
+    /** Network switch notification settings */
+
+    /**
+     * The maximum number of notifications shown in 24 hours when switching networks.
+     */
+    public static final String NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT =
+            "network_switch_notification_daily_limit";
+
+    /**
+     * The minimum time in milliseconds between notifications when switching networks.
+     */
+    public static final String NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS =
+            "network_switch_notification_rate_limit_millis";
+
+    /** Captive portal settings */
+
+    /**
+     * The URL used for HTTP captive portal detection upon a new connection.
+     * A 204 response code from the server is used for validation.
+     */
+    public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url";
+
+    /**
+     * What to do when connecting a network that presents a captive portal.
+     * Must be one of the CAPTIVE_PORTAL_MODE_* constants above.
+     *
+     * The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT.
+     */
+    public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
+
+    /**
+     * Don't attempt to detect captive portals.
+     */
+    public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0;
+
+    /**
+     * When detecting a captive portal, display a notification that
+     * prompts the user to sign in.
+     */
+    public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1;
+
+    /**
+     * When detecting a captive portal, immediately disconnect from the
+     * network and do not reconnect to that network in the future.
+     */
+    public static final int CAPTIVE_PORTAL_MODE_AVOID = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            CAPTIVE_PORTAL_MODE_IGNORE,
+            CAPTIVE_PORTAL_MODE_PROMPT,
+            CAPTIVE_PORTAL_MODE_AVOID,
+    })
+    public @interface CaptivePortalMode {}
+
+    /** Global http proxy settings */
+
+    /**
+     * Host name for global http proxy. Set via ConnectivityManager.
+     */
+    public static final String GLOBAL_HTTP_PROXY_HOST = "global_http_proxy_host";
+
+    /**
+     * Integer host port for global http proxy. Set via ConnectivityManager.
+     */
+    public static final String GLOBAL_HTTP_PROXY_PORT = "global_http_proxy_port";
+
+    /**
+     * Exclusion list for global proxy. This string contains a list of
+     * comma-separated domains where the global proxy does not apply.
+     * Domains should be listed in a comma- separated list. Example of
+     * acceptable formats: ".domain1.com,my.domain2.com" Use
+     * ConnectivityManager to set/get.
+     */
+    public static final String GLOBAL_HTTP_PROXY_EXCLUSION_LIST =
+            "global_http_proxy_exclusion_list";
+
+    /**
+     * The location PAC File for the proxy.
+     */
+    public static final String GLOBAL_HTTP_PROXY_PAC = "global_proxy_pac_url";
+
+    /** Private dns settings */
+
+    /**
+     * The requested Private DNS mode (string), and an accompanying specifier (string).
+     *
+     * Currently, the specifier holds the chosen provider name when the mode requests
+     * a specific provider. It may be used to store the provider name even when the
+     * mode changes so that temporarily disabling and re-enabling the specific
+     * provider mode does not necessitate retyping the provider hostname.
+     */
+    public static final String PRIVATE_DNS_MODE = "private_dns_mode";
+
+    /**
+     * The specific Private DNS provider name.
+     */
+    public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier";
+
+    /**
+     * Forced override of the default mode (hardcoded as "automatic", nee "opportunistic").
+     * This allows changing the default mode without effectively disabling other modes,
+     * all of which require explicit user action to enable/configure. See also b/79719289.
+     *
+     * Value is a string, suitable for assignment to PRIVATE_DNS_MODE above.
+     */
+    public static final String PRIVATE_DNS_DEFAULT_MODE = "private_dns_default_mode";
+
+    /** Other settings */
+
+    /**
+     * The number of milliseconds to hold on to a PendingIntent based request. This delay gives
+     * the receivers of the PendingIntent an opportunity to make a new network request before
+     * the Network satisfying the request is potentially removed.
+     */
+    public static final String CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS =
+            "connectivity_release_pending_intent_delay_ms";
+
+    /**
+     * Whether the mobile data connection should remain active even when higher
+     * priority networks like WiFi are active, to help make network switching faster.
+     *
+     * See ConnectivityService for more info.
+     *
+     * (0 = disabled, 1 = enabled)
+     */
+    public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on";
+
+    /**
+     * Whether the wifi data connection should remain active even when higher
+     * priority networks like Ethernet are active, to keep both networks.
+     * In the case where higher priority networks are connected, wifi will be
+     * unused unless an application explicitly requests to use it.
+     *
+     * See ConnectivityService for more info.
+     *
+     * (0 = disabled, 1 = enabled)
+     */
+    public static final String WIFI_ALWAYS_REQUESTED = "wifi_always_requested";
+
+    /**
+     * Whether to automatically switch away from wifi networks that lose Internet access.
+     * Only meaningful if config_networkAvoidBadWifi is set to 0, otherwise the system always
+     * avoids such networks. Valid values are:
+     *
+     * 0: Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013.
+     * null: Ask the user whether to switch away from bad wifi.
+     * 1: Avoid bad wifi.
+     */
+    public static final String NETWORK_AVOID_BAD_WIFI = "network_avoid_bad_wifi";
+
+    /**
+     * User setting for ConnectivityManager.getMeteredMultipathPreference(). This value may be
+     * overridden by the system based on device or application state. If null, the value
+     * specified by config_networkMeteredMultipathPreference is used.
+     */
+    public static final String NETWORK_METERED_MULTIPATH_PREFERENCE =
+            "network_metered_multipath_preference";
+}
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index 1bbf1a9..d83cc16 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -20,7 +20,7 @@
 import android.net.ConnectionInfo;
 import android.net.ConnectivityDiagnosticsManager;
 import android.net.IConnectivityDiagnosticsCallback;
-import android.net.IOnSetOemNetworkPreferenceListener;
+import android.net.IOnCompleteListener;
 import android.net.INetworkActivityListener;
 import android.net.IQosCallback;
 import android.net.ISocketKeepaliveCallback;
@@ -43,6 +43,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.ResultReceiver;
+import android.os.UserHandle;
 
 import com.android.connectivity.aidl.INetworkAgent;
 
@@ -215,5 +216,8 @@
     void unregisterQosCallback(in IQosCallback callback);
 
     void setOemNetworkPreference(in OemNetworkPreferences preference,
-            in IOnSetOemNetworkPreferenceListener listener);
+            in IOnCompleteListener listener);
+
+    void setProfileNetworkPreference(in UserHandle profile, int preference,
+            in IOnCompleteListener listener);
 }
diff --git a/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl b/framework/src/android/net/IOnCompleteListener.aidl
similarity index 92%
rename from framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl
rename to framework/src/android/net/IOnCompleteListener.aidl
index 7979afc..4bb89f6 100644
--- a/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl
+++ b/framework/src/android/net/IOnCompleteListener.aidl
@@ -18,6 +18,6 @@
 package android.net;
 
 /** @hide */
-oneway interface IOnSetOemNetworkPreferenceListener {
+oneway interface IOnCompleteListener {
     void onComplete();
 }
diff --git a/framework/src/android/net/InetAddressCompat.java b/framework/src/android/net/InetAddressCompat.java
new file mode 100644
index 0000000..8404441
--- /dev/null
+++ b/framework/src/android/net/InetAddressCompat.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Compatibility utility for InetAddress core platform APIs.
+ *
+ * Connectivity has access to such APIs, but they are not part of the module_current stubs yet
+ * (only core_current). Most stable core platform APIs are included manually in the connectivity
+ * build rules, but because InetAddress is also part of the base java SDK that is earlier on the
+ * classpath, the extra core platform APIs are not seen.
+ *
+ * TODO (b/183097033): remove this utility as soon as core_current is part of module_current
+ * @hide
+ */
+public class InetAddressCompat {
+
+    /**
+     * @see InetAddress#clearDnsCache()
+     */
+    public static void clearDnsCache() {
+        try {
+            InetAddress.class.getMethod("clearDnsCache").invoke(null);
+        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+            Log.wtf(InetAddressCompat.class.getSimpleName(), "Error clearing DNS cache", e);
+        }
+    }
+
+    /**
+     * @see InetAddress#getAllByNameOnNet(String, int)
+     */
+    public static InetAddress[] getAllByNameOnNet(String host, int netId) throws
+            UnknownHostException {
+        try {
+            return (InetAddress[]) InetAddress.class.getMethod("getAllByNameOnNet",
+                    String.class, int.class).invoke(null, host, netId);
+        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+            Log.wtf(InetAddressCompat.class.getSimpleName(), "Error calling getAllByNameOnNet", e);
+            throw new IllegalStateException("Error querying via getAllNameOnNet", e);
+        }
+    }
+
+    /**
+     * @see InetAddress#getByNameOnNet(String, int)
+     */
+    public static InetAddress getByNameOnNet(String host, int netId) throws
+            UnknownHostException {
+        try {
+            return (InetAddress) InetAddress.class.getMethod("getByNameOnNet",
+                    String.class, int.class).invoke(null, host, netId);
+        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+            Log.wtf(InetAddressCompat.class.getSimpleName(), "Error calling getAllByNameOnNet", e);
+            throw new IllegalStateException("Error querying via getByNameOnNet", e);
+        }
+    }
+}
diff --git a/framework/src/android/net/Network.java b/framework/src/android/net/Network.java
index 7245db3..0741414 100644
--- a/framework/src/android/net/Network.java
+++ b/framework/src/android/net/Network.java
@@ -142,7 +142,7 @@
      * @throws UnknownHostException if the address lookup fails.
      */
     public InetAddress[] getAllByName(String host) throws UnknownHostException {
-        return InetAddress.getAllByNameOnNet(host, getNetIdForResolv());
+        return InetAddressCompat.getAllByNameOnNet(host, getNetIdForResolv());
     }
 
     /**
@@ -155,7 +155,7 @@
      *             if the address lookup fails.
      */
     public InetAddress getByName(String host) throws UnknownHostException {
-        return InetAddress.getByNameOnNet(host, getNetIdForResolv());
+        return InetAddressCompat.getByNameOnNet(host, getNetIdForResolv());
     }
 
     /**
diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index b3ab0ee..a127c6f 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -37,7 +37,6 @@
 import com.android.connectivity.aidl.INetworkAgent;
 import com.android.connectivity.aidl.INetworkAgentRegistry;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Protocol;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -125,7 +124,10 @@
      */
     public final int providerId;
 
-    private static final int BASE = Protocol.BASE_NETWORK_AGENT;
+    // ConnectivityService parses message constants from itself and NetworkAgent with MessageUtils
+    // for debugging purposes, and crashes if some messages have the same values.
+    // TODO: have ConnectivityService store message names in different maps and remove this base
+    private static final int BASE = 200;
 
     /**
      * Sent by ConnectivityService to the NetworkAgent to inform it of
diff --git a/core/java/android/net/NetworkScore.java b/framework/src/android/net/NetworkScore.java
similarity index 100%
rename from core/java/android/net/NetworkScore.java
rename to framework/src/android/net/NetworkScore.java
diff --git a/framework/src/android/net/OemNetworkPreferences.java b/framework/src/android/net/OemNetworkPreferences.java
index 48bd297..5a76cd6 100644
--- a/framework/src/android/net/OemNetworkPreferences.java
+++ b/framework/src/android/net/OemNetworkPreferences.java
@@ -73,6 +73,14 @@
     private final Bundle mNetworkMappings;
 
     /**
+     * Return whether this object is empty.
+     * @hide
+     */
+    public boolean isEmpty() {
+        return mNetworkMappings.keySet().size() == 0;
+    }
+
+    /**
      * Return the currently built application package name to {@link OemNetworkPreference} mappings.
      * @return the current network preferences map.
      */
diff --git a/framework/src/android/net/util/MultinetworkPolicyTracker.java b/framework/src/android/net/util/MultinetworkPolicyTracker.java
index 739ddad..6a49aa2 100644
--- a/framework/src/android/net/util/MultinetworkPolicyTracker.java
+++ b/framework/src/android/net/util/MultinetworkPolicyTracker.java
@@ -16,8 +16,8 @@
 
 package android.net.util;
 
-import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI;
-import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
+import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI;
+import static android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE;
 
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
@@ -110,8 +110,8 @@
         mHandler = handler;
         mAvoidBadWifiCallback = avoidBadWifiCallback;
         mSettingsUris = Arrays.asList(
-            Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI),
-            Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE));
+                Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI),
+                Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE));
         mResolver = mContext.getContentResolver();
         mSettingObserver = new SettingObserver();
         mBroadcastReceiver = new BroadcastReceiver() {
diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml
index 7d98c76..06c8192 100644
--- a/service/ServiceConnectivityResources/res/values/config.xml
+++ b/service/ServiceConnectivityResources/res/values/config.xml
@@ -42,4 +42,14 @@
         -->
     </string-array>
 
+    <string-array translatable="false" name="config_legacy_networktype_restore_timers">
+        <item>2,60000</item><!-- mobile_mms -->
+        <item>3,60000</item><!-- mobile_supl -->
+        <item>4,60000</item><!-- mobile_dun -->
+        <item>5,60000</item><!-- mobile_hipri -->
+        <item>10,60000</item><!-- mobile_fota -->
+        <item>11,60000</item><!-- mobile_ims -->
+        <item>12,60000</item><!-- mobile_cbs -->
+    </string-array>
+
 </resources>
\ No newline at end of file
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index 00ec2df..da8aee5 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -17,11 +17,11 @@
     <overlayable name="ServiceConnectivityResourcesConfig">
         <policy type="product|system|vendor">
             <!-- Configuration values for ConnectivityService -->
+            <item type="array" name="config_legacy_networktype_restore_timers"/>
             <item type="string" name="config_networkCaptivePortalServerUrl"/>
             <item type="integer" name="config_networkTransitionTimeout"/>
             <item type="array" name="config_wakeonlan_supported_interfaces"/>
 
-
         </policy>
     </overlayable>
 </resources>
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 464f588..58a921f 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -17,6 +17,10 @@
 package com.android.server;
 
 import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static android.content.pm.PackageManager.FEATURE_WIFI;
+import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK;
@@ -28,15 +32,30 @@
 import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
 import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_MOBILE_CBS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
+import static android.net.ConnectivityManager.TYPE_MOBILE_EMERGENCY;
+import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
+import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+import static android.net.ConnectivityManager.TYPE_MOBILE_IA;
+import static android.net.ConnectivityManager.TYPE_MOBILE_IMS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
 import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.ConnectivityManager.TYPE_PROXY;
 import static android.net.ConnectivityManager.TYPE_VPN;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
 import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.ConnectivityManager.isNetworkTypeValid;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
@@ -87,6 +106,7 @@
 import android.net.ConnectivityDiagnosticsManager.DataStallReport;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.ConnectivitySettingsManager;
 import android.net.DataStallReportParcelable;
 import android.net.DnsResolverServiceManager;
 import android.net.ICaptivePortal;
@@ -98,7 +118,7 @@
 import android.net.INetworkMonitor;
 import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkPolicyListener;
-import android.net.IOnSetOemNetworkPreferenceListener;
+import android.net.IOnCompleteListener;
 import android.net.IQosCallback;
 import android.net.ISocketKeepaliveCallback;
 import android.net.InetAddresses;
@@ -111,7 +131,6 @@
 import android.net.NetworkAgent;
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
-import android.net.NetworkConfig;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkMonitorManager;
@@ -215,6 +234,7 @@
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 import com.android.server.connectivity.NetworkRanker;
 import com.android.server.connectivity.PermissionMonitor;
+import com.android.server.connectivity.ProfileNetworkPreferences;
 import com.android.server.connectivity.ProxyTracker;
 import com.android.server.connectivity.QosCallbackTracker;
 import com.android.server.net.NetworkPolicyManagerInternal;
@@ -265,7 +285,7 @@
     /**
      * Default URL to use for {@link #getCaptivePortalServerUrl()}. This should not be changed
      * by OEMs for configuration purposes, as this value is overridden by
-     * Settings.Global.CAPTIVE_PORTAL_HTTP_URL.
+     * ConnectivitySettingsManager.CAPTIVE_PORTAL_HTTP_URL.
      * R.string.config_networkCaptivePortalServerUrl should be overridden instead for this purpose
      * (preferably via runtime resource overlays).
      */
@@ -298,7 +318,7 @@
     protected int mNascentDelayMs;
 
     // How long to delay to removal of a pending intent based request.
-    // See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
+    // See ConnectivitySettingsManager.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
     private final int mReleasePendingIntentDelayMs;
 
     private MockableSystemProperties mSystemProperties;
@@ -559,8 +579,8 @@
     private static final int EVENT_SET_REQUIRE_VPN_FOR_UIDS = 47;
 
     /**
-     * used internally when setting the default networks for OemNetworkPreferences.
-     * obj = OemNetworkPreferences
+     * Used internally when setting the default networks for OemNetworkPreferences.
+     * obj = Pair<OemNetworkPreferences, listener>
      */
     private static final int EVENT_SET_OEM_NETWORK_PREFERENCE = 48;
 
@@ -570,6 +590,12 @@
     private static final int EVENT_REPORT_NETWORK_ACTIVITY = 49;
 
     /**
+     * Used internally when setting a network preference for a user profile.
+     * obj = Pair<ProfileNetworkPreference, Listener>
+     */
+    private static final int EVENT_SET_PROFILE_NETWORK_PREFERENCE = 50;
+
+    /**
      * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
      * should be shown.
      */
@@ -618,11 +644,8 @@
 
     private UserManager mUserManager;
 
-    private NetworkConfig[] mNetConfigs;
-    private int mNetworksDefined;
-
     // the set of network types that can only be enabled by system/sig apps
-    private List mProtectedNetworks;
+    private List<Integer> mProtectedNetworks;
 
     private Set<String> mWolSupportedInterfaces;
 
@@ -712,18 +735,63 @@
          *    They are therefore not thread-safe with respect to each other.
          *  - getNetworkForType() can be called at any time on binder threads. It is synchronized
          *    on mTypeLists to be thread-safe with respect to a concurrent remove call.
+         *  - getRestoreTimerForType(type) is also synchronized on mTypeLists.
          *  - dump is thread-safe with respect to concurrent add and remove calls.
          */
         private final ArrayList<NetworkAgentInfo> mTypeLists[];
         @NonNull
         private final ConnectivityService mService;
 
+        // Restore timers for requestNetworkForFeature (network type -> timer in ms). Types without
+        // an entry have no timer (equivalent to -1). Lazily loaded.
+        @NonNull
+        private ArrayMap<Integer, Integer> mRestoreTimers = new ArrayMap<>();
+
         LegacyTypeTracker(@NonNull ConnectivityService service) {
             mService = service;
             mTypeLists = new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE + 1];
         }
 
-        public void addSupportedType(int type) {
+        public void loadSupportedTypes(@NonNull Context ctx, @NonNull TelephonyManager tm) {
+            final PackageManager pm = ctx.getPackageManager();
+            if (pm.hasSystemFeature(FEATURE_WIFI)) {
+                addSupportedType(TYPE_WIFI);
+            }
+            if (pm.hasSystemFeature(FEATURE_WIFI_DIRECT)) {
+                addSupportedType(TYPE_WIFI_P2P);
+            }
+            if (tm.isDataCapable()) {
+                // Telephony does not have granular support for these types: they are either all
+                // supported, or none is supported
+                addSupportedType(TYPE_MOBILE);
+                addSupportedType(TYPE_MOBILE_MMS);
+                addSupportedType(TYPE_MOBILE_SUPL);
+                addSupportedType(TYPE_MOBILE_DUN);
+                addSupportedType(TYPE_MOBILE_HIPRI);
+                addSupportedType(TYPE_MOBILE_FOTA);
+                addSupportedType(TYPE_MOBILE_IMS);
+                addSupportedType(TYPE_MOBILE_CBS);
+                addSupportedType(TYPE_MOBILE_IA);
+                addSupportedType(TYPE_MOBILE_EMERGENCY);
+            }
+            if (pm.hasSystemFeature(FEATURE_BLUETOOTH)) {
+                addSupportedType(TYPE_BLUETOOTH);
+            }
+            if (pm.hasSystemFeature(FEATURE_WATCH)) {
+                // TYPE_PROXY is only used on Wear
+                addSupportedType(TYPE_PROXY);
+            }
+            // Ethernet is often not specified in the configs, although many devices can use it via
+            // USB host adapters. Add it as long as the ethernet service is here.
+            if (ctx.getSystemService(Context.ETHERNET_SERVICE) != null) {
+                addSupportedType(TYPE_ETHERNET);
+            }
+
+            // Always add TYPE_VPN as a supported type
+            addSupportedType(TYPE_VPN);
+        }
+
+        private void addSupportedType(int type) {
             if (mTypeLists[type] != null) {
                 throw new IllegalStateException(
                         "legacy list for type " + type + "already initialized");
@@ -744,6 +812,35 @@
             return null;
         }
 
+        public int getRestoreTimerForType(int type) {
+            synchronized (mTypeLists) {
+                if (mRestoreTimers == null) {
+                    mRestoreTimers = loadRestoreTimers();
+                }
+                return mRestoreTimers.getOrDefault(type, -1);
+            }
+        }
+
+        private ArrayMap<Integer, Integer> loadRestoreTimers() {
+            final String[] configs = mService.mResources.get().getStringArray(
+                    com.android.connectivity.resources.R.array
+                            .config_legacy_networktype_restore_timers);
+            final ArrayMap<Integer, Integer> ret = new ArrayMap<>(configs.length);
+            for (final String config : configs) {
+                final String[] splits = TextUtils.split(config, ",");
+                if (splits.length != 2) {
+                    logwtf("Invalid restore timer token count: " + config);
+                    continue;
+                }
+                try {
+                    ret.put(Integer.parseInt(splits[0]), Integer.parseInt(splits[1]));
+                } catch (NumberFormatException e) {
+                    logwtf("Invalid restore timer number format: " + config, e);
+                }
+            }
+            return ret;
+        }
+
         private void maybeLogBroadcast(NetworkAgentInfo nai, DetailedState state, int type,
                 boolean isDefaultNetwork) {
             if (DBG) {
@@ -1137,7 +1234,7 @@
                 new ConnectivityDiagnosticsHandler(mHandlerThread.getLooper());
 
         mReleasePendingIntentDelayMs = Settings.Secure.getInt(context.getContentResolver(),
-                Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000);
+                ConnectivitySettingsManager.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000);
 
         mLingerDelayMs = mSystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
         // TODO: Consider making the timer customizable.
@@ -1166,64 +1263,12 @@
         mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
         mPendingIntentWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
 
-        mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1];
-
-        // TODO: What is the "correct" way to do determine if this is a wifi only device?
-        boolean wifiOnly = mSystemProperties.getBoolean("ro.radio.noril", false);
-        log("wifiOnly=" + wifiOnly);
-        String[] naStrings = context.getResources().getStringArray(
-                com.android.internal.R.array.networkAttributes);
-        for (String naString : naStrings) {
-            try {
-                NetworkConfig n = new NetworkConfig(naString);
-                if (VDBG) log("naString=" + naString + " config=" + n);
-                if (n.type > ConnectivityManager.MAX_NETWORK_TYPE) {
-                    loge("Error in networkAttributes - ignoring attempt to define type " +
-                            n.type);
-                    continue;
-                }
-                if (wifiOnly && ConnectivityManager.isNetworkTypeMobile(n.type)) {
-                    log("networkAttributes - ignoring mobile as this dev is wifiOnly " +
-                            n.type);
-                    continue;
-                }
-                if (mNetConfigs[n.type] != null) {
-                    loge("Error in networkAttributes - ignoring attempt to redefine type " +
-                            n.type);
-                    continue;
-                }
-                mLegacyTypeTracker.addSupportedType(n.type);
-
-                mNetConfigs[n.type] = n;
-                mNetworksDefined++;
-            } catch(Exception e) {
-                // ignore it - leave the entry null
-            }
-        }
-
-        // Forcibly add TYPE_VPN as a supported type, if it has not already been added via config.
-        if (mNetConfigs[TYPE_VPN] == null) {
-            // mNetConfigs is used only for "restore time", which isn't applicable to VPNs, so we
-            // don't need to add TYPE_VPN to mNetConfigs.
-            mLegacyTypeTracker.addSupportedType(TYPE_VPN);
-            mNetworksDefined++;  // used only in the log() statement below.
-        }
-
-        // Do the same for Ethernet, since it's often not specified in the configs, although many
-        // devices can use it via USB host adapters.
-        if (mNetConfigs[TYPE_ETHERNET] == null
-                && mContext.getSystemService(Context.ETHERNET_SERVICE) != null) {
-            mLegacyTypeTracker.addSupportedType(TYPE_ETHERNET);
-            mNetworksDefined++;
-        }
-
-        if (VDBG) log("mNetworksDefined=" + mNetworksDefined);
-
-        mProtectedNetworks = new ArrayList<Integer>();
+        mLegacyTypeTracker.loadSupportedTypes(mContext, mTelephonyManager);
+        mProtectedNetworks = new ArrayList<>();
         int[] protectedNetworks = context.getResources().getIntArray(
                 com.android.internal.R.array.config_protectedNetworks);
         for (int p : protectedNetworks) {
-            if ((mNetConfigs[p] != null) && (mProtectedNetworks.contains(p) == false)) {
+            if (mLegacyTypeTracker.isTypeSupported(p) && !mProtectedNetworks.contains(p)) {
                 mProtectedNetworks.add(p);
             } else {
                 if (DBG) loge("Ignoring protectedNetwork " + p);
@@ -1261,10 +1306,10 @@
         mQosCallbackTracker = new QosCallbackTracker(mHandler, mNetworkRequestCounter);
 
         final int dailyLimit = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT,
+                ConnectivitySettingsManager.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT,
                 LingerMonitor.DEFAULT_NOTIFICATION_DAILY_LIMIT);
         final long rateLimit = Settings.Global.getLong(mContext.getContentResolver(),
-                Settings.Global.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS,
+                ConnectivitySettingsManager.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS,
                 LingerMonitor.DEFAULT_NOTIFICATION_RATE_LIMIT_MILLIS);
         mLingerMonitor = new LingerMonitor(mContext, mNotifier, dailyLimit, rateLimit);
 
@@ -1290,11 +1335,16 @@
     }
 
     private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
+        return createDefaultNetworkCapabilitiesForUidRange(new UidRange(uid, uid));
+    }
+
+    private static NetworkCapabilities createDefaultNetworkCapabilitiesForUidRange(
+            @NonNull final UidRange uids) {
         final NetworkCapabilities netCap = new NetworkCapabilities();
         netCap.addCapability(NET_CAPABILITY_INTERNET);
         netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
         netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
-        netCap.setSingleUid(uid);
+        netCap.setUids(Collections.singleton(uids));
         return netCap;
     }
 
@@ -1377,10 +1427,10 @@
     }
 
     private void handleConfigureAlwaysOnNetworks() {
-        handleAlwaysOnNetworkRequest(
-                mDefaultMobileDataRequest, Settings.Global.MOBILE_DATA_ALWAYS_ON, true);
-        handleAlwaysOnNetworkRequest(mDefaultWifiRequest, Settings.Global.WIFI_ALWAYS_REQUESTED,
-                false);
+        handleAlwaysOnNetworkRequest(mDefaultMobileDataRequest,
+                ConnectivitySettingsManager.MOBILE_DATA_ALWAYS_ON, true /* defaultValue */);
+        handleAlwaysOnNetworkRequest(mDefaultWifiRequest,
+                ConnectivitySettingsManager.WIFI_ALWAYS_REQUESTED, false /* defaultValue */);
         handleAlwaysOnNetworkRequest(mDefaultVehicleRequest,
                 com.android.internal.R.bool.config_vehicleInternalNetworkAlwaysRequested);
     }
@@ -1393,12 +1443,12 @@
 
         // Watch for whether or not to keep mobile data always on.
         mSettingsObserver.observe(
-                Settings.Global.getUriFor(Settings.Global.MOBILE_DATA_ALWAYS_ON),
+                Settings.Global.getUriFor(ConnectivitySettingsManager.MOBILE_DATA_ALWAYS_ON),
                 EVENT_CONFIGURE_ALWAYS_ON_NETWORKS);
 
         // Watch for whether or not to keep wifi always on.
         mSettingsObserver.observe(
-                Settings.Global.getUriFor(Settings.Global.WIFI_ALWAYS_REQUESTED),
+                Settings.Global.getUriFor(ConnectivitySettingsManager.WIFI_ALWAYS_REQUESTED),
                 EVENT_CONFIGURE_ALWAYS_ON_NETWORKS);
     }
 
@@ -2604,13 +2654,6 @@
         } catch (RemoteException | ServiceSpecificException e) {
             loge("Can't set TCP buffer sizes:" + e);
         }
-
-        final Integer rwndValue = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.TCP_DEFAULT_INIT_RWND,
-                    mSystemProperties.getInt("net.tcp.default_init_rwnd", 0));
-        if (rwndValue != 0) {
-            mSystemProperties.setTcpInitRwnd(rwndValue);
-        }
     }
 
     @Override
@@ -2627,9 +2670,8 @@
         // if the system property isn't set, use the value for the apn type
         int ret = RESTORE_DEFAULT_NETWORK_DELAY;
 
-        if ((networkType <= ConnectivityManager.MAX_NETWORK_TYPE) &&
-                (mNetConfigs[networkType] != null)) {
-            ret = mNetConfigs[networkType].restoreTime;
+        if (mLegacyTypeTracker.isTypeSupported(networkType)) {
+            ret = mLegacyTypeTracker.getRestoreTimerForType(networkType);
         }
         return ret;
     }
@@ -3068,7 +3110,8 @@
                         nai.lastCaptivePortalDetected = visible;
                         nai.everCaptivePortalDetected |= visible;
                         if (nai.lastCaptivePortalDetected &&
-                            Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) {
+                                ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID
+                                        == getCaptivePortalMode()) {
                             if (DBG) log("Avoiding captive portal network: " + nai.toShortString());
                             nai.onPreventAutomaticReconnect();
                             teardownUnneededNetwork(nai);
@@ -3179,8 +3222,8 @@
 
         private int getCaptivePortalMode() {
             return Settings.Global.getInt(mContext.getContentResolver(),
-                    Settings.Global.CAPTIVE_PORTAL_MODE,
-                    Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
+                    ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE,
+                    ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT);
         }
 
         private boolean maybeHandleNetworkAgentInfoMessage(Message msg) {
@@ -4533,12 +4576,17 @@
                     handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj);
                     break;
                 case EVENT_SET_OEM_NETWORK_PREFERENCE: {
-                    final Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener> arg =
-                            (Pair<OemNetworkPreferences,
-                                    IOnSetOemNetworkPreferenceListener>) msg.obj;
+                    final Pair<OemNetworkPreferences, IOnCompleteListener> arg =
+                            (Pair<OemNetworkPreferences, IOnCompleteListener>) msg.obj;
                     handleSetOemNetworkPreference(arg.first, arg.second);
                     break;
                 }
+                case EVENT_SET_PROFILE_NETWORK_PREFERENCE: {
+                    final Pair<ProfileNetworkPreferences.Preference, IOnCompleteListener> arg =
+                            (Pair<ProfileNetworkPreferences.Preference, IOnCompleteListener>)
+                                    msg.obj;
+                    handleSetProfileNetworkPreference(arg.first, arg.second);
+                }
                 case EVENT_REPORT_NETWORK_ACTIVITY:
                     mNetworkActivityTracker.handleReportNetworkActivity();
                     break;
@@ -4837,6 +4885,10 @@
         Log.wtf(TAG, s);
     }
 
+    private static void logwtf(String s, Throwable t) {
+        Log.wtf(TAG, s, t);
+    }
+
     private static void loge(String s) {
         Log.e(TAG, s);
     }
@@ -5109,6 +5161,9 @@
 
     private void onUserRemoved(UserHandle user) {
         mPermissionMonitor.onUserRemoved(user);
+        // If there was a network preference for this user, remove it.
+        handleSetProfileNetworkPreference(new ProfileNetworkPreferences.Preference(user, null),
+                null /* listener */);
         if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) {
             handleSetOemNetworkPreference(mOemNetworkPreferences, null);
         }
@@ -5907,10 +5962,16 @@
     @GuardedBy("mBlockedAppUids")
     private final HashSet<Integer> mBlockedAppUids = new HashSet<>();
 
-    // Current OEM network preferences.
+    // Current OEM network preferences. This object must only be written to on the handler thread.
+    // Since it is immutable and always non-null, other threads may read it if they only care
+    // about seeing a consistent object but not that it is current.
     @NonNull
     private OemNetworkPreferences mOemNetworkPreferences =
             new OemNetworkPreferences.Builder().build();
+    // Current per-profile network preferences. This object follows the same threading rules as
+    // the OEM network preferences above.
+    @NonNull
+    private ProfileNetworkPreferences mProfileNetworkPreferences = new ProfileNetworkPreferences();
 
     // The always-on request for an Internet-capable network that apps without a specific default
     // fall back to.
@@ -8099,7 +8160,7 @@
         }
 
         settingUrl = Settings.Global.getString(mContext.getContentResolver(),
-                Settings.Global.CAPTIVE_PORTAL_HTTP_URL);
+                ConnectivitySettingsManager.CAPTIVE_PORTAL_HTTP_URL);
         if (!TextUtils.isEmpty(settingUrl)) {
             return settingUrl;
         }
@@ -8181,11 +8242,11 @@
         // restore private DNS settings to default mode (opportunistic)
         if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_PRIVATE_DNS)) {
             Settings.Global.putString(mContext.getContentResolver(),
-                    Settings.Global.PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_OPPORTUNISTIC);
+                    ConnectivitySettingsManager.PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_OPPORTUNISTIC);
         }
 
         Settings.Global.putString(mContext.getContentResolver(),
-                Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
+                ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, null);
     }
 
     @Override
@@ -8890,13 +8951,13 @@
         private int transportTypeToLegacyType(int type) {
             switch (type) {
                 case NetworkCapabilities.TRANSPORT_CELLULAR:
-                    return ConnectivityManager.TYPE_MOBILE;
+                    return TYPE_MOBILE;
                 case NetworkCapabilities.TRANSPORT_WIFI:
-                    return ConnectivityManager.TYPE_WIFI;
+                    return TYPE_WIFI;
                 case NetworkCapabilities.TRANSPORT_BLUETOOTH:
-                    return ConnectivityManager.TYPE_BLUETOOTH;
+                    return TYPE_BLUETOOTH;
                 case NetworkCapabilities.TRANSPORT_ETHERNET:
-                    return ConnectivityManager.TYPE_ETHERNET;
+                    return TYPE_ETHERNET;
                 default:
                     loge("Unexpected transport in transportTypeToLegacyType: " + type);
             }
@@ -8937,13 +8998,13 @@
             if (networkAgent.networkCapabilities.hasTransport(
                     NetworkCapabilities.TRANSPORT_CELLULAR)) {
                 timeout = Settings.Global.getInt(mContext.getContentResolver(),
-                        Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
+                        ConnectivitySettingsManager.DATA_ACTIVITY_TIMEOUT_MOBILE,
                         10);
                 type = NetworkCapabilities.TRANSPORT_CELLULAR;
             } else if (networkAgent.networkCapabilities.hasTransport(
                     NetworkCapabilities.TRANSPORT_WIFI)) {
                 timeout = Settings.Global.getInt(mContext.getContentResolver(),
-                        Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
+                        ConnectivitySettingsManager.DATA_ACTIVITY_TIMEOUT_WIFI,
                         15);
                 type = NetworkCapabilities.TRANSPORT_WIFI;
             } else {
@@ -9111,6 +9172,143 @@
         mQosCallbackTracker.unregisterCallback(callback);
     }
 
+    // Network preference per-profile and OEM network preferences can't be set at the same
+    // time, because it is unclear what should happen if both preferences are active for
+    // one given UID. To make it possible, the stack would have to clarify what would happen
+    // in case both are active at the same time. The implementation may have to be adjusted
+    // to implement the resulting rules. For example, a priority could be defined between them,
+    // where the OEM preference would be considered less or more important than the enterprise
+    // preference ; this would entail implementing the priorities somehow, e.g. by doing
+    // UID arithmetic with UID ranges or passing a priority to netd so that the routing rules
+    // are set at the right level. Other solutions are possible, e.g. merging of the
+    // preferences for the relevant UIDs.
+    private static void throwConcurrentPreferenceException() {
+        throw new IllegalStateException("Can't set NetworkPreferenceForUser and "
+                + "set OemNetworkPreference at the same time");
+    }
+
+    /**
+     * Request that a user profile is put by default on a network matching a given preference.
+     *
+     * See the documentation for the individual preferences for a description of the supported
+     * behaviors.
+     *
+     * @param profile the profile concerned.
+     * @param preference the preference for this profile, as one of the PROFILE_NETWORK_PREFERENCE_*
+     *                   constants.
+     * @param listener an optional listener to listen for completion of the operation.
+     */
+    @Override
+    public void setProfileNetworkPreference(@NonNull final UserHandle profile,
+            @ConnectivityManager.ProfileNetworkPreference final int preference,
+            @Nullable final IOnCompleteListener listener) {
+        Objects.requireNonNull(profile);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
+        if (DBG) {
+            log("setProfileNetworkPreference " + profile + " to " + preference);
+        }
+        if (profile.getIdentifier() < 0) {
+            throw new IllegalArgumentException("Must explicitly specify a user handle ("
+                    + "UserHandle.CURRENT not supported)");
+        }
+        final UserManager um;
+        try {
+            um = mContext.createContextAsUser(profile, 0 /* flags */)
+                    .getSystemService(UserManager.class);
+        } catch (IllegalStateException e) {
+            throw new IllegalArgumentException("Profile does not exist");
+        }
+        if (!um.isManagedProfile()) {
+            throw new IllegalArgumentException("Profile must be a managed profile");
+        }
+        // Strictly speaking, mOemNetworkPreferences should only be touched on the
+        // handler thread. However it is an immutable object, so reading the reference is
+        // safe - it's just possible the value is slightly outdated. For the final check,
+        // see #handleSetProfileNetworkPreference. But if this can be caught here it is a
+        // lot easier to understand, so opportunistically check it.
+        if (!mOemNetworkPreferences.isEmpty()) {
+            throwConcurrentPreferenceException();
+        }
+        final NetworkCapabilities nc;
+        switch (preference) {
+            case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT:
+                nc = null;
+                break;
+            case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE:
+                final UidRange uids = UidRange.createForUser(profile);
+                nc = createDefaultNetworkCapabilitiesForUidRange(uids);
+                nc.addCapability(NET_CAPABILITY_ENTERPRISE);
+                nc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Invalid preference in setProfileNetworkPreference");
+        }
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_PROFILE_NETWORK_PREFERENCE,
+                new Pair<>(new ProfileNetworkPreferences.Preference(profile, nc), listener)));
+    }
+
+    private void validateNetworkCapabilitiesOfProfileNetworkPreference(
+            @Nullable final NetworkCapabilities nc) {
+        if (null == nc) return; // Null caps are always allowed. It means to remove the setting.
+        ensureRequestableCapabilities(nc);
+    }
+
+    private ArraySet<NetworkRequestInfo> createNrisFromProfileNetworkPreferences(
+            @NonNull final ProfileNetworkPreferences prefs) {
+        final ArraySet<NetworkRequestInfo> result = new ArraySet<>();
+        for (final ProfileNetworkPreferences.Preference pref : prefs.preferences) {
+            // The NRI for a user should be comprised of two layers:
+            // - The request for the capabilities
+            // - The request for the default network, for fallback. Create an image of it to
+            //   have the correct UIDs in it (also a request can only be part of one NRI, because
+            //   of lookups in 1:1 associations like mNetworkRequests).
+            // Note that denying a fallback can be implemented simply by not adding the second
+            // request.
+            final ArrayList<NetworkRequest> nrs = new ArrayList<>();
+            nrs.add(createNetworkRequest(NetworkRequest.Type.REQUEST, pref.capabilities));
+            nrs.add(createDefaultRequest());
+            setNetworkRequestUids(nrs, pref.capabilities.getUids());
+            final NetworkRequestInfo nri = new NetworkRequestInfo(nrs);
+            result.add(nri);
+        }
+        return result;
+    }
+
+    private void handleSetProfileNetworkPreference(
+            @NonNull final ProfileNetworkPreferences.Preference preference,
+            @Nullable final IOnCompleteListener listener) {
+        // setProfileNetworkPreference and setOemNetworkPreference are mutually exclusive, in
+        // particular because it's not clear what preference should win in case both apply
+        // to the same app.
+        // The binder call has already checked this, but as mOemNetworkPreferences is only
+        // touched on the handler thread, it's theoretically not impossible that it has changed
+        // since.
+        if (!mOemNetworkPreferences.isEmpty()) {
+            // This may happen on a device with an OEM preference set when a user is removed.
+            // In this case, it's safe to ignore. In particular this happens in the tests.
+            loge("handleSetProfileNetworkPreference, but OEM network preferences not empty");
+            return;
+        }
+
+        validateNetworkCapabilitiesOfProfileNetworkPreference(preference.capabilities);
+
+        mProfileNetworkPreferences = mProfileNetworkPreferences.plus(preference);
+        final ArraySet<NetworkRequestInfo> nris =
+                createNrisFromProfileNetworkPreferences(mProfileNetworkPreferences);
+        replaceDefaultNetworkRequestsForPreference(nris);
+        // Finally, rematch.
+        rematchAllNetworksAndRequests();
+
+        if (null != listener) {
+            try {
+                listener.onComplete();
+            } catch (RemoteException e) {
+                loge("Listener for setProfileNetworkPreference has died");
+            }
+        }
+    }
+
     private void enforceAutomotiveDevice() {
         final boolean isAutomotiveDevice =
                 mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
@@ -9129,17 +9327,26 @@
      * 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
+     * @param listener {@link ConnectivityManager.OnCompleteListener} Listener used
      * to communicate completion of setOemNetworkPreference();
      */
     @Override
     public void setOemNetworkPreference(
             @NonNull final OemNetworkPreferences preference,
-            @Nullable final IOnSetOemNetworkPreferenceListener listener) {
+            @Nullable final IOnCompleteListener listener) {
 
         enforceAutomotiveDevice();
         enforceOemNetworkPreferencesPermission();
 
+        if (!mProfileNetworkPreferences.isEmpty()) {
+            // Strictly speaking, mProfileNetworkPreferences should only be touched on the
+            // handler thread. However it is an immutable object, so reading the reference is
+            // safe - it's just possible the value is slightly outdated. For the final check,
+            // see #handleSetOemPreference. But if this can be caught here it is a
+            // lot easier to understand, so opportunistically check it.
+            throwConcurrentPreferenceException();
+        }
+
         Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
         validateOemNetworkPreferences(preference);
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_OEM_NETWORK_PREFERENCE,
@@ -9158,11 +9365,22 @@
 
     private void handleSetOemNetworkPreference(
             @NonNull final OemNetworkPreferences preference,
-            @Nullable final IOnSetOemNetworkPreferenceListener listener) {
+            @Nullable final IOnCompleteListener listener) {
         Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
         if (DBG) {
             log("set OEM network preferences :" + preference.toString());
         }
+        // setProfileNetworkPreference and setOemNetworkPreference are mutually exclusive, in
+        // particular because it's not clear what preference should win in case both apply
+        // to the same app.
+        // The binder call has already checked this, but as mOemNetworkPreferences is only
+        // touched on the handler thread, it's theoretically not impossible that it has changed
+        // since.
+        if (!mProfileNetworkPreferences.isEmpty()) {
+            logwtf("handleSetOemPreference, but per-profile network preferences not empty");
+            return;
+        }
+
         final ArraySet<NetworkRequestInfo> nris =
                 new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference);
         replaceDefaultNetworkRequestsForPreference(nris);
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index 7a5abf8..ffeb77d 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -18,15 +18,15 @@
 
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+import static android.net.ConnectivitySettingsManager.DNS_RESOLVER_MAX_SAMPLES;
+import static android.net.ConnectivitySettingsManager.DNS_RESOLVER_MIN_SAMPLES;
+import static android.net.ConnectivitySettingsManager.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS;
+import static android.net.ConnectivitySettingsManager.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_SPECIFIER;
 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS;
-import static android.provider.Settings.Global.DNS_RESOLVER_MAX_SAMPLES;
-import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
-import static android.provider.Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS;
-import static android.provider.Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT;
-import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
-import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
-import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
 
 import android.annotation.NonNull;
 import android.content.ContentResolver;
@@ -127,13 +127,17 @@
     private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
     private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
 
-    public static PrivateDnsConfig getPrivateDnsConfig(ContentResolver cr) {
-        final String mode = ConnectivityManager.getPrivateDnsMode(cr);
+    /**
+     * Get PrivateDnsConfig.
+     */
+    public static PrivateDnsConfig getPrivateDnsConfig(Context context) {
+        final String mode = ConnectivityManager.getPrivateDnsMode(context);
 
         final boolean useTls = !TextUtils.isEmpty(mode) && !PRIVATE_DNS_MODE_OFF.equals(mode);
 
         if (PRIVATE_DNS_MODE_PROVIDER_HOSTNAME.equals(mode)) {
-            final String specifier = getStringSetting(cr, PRIVATE_DNS_SPECIFIER);
+            final String specifier = getStringSetting(context.getContentResolver(),
+                    PRIVATE_DNS_SPECIFIER);
             return new PrivateDnsConfig(specifier, null);
         }
 
@@ -268,7 +272,7 @@
     }
 
     public PrivateDnsConfig getPrivateDnsConfig() {
-        return getPrivateDnsConfig(mContentResolver);
+        return getPrivateDnsConfig(mContext);
     }
 
     public void removeNetwork(Network network) {
diff --git a/services/core/java/com/android/server/connectivity/ProfileNetworkPreferences.java b/services/core/java/com/android/server/connectivity/ProfileNetworkPreferences.java
new file mode 100644
index 0000000..dd2815d
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/ProfileNetworkPreferences.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.NetworkCapabilities;
+import android.os.UserHandle;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A data class containing all the per-profile network preferences.
+ *
+ * A given profile can only have one preference.
+ */
+public class ProfileNetworkPreferences {
+    /**
+     * A single preference, as it applies to a given user profile.
+     */
+    public static class Preference {
+        @NonNull public final UserHandle user;
+        // Capabilities are only null when sending an object to remove the setting for a user
+        @Nullable public final NetworkCapabilities capabilities;
+
+        public Preference(@NonNull final UserHandle user,
+                @Nullable final NetworkCapabilities capabilities) {
+            this.user = user;
+            this.capabilities = null == capabilities ? null : new NetworkCapabilities(capabilities);
+        }
+
+        /** toString */
+        public String toString() {
+            return "[ProfileNetworkPreference user=" + user + " caps=" + capabilities + "]";
+        }
+    }
+
+    @NonNull public final List<Preference> preferences;
+
+    public ProfileNetworkPreferences() {
+        preferences = Collections.EMPTY_LIST;
+    }
+
+    private ProfileNetworkPreferences(@NonNull final List<Preference> list) {
+        preferences = Collections.unmodifiableList(list);
+    }
+
+    /**
+     * Returns a new object consisting of this object plus the passed preference.
+     *
+     * If a preference already exists for the same user, it will be replaced by the passed
+     * preference. Passing a Preference object containing a null capabilities object is equivalent
+     * to (and indeed, implemented as) removing the preference for this user.
+     */
+    public ProfileNetworkPreferences plus(@NonNull final Preference pref) {
+        final ArrayList<Preference> newPrefs = new ArrayList<>();
+        for (final Preference existingPref : preferences) {
+            if (!existingPref.user.equals(pref.user)) {
+                newPrefs.add(existingPref);
+            }
+        }
+        if (null != pref.capabilities) {
+            newPrefs.add(pref);
+        }
+        return new ProfileNetworkPreferences(newPrefs);
+    }
+
+    public boolean isEmpty() {
+        return preferences.isEmpty();
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/ProxyTracker.java b/services/core/java/com/android/server/connectivity/ProxyTracker.java
index 8b9c836..f883307 100644
--- a/services/core/java/com/android/server/connectivity/ProxyTracker.java
+++ b/services/core/java/com/android/server/connectivity/ProxyTracker.java
@@ -16,10 +16,10 @@
 
 package com.android.server.connectivity;
 
-import static android.provider.Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST;
-import static android.provider.Settings.Global.GLOBAL_HTTP_PROXY_HOST;
-import static android.provider.Settings.Global.GLOBAL_HTTP_PROXY_PAC;
-import static android.provider.Settings.Global.GLOBAL_HTTP_PROXY_PORT;
+import static android.net.ConnectivitySettingsManager.GLOBAL_HTTP_PROXY_EXCLUSION_LIST;
+import static android.net.ConnectivitySettingsManager.GLOBAL_HTTP_PROXY_HOST;
+import static android.net.ConnectivitySettingsManager.GLOBAL_HTTP_PROXY_PAC;
+import static android.net.ConnectivitySettingsManager.GLOBAL_HTTP_PROXY_PORT;
 import static android.provider.Settings.Global.HTTP_PROXY;
 
 import android.annotation.NonNull;
diff --git a/tests/net/java/android/net/VpnTransportInfoTest.java b/tests/net/java/android/net/VpnTransportInfoTest.java
index d04c87b..b7a42ec 100644
--- a/tests/net/java/android/net/VpnTransportInfoTest.java
+++ b/tests/net/java/android/net/VpnTransportInfoTest.java
@@ -42,7 +42,13 @@
         VpnTransportInfo v1 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
         VpnTransportInfo v2 = new VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE);
         VpnTransportInfo v3 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
+        VpnTransportInfo v4 = new VpnTransportInfo(VpnManager.TYPE_VPN_LEGACY);
+        VpnTransportInfo v5 = new VpnTransportInfo(VpnManager.TYPE_VPN_OEM);
+
         assertNotEquals(v1, v2);
+        assertNotEquals(v3, v4);
+        assertNotEquals(v4, v5);
+
         assertEquals(v1, v3);
         assertEquals(v1.hashCode(), v3.hashCode());
     }
diff --git a/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt b/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt
index c1315f6..1945ce7 100644
--- a/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt
+++ b/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt
@@ -21,10 +21,10 @@
 import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER
 import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE
 import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY
+import android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI
+import android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE
 import android.net.util.MultinetworkPolicyTracker.ActiveDataSubscriptionIdListener
 import android.provider.Settings
-import android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI
-import android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
 import android.telephony.TelephonyManager
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index b894b02..7ac488d 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -23,6 +23,8 @@
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.ACTION_USER_UNLOCKED;
 import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.FEATURE_WIFI;
+import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
@@ -35,11 +37,14 @@
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
 import static android.net.ConnectivityManager.TYPE_ETHERNET;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
 import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
 import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
+import static android.net.ConnectivityManager.TYPE_PROXY;
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
@@ -174,6 +179,7 @@
 import android.net.ConnectivityManager.PacketKeepalive;
 import android.net.ConnectivityManager.PacketKeepaliveCallback;
 import android.net.ConnectivityManager.TooManyRequestsException;
+import android.net.ConnectivitySettingsManager;
 import android.net.ConnectivityThread;
 import android.net.DataStallReportParcelable;
 import android.net.EthernetManager;
@@ -183,7 +189,7 @@
 import android.net.INetworkMonitor;
 import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkPolicyListener;
-import android.net.IOnSetOemNetworkPreferenceListener;
+import android.net.IOnCompleteListener;
 import android.net.IQosCallback;
 import android.net.InetAddresses;
 import android.net.InterfaceConfigurationParcel;
@@ -378,6 +384,11 @@
     // Set a non-zero value to verify the flow to set tcp init rwnd value.
     private static final int TEST_TCP_INIT_RWND = 60;
 
+    // Used for testing the per-work-profile default network.
+    private static final int TEST_APP_ID = 103;
+    private static final int TEST_WORK_PROFILE_USER_ID = 2;
+    private static final int TEST_WORK_PROFILE_APP_UID =
+            UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, TEST_APP_ID);
     private static final String CLAT_PREFIX = "v4-";
     private static final String MOBILE_IFNAME = "test_rmnet_data0";
     private static final String WIFI_IFNAME = "test_wlan0";
@@ -421,6 +432,7 @@
     private VpnManagerService mVpnManagerService;
     private TestNetworkCallback mDefaultNetworkCallback;
     private TestNetworkCallback mSystemDefaultNetworkCallback;
+    private TestNetworkCallback mProfileDefaultNetworkCallback;
 
     // State variables required to emulate NetworkPolicyManagerService behaviour.
     private int mUidRules = RULE_NONE;
@@ -542,13 +554,26 @@
             return super.getSystemService(name);
         }
 
+        final HashMap<UserHandle, UserManager> mUserManagers = new HashMap<>();
         @Override
         public Context createContextAsUser(UserHandle user, int flags) {
             final Context asUser = mock(Context.class, AdditionalAnswers.delegatesTo(this));
             doReturn(user).when(asUser).getUser();
+            doAnswer((inv) -> {
+                final UserManager um = mUserManagers.computeIfAbsent(user,
+                        u -> mock(UserManager.class, AdditionalAnswers.delegatesTo(mUserManager)));
+                return um;
+            }).when(asUser).getSystemService(Context.USER_SERVICE);
             return asUser;
         }
 
+        public void setWorkProfile(@NonNull final UserHandle userHandle, boolean value) {
+            // This relies on all contexts for a given user returning the same UM mock
+            final UserManager umMock = createContextAsUser(userHandle, 0 /* flags */)
+                    .getSystemService(UserManager.class);
+            doReturn(value).when(umMock).isManagedProfile();
+        }
+
         @Override
         public ContentResolver getContentResolver() {
             return mContentResolver;
@@ -1408,17 +1433,36 @@
         fail("ConditionVariable was blocked for more than " + TIMEOUT_MS + "ms");
     }
 
-    private void registerNetworkCallbackAsUid(NetworkRequest request, NetworkCallback callback,
-            int uid) {
+    private <T> T doAsUid(final int uid, @NonNull final Supplier<T> what) {
         when(mDeps.getCallingUid()).thenReturn(uid);
         try {
-            mCm.registerNetworkCallback(request, callback);
-            waitForIdle();
+            return what.get();
         } finally {
             returnRealCallingUid();
         }
     }
 
+    private void doAsUid(final int uid, @NonNull final Runnable what) {
+        doAsUid(uid, () -> {
+            what.run(); return Void.TYPE;
+        });
+    }
+
+    private void registerNetworkCallbackAsUid(NetworkRequest request, NetworkCallback callback,
+            int uid) {
+        doAsUid(uid, () -> {
+            mCm.registerNetworkCallback(request, callback);
+        });
+    }
+
+    private void registerDefaultNetworkCallbackAsUid(@NonNull final NetworkCallback callback,
+            final int uid) {
+        doAsUid(uid, () -> {
+            mCm.registerDefaultNetworkCallback(callback);
+            waitForIdle();
+        });
+    }
+
     private static final int PRIMARY_USER = 0;
     private static final int APP1_UID = UserHandle.getUid(PRIMARY_USER, 10100);
     private static final int APP2_UID = UserHandle.getUid(PRIMARY_USER, 10101);
@@ -1465,6 +1509,9 @@
             Looper.prepare();
         }
         mockDefaultPackages();
+        mockHasSystemFeature(FEATURE_WIFI, true);
+        mockHasSystemFeature(FEATURE_WIFI_DIRECT, true);
+        doReturn(true).when(mTelephonyManager).isDataCapable();
 
         FakeSettingsProvider.clearSettingsProvider();
         mServiceContext = new MockContext(InstrumentationRegistry.getContext(),
@@ -1507,7 +1554,7 @@
         mQosCallbackTracker = mock(QosCallbackTracker.class);
 
         // Ensure that the default setting for Captive Portals is used for most tests
-        setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
+        setCaptivePortalMode(ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT);
         setAlwaysOnNetworks(false);
         setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
     }
@@ -1517,10 +1564,7 @@
     }
 
     private ConnectivityService.Dependencies makeDependencies() {
-        doReturn(TEST_TCP_INIT_RWND).when(mSystemProperties)
-                .getInt("net.tcp.default_init_rwnd", 0);
         doReturn(false).when(mSystemProperties).getBoolean("ro.radio.noril", false);
-        doNothing().when(mSystemProperties).setTcpInitRwnd(anyInt());
         final ConnectivityService.Dependencies deps = mock(ConnectivityService.Dependencies.class);
         doReturn(mCsHandlerThread).when(deps).makeHandlerThread();
         doReturn(mNetIdManager).when(deps).makeNetIdManager();
@@ -1578,6 +1622,7 @@
     @After
     public void tearDown() throws Exception {
         unregisterDefaultNetworkCallbacks();
+        maybeTearDownEnterpriseNetwork();
         setAlwaysOnNetworks(false);
         if (mCellNetworkAgent != null) {
             mCellNetworkAgent.disconnect();
@@ -1788,7 +1833,8 @@
         assertTrue(mCm.isNetworkSupported(TYPE_WIFI));
         assertTrue(mCm.isNetworkSupported(TYPE_MOBILE));
         assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_MMS));
-        assertFalse(mCm.isNetworkSupported(TYPE_MOBILE_FOTA));
+        assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_FOTA));
+        assertFalse(mCm.isNetworkSupported(TYPE_PROXY));
 
         // Check that TYPE_ETHERNET is supported. Unlike the asserts above, which only validate our
         // mocks, this assert exercises the ConnectivityService code path that ensures that
@@ -3361,7 +3407,7 @@
                 .addCapability(NET_CAPABILITY_VALIDATED).build();
         mCm.registerNetworkCallback(validatedRequest, validatedCallback);
 
-        setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_AVOID);
+        setCaptivePortalMode(ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID);
         // Bring up a network with a captive portal.
         // Expect it to fail to connect and not result in any callbacks.
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -4011,20 +4057,21 @@
 
     private void setCaptivePortalMode(int mode) {
         ContentResolver cr = mServiceContext.getContentResolver();
-        Settings.Global.putInt(cr, Settings.Global.CAPTIVE_PORTAL_MODE, mode);
+        Settings.Global.putInt(cr, ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE, mode);
     }
 
     private void setAlwaysOnNetworks(boolean enable) {
         ContentResolver cr = mServiceContext.getContentResolver();
-        Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, enable ? 1 : 0);
+        Settings.Global.putInt(cr, ConnectivitySettingsManager.MOBILE_DATA_ALWAYS_ON,
+                enable ? 1 : 0);
         mService.updateAlwaysOnNetworks();
         waitForIdle();
     }
 
     private void setPrivateDnsSettings(String mode, String specifier) {
         final ContentResolver cr = mServiceContext.getContentResolver();
-        Settings.Global.putString(cr, Settings.Global.PRIVATE_DNS_MODE, mode);
-        Settings.Global.putString(cr, Settings.Global.PRIVATE_DNS_SPECIFIER, specifier);
+        Settings.Global.putString(cr, ConnectivitySettingsManager.PRIVATE_DNS_MODE, mode);
+        Settings.Global.putString(cr, ConnectivitySettingsManager.PRIVATE_DNS_SPECIFIER, specifier);
         mService.updatePrivateDnsSettings();
         waitForIdle();
     }
@@ -4262,7 +4309,7 @@
     @Test
     public void testAvoidBadWifiSetting() throws Exception {
         final ContentResolver cr = mServiceContext.getContentResolver();
-        final String settingName = Settings.Global.NETWORK_AVOID_BAD_WIFI;
+        final String settingName = ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI;
 
         mPolicyTracker.mConfigRestrictsAvoidBadWifi = false;
         String[] values = new String[] {null, "0", "1"};
@@ -4319,7 +4366,7 @@
         TestNetworkCallback validatedWifiCallback = new TestNetworkCallback();
         mCm.registerNetworkCallback(validatedWifiRequest, validatedWifiCallback);
 
-        Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 0);
+        Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 0);
         mPolicyTracker.reevaluate();
 
         // Bring up validated cell.
@@ -4387,7 +4434,7 @@
         validatedWifiCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
 
         // Simulate the user selecting "switch" and checking the don't ask again checkbox.
-        Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
+        Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 1);
         mPolicyTracker.reevaluate();
 
         // We now switch to cell.
@@ -4400,11 +4447,11 @@
 
         // Simulate the user turning the cellular fallback setting off and then on.
         // We switch to wifi and then to cell.
-        Settings.Global.putString(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
+        Settings.Global.putString(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, null);
         mPolicyTracker.reevaluate();
         defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), wifiNetwork);
-        Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
+        Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 1);
         mPolicyTracker.reevaluate();
         defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), cellNetwork);
@@ -4423,7 +4470,7 @@
     @Test
     public void testMeteredMultipathPreferenceSetting() throws Exception {
         final ContentResolver cr = mServiceContext.getContentResolver();
-        final String settingName = Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
+        final String settingName = ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE;
 
         for (int config : Arrays.asList(0, 3, 2)) {
             for (String setting: Arrays.asList(null, "0", "2", "1")) {
@@ -7997,7 +8044,6 @@
 
         // Switching default network updates TCP buffer sizes.
         verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES);
-        verify(mSystemProperties, times(1)).setTcpInitRwnd(eq(TEST_TCP_INIT_RWND));
         // Add an IPv4 address. Expect prefix discovery to be stopped. Netd doesn't tell us that
         // the NAT64 prefix was removed because one was never discovered.
         cellLp.addLinkAddress(myIpv4);
@@ -8483,14 +8529,12 @@
         mCellNetworkAgent.connect(false);
         networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES);
-        verify(mSystemProperties, times(1)).setTcpInitRwnd(eq(TEST_TCP_INIT_RWND));
         // Change link Properties should have updated tcp buffer size.
         LinkProperties lp = new LinkProperties();
         lp.setTcpBufferSizes(testTcpBufferSizes);
         mCellNetworkAgent.sendLinkProperties(lp);
         networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         verifyTcpBufferSizeChange(testTcpBufferSizes);
-        verify(mSystemProperties, times(2)).setTcpInitRwnd(eq(TEST_TCP_INIT_RWND));
         // Clean up.
         mCellNetworkAgent.disconnect();
         networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
@@ -10118,9 +10162,12 @@
                 Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
         mSystemDefaultNetworkCallback = new TestNetworkCallback();
         mDefaultNetworkCallback = new TestNetworkCallback();
+        mProfileDefaultNetworkCallback = new TestNetworkCallback();
         mCm.registerSystemDefaultNetworkCallback(mSystemDefaultNetworkCallback,
                 new Handler(ConnectivityThread.getInstanceLooper()));
         mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback);
+        registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallback,
+                TEST_WORK_PROFILE_APP_UID);
         mServiceContext.setPermission(
                 Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED);
     }
@@ -10132,6 +10179,9 @@
         if (null != mSystemDefaultNetworkCallback) {
             mCm.unregisterNetworkCallback(mSystemDefaultNetworkCallback);
         }
+        if (null != mProfileDefaultNetworkCallback) {
+            mCm.unregisterNetworkCallback(mProfileDefaultNetworkCallback);
+        }
     }
 
     private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(
@@ -10187,7 +10237,7 @@
         oemPrefListener.expectOnComplete();
     }
 
-    private static class TestOemListenerCallback implements IOnSetOemNetworkPreferenceListener {
+    private static class TestOemListenerCallback implements IOnCompleteListener {
         final CompletableFuture<Object> mDone = new CompletableFuture<>();
 
         @Override
@@ -11220,4 +11270,399 @@
         mCellNetworkAgent.disconnect();
         bestMatchingCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
     }
+
+    private UidRangeParcel[] uidRangeFor(final UserHandle handle) {
+        UidRange range = UidRange.createForUser(handle);
+        return new UidRangeParcel[] { new UidRangeParcel(range.start, range.stop) };
+    }
+
+    private static class TestOnCompleteListener implements Runnable {
+        final class OnComplete {}
+        final ArrayTrackRecord<OnComplete>.ReadHead mHistory =
+                new ArrayTrackRecord<OnComplete>().newReadHead();
+
+        @Override
+        public void run() {
+            mHistory.add(new OnComplete());
+        }
+
+        public void expectOnComplete() {
+            assertNotNull(mHistory.poll(TIMEOUT_MS, it -> true));
+        }
+    }
+
+    private TestNetworkAgentWrapper makeEnterpriseNetworkAgent() throws Exception {
+        final NetworkCapabilities workNc = new NetworkCapabilities();
+        workNc.addCapability(NET_CAPABILITY_ENTERPRISE);
+        workNc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+        return new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(), workNc);
+    }
+
+    private TestNetworkCallback mEnterpriseCallback;
+    private UserHandle setupEnterpriseNetwork() {
+        final UserHandle userHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
+        mServiceContext.setWorkProfile(userHandle, true);
+
+        // File a request to avoid the enterprise network being disconnected as soon as the default
+        // request goes away – it would make impossible to test that networkRemoveUidRanges
+        // is called, as the network would disconnect first for lack of a request.
+        mEnterpriseCallback = new TestNetworkCallback();
+        final NetworkRequest keepUpRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_ENTERPRISE)
+                .build();
+        mCm.requestNetwork(keepUpRequest, mEnterpriseCallback);
+        return userHandle;
+    }
+
+    private void maybeTearDownEnterpriseNetwork() {
+        if (null != mEnterpriseCallback) {
+            mCm.unregisterNetworkCallback(mEnterpriseCallback);
+        }
+    }
+
+    /**
+     * Make sure per-profile networking preference behaves as expected when the enterprise network
+     * goes up and down while the preference is active. Make sure they behave as expected whether
+     * there is a general default network or not.
+     */
+    @Test
+    public void testPreferenceForUserNetworkUpDown() throws Exception {
+        final InOrder inOrder = inOrder(mMockNetd);
+        final UserHandle testHandle = setupEnterpriseNetwork();
+        registerDefaultNetworkCallbacks();
+
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+
+        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId,
+                INetd.PERMISSION_NONE);
+
+        final TestOnCompleteListener listener = new TestOnCompleteListener();
+        mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+                r -> r.run(), listener);
+        listener.expectOnComplete();
+
+        // Setting a network preference for this user will create a new set of routing rules for
+        // the UID range that corresponds to this user, so as to define the default network
+        // for these apps separately. This is true because the multi-layer request relevant to
+        // this UID range contains a TRACK_DEFAULT, so the range will be moved through UID-specific
+        // rules to the correct network – in this case the system default network. The case where
+        // the default network for the profile happens to be the same as the system default
+        // is not handled specially, the rules are always active as long as a preference is set.
+        inOrder.verify(mMockNetd).networkAddUidRanges(mCellNetworkAgent.getNetwork().netId,
+                uidRangeFor(testHandle));
+
+        // The enterprise network is not ready yet.
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback,
+                mProfileDefaultNetworkCallback);
+
+        final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent();
+        workAgent.connect(false);
+
+        mProfileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent);
+        mSystemDefaultNetworkCallback.assertNoCallback();
+        mDefaultNetworkCallback.assertNoCallback();
+        inOrder.verify(mMockNetd).networkCreatePhysical(workAgent.getNetwork().netId,
+                INetd.PERMISSION_SYSTEM);
+        inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId,
+                uidRangeFor(testHandle));
+        inOrder.verify(mMockNetd).networkRemoveUidRanges(mCellNetworkAgent.getNetwork().netId,
+                uidRangeFor(testHandle));
+
+        // Make sure changes to the work agent send callbacks to the app in the work profile, but
+        // not to the other apps.
+        workAgent.setNetworkValid(true /* isStrictMode */);
+        workAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+        mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent,
+                nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED)
+                        && nc.hasCapability(NET_CAPABILITY_ENTERPRISE));
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+
+        workAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+        mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, nc ->
+                nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+
+        // Conversely, change a capability on the system-wide default network and make sure
+        // that only the apps outside of the work profile receive the callbacks.
+        mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+        mSystemDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc ->
+                nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+        mDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc ->
+                nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+        mProfileDefaultNetworkCallback.assertNoCallback();
+
+        // Disconnect and reconnect the system-wide default network and make sure that the
+        // apps on this network see the appropriate callbacks, and the app on the work profile
+        // doesn't because it continues to use the enterprise network.
+        mCellNetworkAgent.disconnect();
+        mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+        mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+        mProfileDefaultNetworkCallback.assertNoCallback();
+        inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId);
+
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mProfileDefaultNetworkCallback.assertNoCallback();
+        inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId,
+                INetd.PERMISSION_NONE);
+
+        // When the agent disconnects, test that the app on the work profile falls back to the
+        // default network.
+        workAgent.disconnect();
+        mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent);
+        mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+        inOrder.verify(mMockNetd).networkAddUidRanges(mCellNetworkAgent.getNetwork().netId,
+                uidRangeFor(testHandle));
+        inOrder.verify(mMockNetd).networkDestroy(workAgent.getNetwork().netId);
+
+        mCellNetworkAgent.disconnect();
+        mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+        mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+        mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+
+        // Waiting for the handler to be idle before checking for networkDestroy is necessary
+        // here because ConnectivityService calls onLost before the network is fully torn down.
+        waitForIdle();
+        inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId);
+
+        // If the control comes here, callbacks seem to behave correctly in the presence of
+        // a default network when the enterprise network goes up and down. Now, make sure they
+        // also behave correctly in the absence of a system-wide default network.
+        final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent();
+        workAgent2.connect(false);
+
+        mProfileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent2);
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+        inOrder.verify(mMockNetd).networkCreatePhysical(workAgent2.getNetwork().netId,
+                INetd.PERMISSION_SYSTEM);
+        inOrder.verify(mMockNetd).networkAddUidRanges(workAgent2.getNetwork().netId,
+                uidRangeFor(testHandle));
+
+        workAgent2.setNetworkValid(true /* isStrictMode */);
+        workAgent2.mNetworkMonitor.forceReevaluation(Process.myUid());
+        mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent2,
+                nc -> nc.hasCapability(NET_CAPABILITY_ENTERPRISE)
+                        && !nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+        inOrder.verify(mMockNetd, never()).networkAddUidRanges(anyInt(), any());
+
+        // When the agent disconnects, test that the app on the work profile falls back to the
+        // default network.
+        workAgent2.disconnect();
+        mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent2);
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+        inOrder.verify(mMockNetd).networkDestroy(workAgent2.getNetwork().netId);
+
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback,
+                mProfileDefaultNetworkCallback);
+
+        // Callbacks will be unregistered by tearDown()
+    }
+
+    /**
+     * Test that, in a given networking context, calling setPreferenceForUser to set per-profile
+     * defaults on then off works as expected.
+     */
+    @Test
+    public void testSetPreferenceForUserOnOff() throws Exception {
+        final InOrder inOrder = inOrder(mMockNetd);
+        final UserHandle testHandle = setupEnterpriseNetwork();
+
+        // Connect both a regular cell agent and an enterprise network first.
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+
+        final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent();
+        workAgent.connect(true);
+
+        final TestOnCompleteListener listener = new TestOnCompleteListener();
+        mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+                r -> r.run(), listener);
+        listener.expectOnComplete();
+        inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId,
+                INetd.PERMISSION_NONE);
+        inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId,
+                uidRangeFor(testHandle));
+
+        registerDefaultNetworkCallbacks();
+
+        mSystemDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent);
+
+        mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_DEFAULT,
+                r -> r.run(), listener);
+        listener.expectOnComplete();
+
+        mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+        inOrder.verify(mMockNetd).networkRemoveUidRanges(workAgent.getNetwork().netId,
+                uidRangeFor(testHandle));
+
+        workAgent.disconnect();
+        mCellNetworkAgent.disconnect();
+
+        // Callbacks will be unregistered by tearDown()
+    }
+
+    /**
+     * Test per-profile default networks for two different profiles concurrently.
+     */
+    @Test
+    public void testSetPreferenceForTwoProfiles() throws Exception {
+        final InOrder inOrder = inOrder(mMockNetd);
+        final UserHandle testHandle2 = setupEnterpriseNetwork();
+        final UserHandle testHandle4 = UserHandle.of(TEST_WORK_PROFILE_USER_ID + 2);
+        mServiceContext.setWorkProfile(testHandle4, true);
+        registerDefaultNetworkCallbacks();
+
+        final TestNetworkCallback app4Cb = new TestNetworkCallback();
+        final int testWorkProfileAppUid4 =
+                UserHandle.getUid(testHandle4.getIdentifier(), TEST_APP_ID);
+        registerDefaultNetworkCallbackAsUid(app4Cb, testWorkProfileAppUid4);
+
+        // Connect both a regular cell agent and an enterprise network first.
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+
+        final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent();
+        workAgent.connect(true);
+
+        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        app4Cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId,
+                INetd.PERMISSION_NONE);
+        inOrder.verify(mMockNetd).networkCreatePhysical(workAgent.getNetwork().netId,
+                INetd.PERMISSION_SYSTEM);
+
+        final TestOnCompleteListener listener = new TestOnCompleteListener();
+        mCm.setProfileNetworkPreference(testHandle2, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+                r -> r.run(), listener);
+        listener.expectOnComplete();
+        inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId,
+                uidRangeFor(testHandle2));
+
+        mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent);
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback,
+                app4Cb);
+
+        mCm.setProfileNetworkPreference(testHandle4, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+                r -> r.run(), listener);
+        listener.expectOnComplete();
+        inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId,
+                uidRangeFor(testHandle4));
+
+        app4Cb.expectAvailableCallbacksValidated(workAgent);
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback,
+                mProfileDefaultNetworkCallback);
+
+        mCm.setProfileNetworkPreference(testHandle2, PROFILE_NETWORK_PREFERENCE_DEFAULT,
+                r -> r.run(), listener);
+        listener.expectOnComplete();
+        inOrder.verify(mMockNetd).networkRemoveUidRanges(workAgent.getNetwork().netId,
+                uidRangeFor(testHandle2));
+
+        mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback,
+                app4Cb);
+
+        workAgent.disconnect();
+        mCellNetworkAgent.disconnect();
+
+        mCm.unregisterNetworkCallback(app4Cb);
+        // Other callbacks will be unregistered by tearDown()
+    }
+
+    @Test
+    public void testProfilePreferenceRemovedUponUserRemoved() throws Exception {
+        final InOrder inOrder = inOrder(mMockNetd);
+        final UserHandle testHandle = setupEnterpriseNetwork();
+
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+
+        final TestOnCompleteListener listener = new TestOnCompleteListener();
+        mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+                r -> r.run(), listener);
+        listener.expectOnComplete();
+        inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId,
+                INetd.PERMISSION_NONE);
+        inOrder.verify(mMockNetd).networkAddUidRanges(mCellNetworkAgent.getNetwork().netId,
+                uidRangeFor(testHandle));
+
+        final Intent removedIntent = new Intent(ACTION_USER_REMOVED);
+        removedIntent.putExtra(Intent.EXTRA_USER, testHandle);
+        processBroadcast(removedIntent);
+
+        inOrder.verify(mMockNetd).networkRemoveUidRanges(mCellNetworkAgent.getNetwork().netId,
+                uidRangeFor(testHandle));
+    }
+
+    /**
+     * Make sure that OEM preference and per-profile preference can't be used at the same
+     * time and throw ISE if tried
+     */
+    @Test
+    public void testOemPreferenceAndProfilePreferenceExclusive() throws Exception {
+        final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
+        mServiceContext.setWorkProfile(testHandle, true);
+        final TestOnCompleteListener listener = new TestOnCompleteListener();
+
+        setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(
+                OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY);
+        assertThrows("Should not be able to set per-profile pref while OEM prefs present",
+                IllegalStateException.class, () ->
+                        mCm.setProfileNetworkPreference(testHandle,
+                                PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+                                r -> r.run(), listener));
+
+        // Empty the OEM prefs
+        final TestOemListenerCallback oemPrefListener = new TestOemListenerCallback();
+        final OemNetworkPreferences emptyOemPref = new OemNetworkPreferences.Builder().build();
+        mService.setOemNetworkPreference(emptyOemPref, oemPrefListener);
+        oemPrefListener.expectOnComplete();
+
+        mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+                r -> r.run(), listener);
+        listener.expectOnComplete();
+        assertThrows("Should not be able to set OEM prefs while per-profile pref is on",
+                IllegalStateException.class , () ->
+                        mService.setOemNetworkPreference(emptyOemPref, oemPrefListener));
+    }
+
+    /**
+     * Make sure wrong preferences for per-profile default networking are rejected.
+     */
+    @Test
+    public void testProfileNetworkPrefWrongPreference() throws Exception {
+        final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
+        mServiceContext.setWorkProfile(testHandle, true);
+        assertThrows("Should not be able to set an illegal preference",
+                IllegalArgumentException.class,
+                () -> mCm.setProfileNetworkPreference(testHandle,
+                        PROFILE_NETWORK_PREFERENCE_ENTERPRISE + 1, null, null));
+    }
+
+    /**
+     * Make sure requests for per-profile default networking for a non-work profile are
+     * rejected
+     */
+    @Test
+    public void testProfileNetworkPrefWrongProfile() throws Exception {
+        final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
+        mServiceContext.setWorkProfile(testHandle, false);
+        assertThrows("Should not be able to set a user pref for a non-work profile",
+                IllegalArgumentException.class , () ->
+                        mCm.setProfileNetworkPreference(testHandle,
+                                PROFILE_NETWORK_PREFERENCE_ENTERPRISE, null, null));
+    }
 }
diff --git a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt
index a10a3c8..5ec1119 100644
--- a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt
+++ b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt
@@ -21,13 +21,29 @@
 
 package com.android.server
 
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.FEATURE_WIFI
+import android.content.pm.PackageManager.FEATURE_WIFI_DIRECT
 import android.net.ConnectivityManager.TYPE_ETHERNET
 import android.net.ConnectivityManager.TYPE_MOBILE
+import android.net.ConnectivityManager.TYPE_MOBILE_CBS
+import android.net.ConnectivityManager.TYPE_MOBILE_DUN
+import android.net.ConnectivityManager.TYPE_MOBILE_EMERGENCY
+import android.net.ConnectivityManager.TYPE_MOBILE_FOTA
+import android.net.ConnectivityManager.TYPE_MOBILE_HIPRI
+import android.net.ConnectivityManager.TYPE_MOBILE_IA
+import android.net.ConnectivityManager.TYPE_MOBILE_IMS
+import android.net.ConnectivityManager.TYPE_MOBILE_MMS
 import android.net.ConnectivityManager.TYPE_MOBILE_SUPL
+import android.net.ConnectivityManager.TYPE_VPN
 import android.net.ConnectivityManager.TYPE_WIFI
+import android.net.ConnectivityManager.TYPE_WIFI_P2P
 import android.net.ConnectivityManager.TYPE_WIMAX
+import android.net.EthernetManager
 import android.net.NetworkInfo.DetailedState.CONNECTED
 import android.net.NetworkInfo.DetailedState.DISCONNECTED
+import android.telephony.TelephonyManager
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
 import com.android.server.ConnectivityService.LegacyTypeTracker
@@ -36,7 +52,6 @@
 import org.junit.Assert.assertNull
 import org.junit.Assert.assertSame
 import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.any
@@ -52,88 +67,130 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class LegacyTypeTrackerTest {
-    private val supportedTypes = arrayOf(TYPE_MOBILE, TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_SUPL)
+    private val supportedTypes = arrayOf(TYPE_WIFI, TYPE_WIFI_P2P, TYPE_ETHERNET, TYPE_MOBILE,
+            TYPE_MOBILE_SUPL, TYPE_MOBILE_MMS, TYPE_MOBILE_SUPL, TYPE_MOBILE_DUN, TYPE_MOBILE_HIPRI,
+            TYPE_MOBILE_FOTA, TYPE_MOBILE_IMS, TYPE_MOBILE_CBS, TYPE_MOBILE_IA,
+            TYPE_MOBILE_EMERGENCY, TYPE_VPN)
 
     private val mMockService = mock(ConnectivityService::class.java).apply {
         doReturn(false).`when`(this).isDefaultNetwork(any())
     }
-    private val mTracker = LegacyTypeTracker(mMockService).apply {
-        supportedTypes.forEach {
-            addSupportedType(it)
-        }
+    private val mPm = mock(PackageManager::class.java)
+    private val mContext = mock(Context::class.java).apply {
+        doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_WIFI)
+        doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_WIFI_DIRECT)
+        doReturn(mPm).`when`(this).packageManager
+        doReturn(mock(EthernetManager::class.java)).`when`(this).getSystemService(
+                Context.ETHERNET_SERVICE)
+    }
+    private val mTm = mock(TelephonyManager::class.java).apply {
+        doReturn(true).`when`(this).isDataCapable
+    }
+
+    private fun makeTracker() = LegacyTypeTracker(mMockService).apply {
+        loadSupportedTypes(mContext, mTm)
     }
 
     @Test
     fun testSupportedTypes() {
-        try {
-            mTracker.addSupportedType(supportedTypes[0])
-            fail("Expected IllegalStateException")
-        } catch (expected: IllegalStateException) {}
+        val tracker = makeTracker()
         supportedTypes.forEach {
-            assertTrue(mTracker.isTypeSupported(it))
+            assertTrue(tracker.isTypeSupported(it))
         }
-        assertFalse(mTracker.isTypeSupported(UNSUPPORTED_TYPE))
+        assertFalse(tracker.isTypeSupported(UNSUPPORTED_TYPE))
+    }
+
+    @Test
+    fun testSupportedTypes_NoEthernet() {
+        doReturn(null).`when`(mContext).getSystemService(Context.ETHERNET_SERVICE)
+        assertFalse(makeTracker().isTypeSupported(TYPE_ETHERNET))
+    }
+
+    @Test
+    fun testSupportedTypes_NoTelephony() {
+        doReturn(false).`when`(mTm).isDataCapable
+        val tracker = makeTracker()
+        val nonMobileTypes = arrayOf(TYPE_WIFI, TYPE_WIFI_P2P, TYPE_ETHERNET, TYPE_VPN)
+        nonMobileTypes.forEach {
+            assertTrue(tracker.isTypeSupported(it))
+        }
+        supportedTypes.toSet().minus(nonMobileTypes).forEach {
+            assertFalse(tracker.isTypeSupported(it))
+        }
+    }
+
+    @Test
+    fun testSupportedTypes_NoWifiDirect() {
+        doReturn(false).`when`(mPm).hasSystemFeature(FEATURE_WIFI_DIRECT)
+        val tracker = makeTracker()
+        assertFalse(tracker.isTypeSupported(TYPE_WIFI_P2P))
+        supportedTypes.toSet().minus(TYPE_WIFI_P2P).forEach {
+            assertTrue(tracker.isTypeSupported(it))
+        }
     }
 
     @Test
     fun testSupl() {
+        val tracker = makeTracker()
         val mobileNai = mock(NetworkAgentInfo::class.java)
-        mTracker.add(TYPE_MOBILE, mobileNai)
+        tracker.add(TYPE_MOBILE, mobileNai)
         verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE)
         reset(mMockService)
-        mTracker.add(TYPE_MOBILE_SUPL, mobileNai)
+        tracker.add(TYPE_MOBILE_SUPL, mobileNai)
         verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE_SUPL)
         reset(mMockService)
-        mTracker.remove(TYPE_MOBILE_SUPL, mobileNai, false /* wasDefault */)
+        tracker.remove(TYPE_MOBILE_SUPL, mobileNai, false /* wasDefault */)
         verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE_SUPL)
         reset(mMockService)
-        mTracker.add(TYPE_MOBILE_SUPL, mobileNai)
+        tracker.add(TYPE_MOBILE_SUPL, mobileNai)
         verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE_SUPL)
         reset(mMockService)
-        mTracker.remove(mobileNai, false)
+        tracker.remove(mobileNai, false)
         verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE_SUPL)
         verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE)
     }
 
     @Test
     fun testAddNetwork() {
+        val tracker = makeTracker()
         val mobileNai = mock(NetworkAgentInfo::class.java)
         val wifiNai = mock(NetworkAgentInfo::class.java)
-        mTracker.add(TYPE_MOBILE, mobileNai)
-        mTracker.add(TYPE_WIFI, wifiNai)
-        assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai)
-        assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
+        tracker.add(TYPE_MOBILE, mobileNai)
+        tracker.add(TYPE_WIFI, wifiNai)
+        assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai)
+        assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai)
         // Make sure adding a second NAI does not change the results.
         val secondMobileNai = mock(NetworkAgentInfo::class.java)
-        mTracker.add(TYPE_MOBILE, secondMobileNai)
-        assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai)
-        assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
+        tracker.add(TYPE_MOBILE, secondMobileNai)
+        assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai)
+        assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai)
         // Make sure removing a network that wasn't added for this type is a no-op.
-        mTracker.remove(TYPE_MOBILE, wifiNai, false /* wasDefault */)
-        assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai)
-        assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
+        tracker.remove(TYPE_MOBILE, wifiNai, false /* wasDefault */)
+        assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai)
+        assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai)
         // Remove the top network for mobile and make sure the second one becomes the network
         // of record for this type.
-        mTracker.remove(TYPE_MOBILE, mobileNai, false /* wasDefault */)
-        assertSame(mTracker.getNetworkForType(TYPE_MOBILE), secondMobileNai)
-        assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
+        tracker.remove(TYPE_MOBILE, mobileNai, false /* wasDefault */)
+        assertSame(tracker.getNetworkForType(TYPE_MOBILE), secondMobileNai)
+        assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai)
         // Make sure adding a network for an unsupported type does not register it.
-        mTracker.add(UNSUPPORTED_TYPE, mobileNai)
-        assertNull(mTracker.getNetworkForType(UNSUPPORTED_TYPE))
+        tracker.add(UNSUPPORTED_TYPE, mobileNai)
+        assertNull(tracker.getNetworkForType(UNSUPPORTED_TYPE))
     }
 
     @Test
     fun testBroadcastOnDisconnect() {
+        val tracker = makeTracker()
         val mobileNai1 = mock(NetworkAgentInfo::class.java)
         val mobileNai2 = mock(NetworkAgentInfo::class.java)
         doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai1)
-        mTracker.add(TYPE_MOBILE, mobileNai1)
+        tracker.add(TYPE_MOBILE, mobileNai1)
         verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, CONNECTED, TYPE_MOBILE)
         reset(mMockService)
         doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai2)
-        mTracker.add(TYPE_MOBILE, mobileNai2)
+        tracker.add(TYPE_MOBILE, mobileNai2)
         verify(mMockService, never()).sendLegacyNetworkBroadcast(any(), any(), anyInt())
-        mTracker.remove(TYPE_MOBILE, mobileNai1, false /* wasDefault */)
+        tracker.remove(TYPE_MOBILE, mobileNai1, false /* wasDefault */)
         verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, DISCONNECTED, TYPE_MOBILE)
         verify(mMockService).sendLegacyNetworkBroadcast(mobileNai2, CONNECTED, TYPE_MOBILE)
     }
diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
index 5760211..692c50f 100644
--- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
@@ -18,15 +18,15 @@
 
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_SPECIFIER;
 import static android.net.NetworkCapabilities.MAX_TRANSPORT;
 import static android.net.NetworkCapabilities.MIN_TRANSPORT;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS;
-import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
-import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
-import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
 
 import static com.android.testutils.MiscAsserts.assertContainsExactly;
 import static com.android.testutils.MiscAsserts.assertContainsStringsExactly;
@@ -312,14 +312,14 @@
     @Test
     public void testOverrideDefaultMode() throws Exception {
         // Hard-coded default is opportunistic mode.
-        final PrivateDnsConfig cfgAuto = DnsManager.getPrivateDnsConfig(mContentResolver);
+        final PrivateDnsConfig cfgAuto = DnsManager.getPrivateDnsConfig(mCtx);
         assertTrue(cfgAuto.useTls);
         assertEquals("", cfgAuto.hostname);
         assertEquals(new InetAddress[0], cfgAuto.ips);
 
         // Pretend a gservices push sets the default to "off".
         Settings.Global.putString(mContentResolver, PRIVATE_DNS_DEFAULT_MODE, "off");
-        final PrivateDnsConfig cfgOff = DnsManager.getPrivateDnsConfig(mContentResolver);
+        final PrivateDnsConfig cfgOff = DnsManager.getPrivateDnsConfig(mCtx);
         assertFalse(cfgOff.useTls);
         assertEquals("", cfgOff.hostname);
         assertEquals(new InetAddress[0], cfgOff.ips);
@@ -328,7 +328,7 @@
         Settings.Global.putString(
                 mContentResolver, PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
         Settings.Global.putString(mContentResolver, PRIVATE_DNS_SPECIFIER, "strictmode.com");
-        final PrivateDnsConfig cfgStrict = DnsManager.getPrivateDnsConfig(mContentResolver);
+        final PrivateDnsConfig cfgStrict = DnsManager.getPrivateDnsConfig(mCtx);
         assertTrue(cfgStrict.useTls);
         assertEquals("strictmode.com", cfgStrict.hostname);
         assertEquals(new InetAddress[0], cfgStrict.ips);