Merge changes from topic "connectivity-cts-cherrypicks" into sc-dev

* changes:
  Use assertEquals instead of assertTrue to compare the vpn type
  Add CTS for systemReady() & getIpSecNetIdRange()
  Call ConnectivitySettingsUtils to set/get private DNS related settings
  Add CTS for isUidNetworkingBlocked & isUidRestrictedOnMeteredNetworks
  Add test for CSM#[get|set]MobileDataPreferredUids
  Add test for setHttpProxyConfiguration
  CTS test for PacProxyManager
  Add test for CM#setAcceptUnvalidated
  Add test for CM#setAcceptPartialConnectivity
  Skip CaptivePortalTest for wearables
  Improve handling of invalid private DNS settings
diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java
index 03c3600..4644e4f 100644
--- a/framework/src/android/net/ConnectivitySettingsManager.java
+++ b/framework/src/android/net/ConnectivitySettingsManager.java
@@ -20,12 +20,13 @@
 import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE;
 import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
 
+import static com.android.net.module.util.ConnectivitySettingsUtils.getPrivateDnsModeAsString;
+
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.net.ConnectivityManager.MultipathPreference;
 import android.os.Process;
@@ -35,6 +36,7 @@
 import android.util.ArraySet;
 import android.util.Range;
 
+import com.android.net.module.util.ConnectivitySettingsUtils;
 import com.android.net.module.util.ProxyUtils;
 
 import java.lang.annotation.Retention;
@@ -345,20 +347,22 @@
     /**
      * One of the private DNS modes that indicates the private DNS mode is off.
      */
-    public static final int PRIVATE_DNS_MODE_OFF = 1;
+    public static final int PRIVATE_DNS_MODE_OFF = ConnectivitySettingsUtils.PRIVATE_DNS_MODE_OFF;
 
     /**
      * One of the private DNS modes that indicates the private DNS mode is automatic, which
      * will try to use the current DNS as private DNS.
      */
-    public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC = 2;
+    public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC =
+            ConnectivitySettingsUtils.PRIVATE_DNS_MODE_OPPORTUNISTIC;
 
     /**
      * One of the private DNS modes that indicates the private DNS mode is strict and the
      * {@link #PRIVATE_DNS_SPECIFIER} is required, which will try to use the value of
      * {@link #PRIVATE_DNS_SPECIFIER} as private DNS.
      */
-    public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3;
+    public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME =
+            ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -369,10 +373,6 @@
     })
     public @interface PrivateDnsMode {}
 
-    private static final String PRIVATE_DNS_MODE_OFF_STRING = "off";
-    private static final String PRIVATE_DNS_MODE_OPPORTUNISTIC_STRING = "opportunistic";
-    private static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING = "hostname";
-
     /**
      * A list of uids that is allowed to use restricted networks.
      *
@@ -730,32 +730,6 @@
                 context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, "" /* value */);
     }
 
-    private static String getPrivateDnsModeAsString(@PrivateDnsMode int mode) {
-        switch (mode) {
-            case PRIVATE_DNS_MODE_OFF:
-                return PRIVATE_DNS_MODE_OFF_STRING;
-            case PRIVATE_DNS_MODE_OPPORTUNISTIC:
-                return PRIVATE_DNS_MODE_OPPORTUNISTIC_STRING;
-            case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME:
-                return PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING;
-            default:
-                throw new IllegalArgumentException("Invalid private dns mode: " + mode);
-        }
-    }
-
-    private static int getPrivateDnsModeAsInt(String mode) {
-        switch (mode) {
-            case "off":
-                return PRIVATE_DNS_MODE_OFF;
-            case "hostname":
-                return PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
-            case "opportunistic":
-                return PRIVATE_DNS_MODE_OPPORTUNISTIC;
-            default:
-                throw new IllegalArgumentException("Invalid private dns mode: " + mode);
-        }
-    }
-
     /**
      * Get private DNS mode from settings.
      *
@@ -764,13 +738,7 @@
      */
     @PrivateDnsMode
     public static int 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
-        // PRIVATE_DNS_MODE_OPPORTUNISTIC as default mode.
-        if (TextUtils.isEmpty(mode)) return PRIVATE_DNS_MODE_OPPORTUNISTIC;
-        return getPrivateDnsModeAsInt(mode);
+        return ConnectivitySettingsUtils.getPrivateDnsMode(context);
     }
 
     /**
@@ -780,13 +748,7 @@
      * @param mode The private dns mode. This should be one of the PRIVATE_DNS_MODE_* constants.
      */
     public static void setPrivateDnsMode(@NonNull Context context, @PrivateDnsMode int mode) {
-        if (!(mode == PRIVATE_DNS_MODE_OFF
-                || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC
-                || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) {
-            throw new IllegalArgumentException("Invalid private dns mode: " + mode);
-        }
-        Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_MODE,
-                getPrivateDnsModeAsString(mode));
+        ConnectivitySettingsUtils.setPrivateDnsMode(context, mode);
     }
 
     /**
@@ -797,7 +759,7 @@
      */
     @Nullable
     public static String getPrivateDnsHostname(@NonNull Context context) {
-        return Settings.Global.getString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER);
+        return ConnectivitySettingsUtils.getPrivateDnsHostname(context);
     }
 
     /**
@@ -806,9 +768,8 @@
      * @param context The {@link Context} to set the setting.
      * @param specifier The specific private dns provider name.
      */
-    public static void setPrivateDnsHostname(@NonNull Context context,
-            @Nullable String specifier) {
-        Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER, specifier);
+    public static void setPrivateDnsHostname(@NonNull Context context, @Nullable String specifier) {
+        ConnectivitySettingsUtils.setPrivateDnsHostname(context, specifier);
     }
 
     /**
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 5b95eea..5352a60 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -975,4 +975,15 @@
          */
         String getExpected();
     }
+
+    protected void setRestrictedNetworkingMode(boolean enabled) throws Exception {
+        executeSilentShellCommand(
+                "settings put global restricted_networking_mode " + (enabled ? 1 : 0));
+        assertRestrictedNetworkingModeState(enabled);
+    }
+
+    protected void assertRestrictedNetworkingModeState(boolean enabled) throws Exception {
+        assertDelayedShellCommand("cmd netpolicy get restricted-mode",
+                "Restricted mode status: " + (enabled ? "enabled" : "disabled"));
+    }
 }
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java
new file mode 100644
index 0000000..ddc5fd4
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.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 com.android.cts.net.hostside;
+
+import static android.os.Process.SYSTEM_UID;
+
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.assertNetworkingBlockedStatusForUid;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isUidNetworkingBlocked;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isUidRestrictedOnMeteredNetworks;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class NetworkPolicyManagerTest extends AbstractRestrictBackgroundNetworkTestCase {
+    private static final boolean METERED = true;
+    private static final boolean NON_METERED = false;
+
+    @Rule
+    public final MeterednessConfigurationRule mMeterednessConfiguration =
+            new MeterednessConfigurationRule();
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        assumeTrue(canChangeActiveNetworkMeteredness());
+
+        registerBroadcastReceiver();
+
+        removeRestrictBackgroundWhitelist(mUid);
+        removeRestrictBackgroundBlacklist(mUid);
+        assertRestrictBackgroundChangedReceived(0);
+
+        // Initial state
+        setBatterySaverMode(false);
+        setRestrictBackground(false);
+        setRestrictedNetworkingMode(false);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+
+        setBatterySaverMode(false);
+        setRestrictBackground(false);
+        setRestrictedNetworkingMode(false);
+        unregisterNetworkCallback();
+    }
+
+    @Test
+    public void testIsUidNetworkingBlocked_withUidNotBlocked() throws Exception {
+        // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
+        // test the cases of non-metered network and uid not matched by any rule.
+        // If mUid is not blocked by data saver mode or power saver mode, no matter the network is
+        // metered or non-metered, mUid shouldn't be blocked.
+        assertFalse(isUidNetworkingBlocked(mUid, METERED)); // Match NTWK_ALLOWED_DEFAULT
+        assertFalse(isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
+    }
+
+    @Test
+    public void testIsUidNetworkingBlocked_withSystemUid() throws Exception {
+        // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
+        // test the case of uid is system uid.
+        // SYSTEM_UID will never be blocked.
+        assertFalse(isUidNetworkingBlocked(SYSTEM_UID, METERED)); // Match NTWK_ALLOWED_SYSTEM
+        assertFalse(isUidNetworkingBlocked(SYSTEM_UID, NON_METERED)); // Match NTWK_ALLOWED_SYSTEM
+        try {
+            setRestrictBackground(true);
+            setBatterySaverMode(true);
+            setRestrictedNetworkingMode(true);
+            assertNetworkingBlockedStatusForUid(SYSTEM_UID, METERED,
+                    false /* expectedResult */); // Match NTWK_ALLOWED_SYSTEM
+            assertFalse(
+                    isUidNetworkingBlocked(SYSTEM_UID, NON_METERED)); // Match NTWK_ALLOWED_SYSTEM
+        } finally {
+            setRestrictBackground(false);
+            setBatterySaverMode(false);
+            setRestrictedNetworkingMode(false);
+            assertNetworkingBlockedStatusForUid(mUid, METERED,
+                    false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
+        }
+    }
+
+    @Test
+    public void testIsUidNetworkingBlocked_withDataSaverMode() throws Exception {
+        // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
+        // test the cases of non-metered network, uid is matched by restrict background blacklist,
+        // uid is matched by restrict background whitelist, app is in the foreground with restrict
+        // background enabled and the app is in the background with restrict background enabled.
+        try {
+            // Enable restrict background and mUid will be blocked because it's not in the
+            // foreground.
+            setRestrictBackground(true);
+            assertNetworkingBlockedStatusForUid(mUid, METERED,
+                    true /* expectedResult */); // Match NTWK_BLOCKED_BG_RESTRICT
+
+            // Although restrict background is enabled and mUid is in the background, but mUid will
+            // not be blocked if network is non-metered.
+            assertFalse(
+                    isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
+
+            // Add mUid into the restrict background blacklist.
+            addRestrictBackgroundBlacklist(mUid);
+            assertNetworkingBlockedStatusForUid(mUid, METERED,
+                    true /* expectedResult */); // Match NTWK_BLOCKED_DENYLIST
+
+            // Although mUid is in the restrict background blacklist, but mUid won't be blocked if
+            // the network is non-metered.
+            assertFalse(
+                    isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
+            removeRestrictBackgroundBlacklist(mUid);
+
+            // Add mUid into the restrict background whitelist.
+            addRestrictBackgroundWhitelist(mUid);
+            assertNetworkingBlockedStatusForUid(mUid, METERED,
+                    false /* expectedResult */); // Match NTWK_ALLOWED_ALLOWLIST
+            assertFalse(
+                    isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
+            removeRestrictBackgroundWhitelist(mUid);
+
+            // Make TEST_APP2_PKG go to foreground and mUid will be allowed temporarily.
+            launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
+            assertForegroundState();
+            assertNetworkingBlockedStatusForUid(mUid, METERED,
+                    false /* expectedResult */); // Match NTWK_ALLOWED_TMP_ALLOWLIST
+
+            // Back to background.
+            finishActivity();
+            assertNetworkingBlockedStatusForUid(mUid, METERED,
+                    true /* expectedResult */); // Match NTWK_BLOCKED_BG_RESTRICT
+        } finally {
+            setRestrictBackground(false);
+            assertNetworkingBlockedStatusForUid(mUid, METERED,
+                    false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
+        }
+    }
+
+    @Test
+    public void testIsUidNetworkingBlocked_withRestrictedNetworkingMode() throws Exception {
+        // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
+        // test the cases of restricted networking mode enabled.
+        try {
+            // All apps should be blocked if restricted networking mode is enabled except for those
+            // apps who have CONNECTIVITY_USE_RESTRICTED_NETWORKS permission.
+            // This test won't test if an app who has CONNECTIVITY_USE_RESTRICTED_NETWORKS will not
+            // be blocked because CONNECTIVITY_USE_RESTRICTED_NETWORKS is a signature/privileged
+            // permission that CTS cannot acquire. Also it's not good for this test to use those
+            // privileged apps which have CONNECTIVITY_USE_RESTRICTED_NETWORKS to test because there
+            // is no guarantee that those apps won't remove this permission someday, and if it
+            // happens, then this test will fail.
+            setRestrictedNetworkingMode(true);
+            assertNetworkingBlockedStatusForUid(mUid, METERED,
+                    true /* expectedResult */); // Match NTWK_BLOCKED_RESTRICTED_MODE
+            assertTrue(isUidNetworkingBlocked(mUid,
+                    NON_METERED)); // Match NTWK_BLOCKED_RESTRICTED_MODE
+        } finally {
+            setRestrictedNetworkingMode(false);
+            assertNetworkingBlockedStatusForUid(mUid, METERED,
+                    false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
+        }
+    }
+
+    @Test
+    public void testIsUidNetworkingBlocked_withPowerSaverMode() throws Exception {
+        // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
+        // test the cases of power saver mode enabled, uid in the power saver mode whitelist and
+        // uid in the power saver mode whitelist with non-metered network.
+        try {
+            // mUid should be blocked if power saver mode is enabled.
+            setBatterySaverMode(true);
+            assertNetworkingBlockedStatusForUid(mUid, METERED,
+                    true /* expectedResult */); // Match NTWK_BLOCKED_POWER
+            assertTrue(isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_BLOCKED_POWER
+
+            // Add TEST_APP2_PKG into power saver mode whitelist, its uid rule is RULE_ALLOW_ALL and
+            // it shouldn't be blocked.
+            addPowerSaveModeWhitelist(TEST_APP2_PKG);
+            assertNetworkingBlockedStatusForUid(mUid, METERED,
+                    false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
+            assertFalse(
+                    isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
+            removePowerSaveModeWhitelist(TEST_APP2_PKG);
+        } finally {
+            setBatterySaverMode(false);
+            assertNetworkingBlockedStatusForUid(mUid, METERED,
+                    false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
+        }
+    }
+
+    @Test
+    public void testIsUidRestrictedOnMeteredNetworks() throws Exception {
+        try {
+            // isUidRestrictedOnMeteredNetworks() will only return true when restrict background is
+            // enabled and mUid is not in the restrict background whitelist and TEST_APP2_PKG is not
+            // in the foreground. For other cases, it will return false.
+            setRestrictBackground(true);
+            assertTrue(isUidRestrictedOnMeteredNetworks(mUid));
+
+            // Make TEST_APP2_PKG go to foreground and isUidRestrictedOnMeteredNetworks() will
+            // return false.
+            launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
+            assertForegroundState();
+            assertFalse(isUidRestrictedOnMeteredNetworks(mUid));
+            // Back to background.
+            finishActivity();
+
+            // Add mUid into restrict background whitelist and isUidRestrictedOnMeteredNetworks()
+            // will return false.
+            addRestrictBackgroundWhitelist(mUid);
+            assertFalse(isUidRestrictedOnMeteredNetworks(mUid));
+            removeRestrictBackgroundWhitelist(mUid);
+        } finally {
+            // Restrict background is disabled and isUidRestrictedOnMeteredNetworks() will return
+            // false.
+            setRestrictBackground(false);
+            assertFalse(isUidRestrictedOnMeteredNetworks(mUid));
+        }
+    }
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index 7da1a21..4f9ce7c 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -43,6 +43,7 @@
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.NetworkPolicyManager;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.ActionListener;
@@ -58,6 +59,7 @@
 
 import com.android.compatibility.common.util.AppStandbyUtils;
 import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.ThrowingRunnable;
 
@@ -81,6 +83,7 @@
     private static ConnectivityManager mCm;
     private static WifiManager mWm;
     private static CarrierConfigManager mCarrierConfigManager;
+    private static NetworkPolicyManager sNpm;
 
     private static Boolean mBatterySaverSupported;
     private static Boolean mDataSaverSupported;
@@ -408,6 +411,13 @@
         return mCarrierConfigManager;
     }
 
+    public static NetworkPolicyManager getNetworkPolicyManager() {
+        if (sNpm == null) {
+            sNpm = getContext().getSystemService(NetworkPolicyManager.class);
+        }
+        return sNpm;
+    }
+
     public static Context getContext() {
         return getInstrumentation().getContext();
     }
@@ -415,4 +425,33 @@
     public static Instrumentation getInstrumentation() {
         return InstrumentationRegistry.getInstrumentation();
     }
+
+    // When power saver mode or restrict background enabled or adding any white/black list into
+    // those modes, NetworkPolicy may need to take some time to update the rules of uids. So having
+    // this function and using PollingCheck to try to make sure the uid has updated and reduce the
+    // flaky rate.
+    public static void assertNetworkingBlockedStatusForUid(int uid, boolean metered,
+            boolean expectedResult) throws Exception {
+        PollingCheck.waitFor(() -> (expectedResult == isUidNetworkingBlocked(uid, metered)));
+    }
+
+    public static boolean isUidNetworkingBlocked(int uid, boolean meteredNetwork) {
+        final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            return getNetworkPolicyManager().isUidNetworkingBlocked(uid, meteredNetwork);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    public static boolean isUidRestrictedOnMeteredNetworks(int uid) {
+        final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            return getNetworkPolicyManager().isUidRestrictedOnMeteredNetworks(uid);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
 }
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java
index 29d3c6e..5f0f6d6 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java
@@ -28,28 +28,17 @@
 
     @After
     public void tearDown() throws Exception {
-        setRestrictedMode(false);
+        setRestrictedNetworkingMode(false);
         super.tearDown();
     }
 
-    private void setRestrictedMode(boolean enabled) throws Exception {
-        executeSilentShellCommand(
-                "settings put global restricted_networking_mode " + (enabled ? 1 : 0));
-        assertRestrictedModeState(enabled);
-    }
-
-    private void assertRestrictedModeState(boolean enabled) throws Exception {
-        assertDelayedShellCommand("cmd netpolicy get restricted-mode",
-                "Restricted mode status: " + (enabled ? "enabled" : "disabled"));
-    }
-
     @Test
     public void testNetworkAccess() throws Exception {
-        setRestrictedMode(false);
+        setRestrictedNetworkingMode(false);
 
         // go to foreground state and enable restricted mode
         launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
-        setRestrictedMode(true);
+        setRestrictedNetworkingMode(true);
         assertForegroundNetworkAccess(false);
 
         // go to background state
@@ -57,7 +46,7 @@
         assertBackgroundNetworkAccess(false);
 
         // disable restricted mode and assert network access in foreground and background states
-        setRestrictedMode(false);
+        setRestrictedNetworkingMode(false);
         launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
         assertForegroundNetworkAccess(true);
 
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index 8485263..62aa493 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -760,7 +760,7 @@
         assertEquals(vpnNetwork, mCM.getActiveNetwork());
         assertNotEqual(defaultNetwork, vpnNetwork);
         maybeExpectVpnTransportInfo(vpnNetwork);
-        assertTrue(mCM.getNetworkInfo(vpnNetwork).getType() == TYPE_VPN);
+        assertEquals(TYPE_VPN, mCM.getNetworkInfo(vpnNetwork).getType());
 
         if (SdkLevel.isAtLeastS()) {
             // Check that system default network callback has not seen any network changes, even
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
new file mode 100644
index 0000000..fdb8876
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
@@ -0,0 +1,66 @@
+/*
+ * 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.cts.net;
+
+public class HostsideNetworkPolicyManagerTests extends HostsideNetworkTestCase {
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        uninstallPackage(TEST_APP2_PKG, false);
+        installPackage(TEST_APP2_APK);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        uninstallPackage(TEST_APP2_PKG, true);
+    }
+
+    public void testIsUidNetworkingBlocked_withUidNotBlocked() throws Exception {
+        runDeviceTests(TEST_PKG,
+                TEST_PKG + ".NetworkPolicyManagerTest",
+                "testIsUidNetworkingBlocked_withUidNotBlocked");
+    }
+
+    public void testIsUidNetworkingBlocked_withSystemUid() throws Exception {
+        runDeviceTests(TEST_PKG,
+                TEST_PKG + ".NetworkPolicyManagerTest", "testIsUidNetworkingBlocked_withSystemUid");
+    }
+
+    public void testIsUidNetworkingBlocked_withDataSaverMode() throws Exception {
+        runDeviceTests(TEST_PKG,
+                TEST_PKG + ".NetworkPolicyManagerTest",
+                "testIsUidNetworkingBlocked_withDataSaverMode");
+    }
+
+    public void testIsUidNetworkingBlocked_withRestrictedNetworkingMode() throws Exception {
+        runDeviceTests(TEST_PKG,
+                TEST_PKG + ".NetworkPolicyManagerTest",
+                "testIsUidNetworkingBlocked_withRestrictedNetworkingMode");
+    }
+
+    public void testIsUidNetworkingBlocked_withPowerSaverMode() throws Exception {
+        runDeviceTests(TEST_PKG,
+                TEST_PKG + ".NetworkPolicyManagerTest",
+                "testIsUidNetworkingBlocked_withPowerSaverMode");
+    }
+
+    public void testIsUidRestrictedOnMeteredNetworks() throws Exception {
+        runDeviceTests(TEST_PKG,
+                TEST_PKG + ".NetworkPolicyManagerTest", "testIsUidRestrictedOnMeteredNetworks");
+    }
+}
diff --git a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
index a889c41..9f079c4 100644
--- a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
+++ b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
@@ -20,6 +20,7 @@
 import android.Manifest.permission.NETWORK_SETTINGS
 import android.Manifest.permission.READ_DEVICE_CONFIG
 import android.content.pm.PackageManager.FEATURE_TELEPHONY
+import android.content.pm.PackageManager.FEATURE_WATCH
 import android.content.pm.PackageManager.FEATURE_WIFI
 import android.net.ConnectivityManager
 import android.net.ConnectivityManager.NetworkCallback
@@ -57,6 +58,7 @@
 import junit.framework.AssertionFailedError
 import org.junit.After
 import org.junit.Assume.assumeTrue
+import org.junit.Assume.assumeFalse
 import org.junit.Before
 import org.junit.runner.RunWith
 import java.util.concurrent.CompletableFuture
@@ -128,6 +130,7 @@
     fun testCaptivePortalIsNotDefaultNetwork() {
         assumeTrue(pm.hasSystemFeature(FEATURE_TELEPHONY))
         assumeTrue(pm.hasSystemFeature(FEATURE_WIFI))
+        assumeFalse(pm.hasSystemFeature(FEATURE_WATCH))
         utils.ensureWifiConnected()
         val cellNetwork = utils.connectToCell()
 
@@ -148,8 +151,8 @@
         server.addResponse(Request(TEST_PORTAL_URL_PATH), Status.OK,
                 content = "Test captive portal content")
         server.addResponse(Request(TEST_HTTPS_URL_PATH), Status.INTERNAL_ERROR)
-        server.addResponse(Request(TEST_HTTP_URL_PATH), Status.REDIRECT,
-                locationHeader = makeUrl(TEST_PORTAL_URL_PATH))
+        val headers = mapOf("Location" to makeUrl(TEST_PORTAL_URL_PATH))
+        server.addResponse(Request(TEST_HTTP_URL_PATH), Status.REDIRECT, headers)
         setHttpsUrlDeviceConfig(makeUrl(TEST_HTTPS_URL_PATH))
         setHttpUrlDeviceConfig(makeUrl(TEST_HTTP_URL_PATH))
         // URL expiration needs to be in the next 10 minutes
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index d649518..eb9c305 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
 import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.READ_DEVICE_CONFIG;
 import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
 import static android.content.pm.PackageManager.FEATURE_ETHERNET;
 import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
@@ -48,6 +49,8 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.TetheringManager.TETHERING_WIFI;
@@ -60,6 +63,8 @@
 import static android.net.cts.util.CtsTetheringUtils.StartTetheringCallback;
 import static android.net.cts.util.CtsTetheringUtils.TestTetheringEventCallback;
 import static android.net.cts.util.CtsTetheringUtils.isWifiTetheringSupported;
+import static android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL;
+import static android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL;
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
 import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
 import static android.system.OsConstants.AF_INET;
@@ -81,7 +86,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
@@ -122,6 +126,7 @@
 import android.net.TestNetworkInterface;
 import android.net.TestNetworkManager;
 import android.net.TetheringManager;
+import android.net.Uri;
 import android.net.cts.util.CtsNetUtils;
 import android.net.util.KeepaliveUtils;
 import android.net.wifi.WifiManager;
@@ -136,6 +141,7 @@
 import android.os.UserHandle;
 import android.os.VintfRuntimeInfo;
 import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -160,6 +166,7 @@
 import com.android.testutils.DevSdkIgnoreRuleKt;
 import com.android.testutils.RecorderCallback.CallbackEntry;
 import com.android.testutils.SkipPresubmit;
+import com.android.testutils.TestHttpServer;
 import com.android.testutils.TestNetworkTracker;
 import com.android.testutils.TestableNetworkCallback;
 
@@ -192,6 +199,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
@@ -202,6 +210,10 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import fi.iki.elonen.NanoHTTPD.Method;
+import fi.iki.elonen.NanoHTTPD.Response.IStatus;
+import fi.iki.elonen.NanoHTTPD.Response.Status;
+
 @RunWith(AndroidJUnit4.class)
 public class ConnectivityManagerTest {
     @Rule
@@ -245,6 +257,12 @@
     private static final int AIRPLANE_MODE_OFF = 0;
     private static final int AIRPLANE_MODE_ON = 1;
 
+    private static final String TEST_HTTPS_URL_PATH = "/https_path";
+    private static final String TEST_HTTP_URL_PATH = "/http_path";
+    private static final String LOCALHOST_HOSTNAME = "localhost";
+    // Re-connecting to the AP, obtaining an IP address, revalidating can take a long time
+    private static final long WIFI_CONNECT_TIMEOUT_MS = 60_000L;
+
     private Context mContext;
     private Instrumentation mInstrumentation;
     private ConnectivityManager mCm;
@@ -259,6 +277,8 @@
     // Used for cleanup purposes.
     private final List<Range<Integer>> mVpnRequiredUidRanges = new ArrayList<>();
 
+    private final TestHttpServer mHttpServer = new TestHttpServer(LOCALHOST_HOSTNAME);
+
     @Before
     public void setUp() throws Exception {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -667,12 +687,14 @@
     private NetworkRequest makeWifiNetworkRequest() {
         return new NetworkRequest.Builder()
                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .addCapability(NET_CAPABILITY_INTERNET)
                 .build();
     }
 
     private NetworkRequest makeCellNetworkRequest() {
         return new NetworkRequest.Builder()
                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_INTERNET)
                 .build();
     }
 
@@ -2134,6 +2156,24 @@
                 null /* listener */));
     }
 
+    @Test
+    public void testSystemReady() {
+        assumeTrue(TestUtils.shouldTestSApis());
+        assertThrows(SecurityException.class, () -> mCm.systemReady());
+    }
+
+    @Test
+    public void testGetIpSecNetIdRange() {
+        assumeTrue(TestUtils.shouldTestSApis());
+        // The lower refers to ConnectivityManager.TUN_INTF_NETID_START.
+        final long lower = 64512;
+        // The upper refers to ConnectivityManager.TUN_INTF_NETID_START
+        // + ConnectivityManager.TUN_INTF_NETID_RANGE - 1
+        final long upper = 65535;
+        assertEquals(lower, (long) ConnectivityManager.getIpSecNetIdRange().getLower());
+        assertEquals(upper, (long) ConnectivityManager.getIpSecNetIdRange().getUpper());
+    }
+
     private void verifySettings(int expectedAirplaneMode, int expectedPrivateDnsMode,
             int expectedAvoidBadWifi) throws Exception {
         assertEquals(expectedAirplaneMode, Settings.Global.getInt(
@@ -2327,4 +2367,268 @@
         }
         oemPrefListener.expectOnComplete();
     }
+
+    @Test
+    public void testSetAcceptPartialConnectivity_NoPermission_GetException() {
+        assumeTrue(TestUtils.shouldTestSApis());
+        assertThrows(SecurityException.class, () -> mCm.setAcceptPartialConnectivity(
+                mCm.getActiveNetwork(), false /* accept */ , false /* always */));
+    }
+
+    @AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
+    @Test
+    public void testAcceptPartialConnectivity_validatedNetwork() throws Exception {
+        assumeTrue(TestUtils.shouldTestSApis());
+        assumeTrue("testAcceptPartialConnectivity_validatedNetwork cannot execute"
+                        + " unless device supports WiFi",
+                mPackageManager.hasSystemFeature(FEATURE_WIFI));
+
+        try {
+            // Wait for partial connectivity to be detected on the network
+            final Network network = preparePartialConnectivity();
+
+            runAsShell(NETWORK_SETTINGS, () -> {
+                // The always bit is verified in NetworkAgentTest
+                mCm.setAcceptPartialConnectivity(network, true /* accept */, false /* always */);
+            });
+
+            // Accept partial connectivity network should result in a validated network
+            expectNetworkHasCapability(network, NET_CAPABILITY_VALIDATED, WIFI_CONNECT_TIMEOUT_MS);
+        } finally {
+            resetValidationConfig();
+            // Reconnect wifi to reset the wifi status
+            mCtsNetUtils.ensureWifiDisconnected(null /* wifiNetworkToCheck */);
+            mCtsNetUtils.ensureWifiConnected();
+        }
+    }
+
+    @AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
+    @Test
+    public void testRejectPartialConnectivity_TearDownNetwork() throws Exception {
+        assumeTrue(TestUtils.shouldTestSApis());
+        assumeTrue("testAcceptPartialConnectivity_validatedNetwork cannot execute"
+                        + " unless device supports WiFi",
+                mPackageManager.hasSystemFeature(FEATURE_WIFI));
+
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        try {
+            // Wait for partial connectivity to be detected on the network
+            final Network network = preparePartialConnectivity();
+
+            mCm.requestNetwork(makeWifiNetworkRequest(), cb);
+            runAsShell(NETWORK_SETTINGS, () -> {
+                // The always bit is verified in NetworkAgentTest
+                mCm.setAcceptPartialConnectivity(network, false /* accept */, false /* always */);
+            });
+            // Reject partial connectivity network should cause the network being torn down
+            assertEquals(network, cb.waitForLost());
+        } finally {
+            mCm.unregisterNetworkCallback(cb);
+            resetValidationConfig();
+            // Wifi will not automatically reconnect to the network. ensureWifiDisconnected cannot
+            // apply here. Thus, turn off wifi first and restart to restore.
+            runShellCommand("svc wifi disable");
+            mCtsNetUtils.ensureWifiConnected();
+        }
+    }
+
+    @Test
+    public void testSetAcceptUnvalidated_NoPermission_GetException() {
+        assumeTrue(TestUtils.shouldTestSApis());
+        assertThrows(SecurityException.class, () -> mCm.setAcceptUnvalidated(
+                mCm.getActiveNetwork(), false /* accept */ , false /* always */));
+    }
+
+    @AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
+    @Test
+    public void testRejectUnvalidated_TearDownNetwork() throws Exception {
+        assumeTrue(TestUtils.shouldTestSApis());
+        final boolean canRunTest = mPackageManager.hasSystemFeature(FEATURE_WIFI)
+                && mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
+        assumeTrue("testAcceptPartialConnectivity_validatedNetwork cannot execute"
+                        + " unless device supports WiFi and telephony", canRunTest);
+
+        final TestableNetworkCallback wifiCb = new TestableNetworkCallback();
+        try {
+            // Ensure at least one default network candidate connected.
+            mCtsNetUtils.connectToCell();
+
+            final Network wifiNetwork = prepareUnvalidatedNetwork();
+            // Default network should not be wifi ,but checking that wifi is not the default doesn't
+            // guarantee that it won't become the default in the future.
+            assertNotEquals(wifiNetwork, mCm.getActiveNetwork());
+
+            mCm.registerNetworkCallback(makeWifiNetworkRequest(), wifiCb);
+            runAsShell(NETWORK_SETTINGS, () -> {
+                mCm.setAcceptUnvalidated(wifiNetwork, false /* accept */, false /* always */);
+            });
+            waitForLost(wifiCb);
+        } finally {
+            mCm.unregisterNetworkCallback(wifiCb);
+            resetValidationConfig();
+            /// Wifi will not automatically reconnect to the network. ensureWifiDisconnected cannot
+            // apply here. Thus, turn off wifi first and restart to restore.
+            runShellCommand("svc wifi disable");
+            mCtsNetUtils.ensureWifiConnected();
+        }
+    }
+
+    private Network expectNetworkHasCapability(Network network, int expectedNetCap, long timeout)
+            throws Exception {
+        final CompletableFuture<Network> future = new CompletableFuture();
+        final NetworkCallback cb = new NetworkCallback() {
+            @Override
+            public void onCapabilitiesChanged(Network n, NetworkCapabilities nc) {
+                if (n.equals(network) && nc.hasCapability(expectedNetCap)) {
+                    future.complete(network);
+                }
+            }
+        };
+
+        try {
+            mCm.registerNetworkCallback(new NetworkRequest.Builder().build(), cb);
+            return future.get(timeout, TimeUnit.MILLISECONDS);
+        } finally {
+            mCm.unregisterNetworkCallback(cb);
+        }
+    }
+
+    private void resetValidationConfig() {
+        NetworkValidationTestUtil.clearValidationTestUrlsDeviceConfig();
+        mHttpServer.stop();
+    }
+
+    private void prepareHttpServer() throws Exception {
+        runAsShell(READ_DEVICE_CONFIG, () -> {
+            // Verify that the test URLs are not normally set on the device, but do not fail if the
+            // test URLs are set to what this test uses (URLs on localhost), in case the test was
+            // interrupted manually and rerun.
+            assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL);
+            assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTP_URL);
+        });
+
+        NetworkValidationTestUtil.clearValidationTestUrlsDeviceConfig();
+
+        mHttpServer.start();
+    }
+
+    private Network preparePartialConnectivity() throws Exception {
+        prepareHttpServer();
+        // Configure response code for partial connectivity
+        configTestServer(Status.INTERNAL_ERROR  /* httpsStatusCode */,
+                Status.NO_CONTENT  /* httpStatusCode */);
+        // Disconnect wifi first then start wifi network with configuration.
+        mCtsNetUtils.ensureWifiDisconnected(null /* wifiNetworkToCheck */);
+        final Network network = mCtsNetUtils.ensureWifiConnected();
+
+        return expectNetworkHasCapability(network, NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+                WIFI_CONNECT_TIMEOUT_MS);
+    }
+
+    private Network prepareUnvalidatedNetwork() throws Exception {
+        prepareHttpServer();
+        // Configure response code for unvalidated network
+        configTestServer(Status.INTERNAL_ERROR /* httpsStatusCode */,
+                Status.INTERNAL_ERROR /* httpStatusCode */);
+
+        // Disconnect wifi first then start wifi network with configuration.
+        mCtsNetUtils.ensureWifiDisconnected(null /* wifiNetworkToCheck */);
+        final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
+        return expectNetworkHasCapability(wifiNetwork, NET_CAPABILITY_INTERNET,
+                WIFI_CONNECT_TIMEOUT_MS);
+    }
+
+    private String makeUrl(String path) {
+        return "http://localhost:" + mHttpServer.getListeningPort() + path;
+    }
+
+    private void assertEmptyOrLocalhostUrl(String urlKey) {
+        final String url = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, urlKey);
+        assertTrue(urlKey + " must not be set in production scenarios, current value= " + url,
+                TextUtils.isEmpty(url) || LOCALHOST_HOSTNAME.equals(Uri.parse(url).getHost()));
+    }
+
+    private void configTestServer(IStatus httpsStatusCode, IStatus httpStatusCode) {
+        mHttpServer.addResponse(new TestHttpServer.Request(
+                TEST_HTTPS_URL_PATH, Method.GET, "" /* queryParameters */),
+                httpsStatusCode, null /* locationHeader */, "" /* content */);
+        mHttpServer.addResponse(new TestHttpServer.Request(
+                TEST_HTTP_URL_PATH, Method.GET, "" /* queryParameters */),
+                httpStatusCode, null /* locationHeader */, "" /* content */);
+        NetworkValidationTestUtil.setHttpsUrlDeviceConfig(makeUrl(TEST_HTTPS_URL_PATH));
+        NetworkValidationTestUtil.setHttpUrlDeviceConfig(makeUrl(TEST_HTTP_URL_PATH));
+        NetworkValidationTestUtil.setUrlExpirationDeviceConfig(
+                System.currentTimeMillis() + WIFI_CONNECT_TIMEOUT_MS);
+    }
+
+    @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+    @Test
+    public void testMobileDataPreferredUids() throws Exception {
+        assumeTrue(TestUtils.shouldTestSApis());
+        final boolean canRunTest = mPackageManager.hasSystemFeature(FEATURE_WIFI)
+                && mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
+        assumeTrue("testMobileDataPreferredUidsWithCallback cannot execute"
+                + " unless device supports both WiFi and telephony", canRunTest);
+
+        final int uid = mPackageManager.getPackageUid(mContext.getPackageName(), 0 /* flag */);
+        final Set<Integer> mobileDataPreferredUids =
+                ConnectivitySettingsManager.getMobileDataPreferredUids(mContext);
+        // CtsNetTestCases uid should not list in MOBILE_DATA_PREFERRED_UIDS setting because it just
+        // installs to device. In case the uid is existed in setting mistakenly, try to remove the
+        // uid and set correct uids to setting.
+        mobileDataPreferredUids.remove(uid);
+        ConnectivitySettingsManager.setMobileDataPreferredUids(mContext, mobileDataPreferredUids);
+
+        // For testing mobile data preferred uids feature, it needs both wifi and cell network.
+        final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
+        final Network cellNetwork = mCtsNetUtils.connectToCell();
+        final TestableNetworkCallback defaultTrackingCb = new TestableNetworkCallback();
+        final TestableNetworkCallback systemDefaultCb = new TestableNetworkCallback();
+        final Handler h = new Handler(Looper.getMainLooper());
+        runWithShellPermissionIdentity(() -> mCm.registerSystemDefaultNetworkCallback(
+                systemDefaultCb, h), NETWORK_SETTINGS);
+        mCm.registerDefaultNetworkCallback(defaultTrackingCb);
+
+        try {
+            // CtsNetTestCases uid is not listed in MOBILE_DATA_PREFERRED_UIDS setting, so the
+            // per-app default network should be same as system default network.
+            waitForAvailable(systemDefaultCb, wifiNetwork);
+            defaultTrackingCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
+                    entry -> wifiNetwork.equals(entry.getNetwork()));
+            // Active network for CtsNetTestCases uid should be wifi now.
+            assertEquals(wifiNetwork, mCm.getActiveNetwork());
+
+            // Add CtsNetTestCases uid to MOBILE_DATA_PREFERRED_UIDS setting, then available per-app
+            // default network callback should be received with cell network.
+            final Set<Integer> newMobileDataPreferredUids = new ArraySet<>(mobileDataPreferredUids);
+            newMobileDataPreferredUids.add(uid);
+            ConnectivitySettingsManager.setMobileDataPreferredUids(
+                    mContext, newMobileDataPreferredUids);
+            defaultTrackingCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
+                    entry -> cellNetwork.equals(entry.getNetwork()));
+            // System default network doesn't change.
+            systemDefaultCb.assertNoCallback();
+            // Active network for CtsNetTestCases uid should change to cell, too.
+            assertEquals(cellNetwork, mCm.getActiveNetwork());
+
+            // Remove CtsNetTestCases uid from MOBILE_DATA_PREFERRED_UIDS setting, then available
+            // per-app default network callback should be received again with system default network
+            newMobileDataPreferredUids.remove(uid);
+            ConnectivitySettingsManager.setMobileDataPreferredUids(
+                    mContext, newMobileDataPreferredUids);
+            defaultTrackingCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
+                    entry -> wifiNetwork.equals(entry.getNetwork()));
+            // System default network still doesn't change.
+            systemDefaultCb.assertNoCallback();
+            // Active network for CtsNetTestCases uid should change back to wifi.
+            assertEquals(wifiNetwork, mCm.getActiveNetwork());
+        } finally {
+            mCm.unregisterNetworkCallback(systemDefaultCb);
+            mCm.unregisterNetworkCallback(defaultTrackingCb);
+
+            // Restore setting.
+            ConnectivitySettingsManager.setMobileDataPreferredUids(
+                    mContext, mobileDataPreferredUids);
+        }
+    }
 }
diff --git a/tests/cts/net/src/android/net/cts/NetworkValidationTestUtil.kt b/tests/cts/net/src/android/net/cts/NetworkValidationTestUtil.kt
index f6fc75b..dde14ac 100644
--- a/tests/cts/net/src/android/net/cts/NetworkValidationTestUtil.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkValidationTestUtil.kt
@@ -29,7 +29,7 @@
     /**
      * Clear the test network validation URLs.
      */
-    fun clearValidationTestUrlsDeviceConfig() {
+    @JvmStatic fun clearValidationTestUrlsDeviceConfig() {
         setHttpsUrlDeviceConfig(null)
         setHttpUrlDeviceConfig(null)
         setUrlExpirationDeviceConfig(null)
@@ -40,7 +40,7 @@
      *
      * @see NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL
      */
-    fun setHttpsUrlDeviceConfig(url: String?) =
+    @JvmStatic fun setHttpsUrlDeviceConfig(url: String?) =
             setConfig(NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL, url)
 
     /**
@@ -48,7 +48,7 @@
      *
      * @see NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL
      */
-    fun setHttpUrlDeviceConfig(url: String?) =
+    @JvmStatic fun setHttpUrlDeviceConfig(url: String?) =
             setConfig(NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL, url)
 
     /**
@@ -56,7 +56,7 @@
      *
      * @see NetworkStackUtils.TEST_URL_EXPIRATION_TIME
      */
-    fun setUrlExpirationDeviceConfig(timestamp: Long?) =
+    @JvmStatic fun setUrlExpirationDeviceConfig(timestamp: Long?) =
             setConfig(NetworkStackUtils.TEST_URL_EXPIRATION_TIME, timestamp?.toString())
 
     private fun setConfig(configKey: String, value: String?) {
diff --git a/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java b/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java
new file mode 100644
index 0000000..7d5e9ff
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.cts;
+
+import static android.Manifest.permission.NETWORK_SETTINGS;
+
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.PacProxyManager;
+import android.net.Proxy;
+import android.net.ProxyInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.util.Log;
+import android.util.Range;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.TestHttpServer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.ServerSocket;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+import fi.iki.elonen.NanoHTTPD.Response.Status;
+
+@IgnoreUpTo(Build.VERSION_CODES.R)
+@RunWith(DevSdkIgnoreRunner.class)
+public final class PacProxyManagerTest {
+    private static final String TAG = PacProxyManagerTest.class.getSimpleName();
+    private static final int PROXY_CHANGED_BROADCAST_TIMEOUT_MS = 5000;
+    private static final int CALLBACK_TIMEOUT_MS = 3 * 1000;
+
+    private Context mContext;
+    private TestHttpServer mServer;
+    private ConnectivityManager mCm;
+    private PacProxyManager mPacProxyManager;
+    private ServerSocket mServerSocket;
+    private Instrumentation mInstrumentation;
+
+    private static final String PAC_FILE = "function FindProxyForURL(url, host)"
+            + "{"
+            + "  return \"PROXY 192.168.0.1:9091\";"
+            + "}";
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mContext = mInstrumentation.getContext();
+
+        mCm = mContext.getSystemService(ConnectivityManager.class);
+        mPacProxyManager = (PacProxyManager) mContext.getSystemService(PacProxyManager.class);
+        mServer = new TestHttpServer();
+        mServer.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mServer != null) {
+            mServer.stop();
+            mServer = null;
+        }
+    }
+
+    private class TestPacProxyInstalledListener implements
+            PacProxyManager.PacProxyInstalledListener {
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+
+        public void onPacProxyInstalled(Network network, ProxyInfo proxy) {
+            Log.e(TAG, "onPacProxyInstalled is called.");
+            mLatch.countDown();
+        }
+
+        public boolean waitForCallback() throws Exception {
+            final boolean result = mLatch.await(CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            return result;
+        }
+    }
+
+    private class ProxyBroadcastReceiver extends BroadcastReceiver {
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private final ProxyInfo mProxy;
+
+        ProxyBroadcastReceiver(ProxyInfo proxy) {
+            mProxy = proxy;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final ProxyInfo proxy = (ProxyInfo) intent.getExtra(Proxy.EXTRA_PROXY_INFO,
+                    ProxyInfo.buildPacProxy(Uri.EMPTY));
+            // ProxyTracker sends sticky broadcast which will receive the last broadcast while
+            // register the intent receiver. That is, if system never receives the intent then
+            // it won't receive an intent when register the receiver. How many intents will be
+            // received in the test is unpredictable so here counts down the latch when the PAC
+            // file in the intent is the same as the one at registration.
+            if (mProxy.getPacFileUrl().equals(proxy.getPacFileUrl())) {
+                // Host/Port represent a local proxy server that redirects to the PAC-configured
+                // server. Host should be "localhost" and the port should be a value which is
+                // between 0 and 65535.
+                assertEquals(proxy.getHost(), "localhost");
+                assertInRange(proxy.getPort(), 0 /* lower */, 65535 /* upper */);
+                mLatch.countDown();
+            }
+        }
+
+        public boolean waitForProxyChanged() throws Exception {
+            final boolean result = mLatch.await(PROXY_CHANGED_BROADCAST_TIMEOUT_MS,
+                    TimeUnit.MILLISECONDS);
+            return result;
+        }
+    }
+
+    @Test
+    public void testSetCurrentProxyScriptUrl() throws Exception {
+        // Register a PacProxyInstalledListener
+        final TestPacProxyInstalledListener listener = new TestPacProxyInstalledListener();
+        final Executor executor = (Runnable r) -> r.run();
+
+        runAsShell(NETWORK_SETTINGS, () -> {
+            mPacProxyManager.addPacProxyInstalledListener(executor, listener);
+        });
+
+        final Map<String, String> headers = new HashMap<String, String>();
+        headers.put("Content-Type", "application/x-ns-proxy-autoconfig");
+        final Uri pacProxyUrl = Uri.parse("http://localhost:"
+                + mServer.getListeningPort() + "/proxy.pac");
+        mServer.addResponse(pacProxyUrl, Status.OK, headers, PAC_FILE);
+
+        final ProxyInfo proxy = ProxyInfo.buildPacProxy(pacProxyUrl);
+        final ProxyBroadcastReceiver receiver = new ProxyBroadcastReceiver(proxy);
+        mContext.registerReceiver(receiver, new IntentFilter(Proxy.PROXY_CHANGE_ACTION));
+
+        // Call setCurrentProxyScriptUrl with the URL of the pac file.
+        runAsShell(NETWORK_SETTINGS, () -> {
+            mPacProxyManager.setCurrentProxyScriptUrl(proxy);
+        });
+
+        // Make sure the listener was called and testing the intent is received.
+        try {
+            assertTrue("Didn't receive PROXY_CHANGE_ACTION broadcast.",
+                    receiver.waitForProxyChanged());
+            assertTrue("Did not receive onPacProxyInstalled callback.",
+                    listener.waitForCallback());
+        } finally {
+            runAsShell(NETWORK_SETTINGS, () -> {
+                mPacProxyManager.removePacProxyInstalledListener(listener);
+            });
+            mContext.unregisterReceiver(receiver);
+        }
+    }
+
+    private void assertInRange(int value, int lower, int upper) {
+        final Range range = new Range(lower, upper);
+        assertTrue(value + "is not within range [" + lower + ", " + upper + "]",
+                range.contains(value));
+    }
+}
diff --git a/tests/cts/net/src/android/net/cts/ProxyTest.java b/tests/cts/net/src/android/net/cts/ProxyTest.java
deleted file mode 100644
index 467d12f..0000000
--- a/tests/cts/net/src/android/net/cts/ProxyTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2009 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.cts;
-
-
-import android.net.Proxy;
-import android.test.AndroidTestCase;
-
-public class ProxyTest extends AndroidTestCase {
-
-    public void testConstructor() {
-        new Proxy();
-    }
-
-    public void testAccessProperties() {
-        final int minValidPort = 0;
-        final int maxValidPort = 65535;
-        int defaultPort = Proxy.getDefaultPort();
-        if(null == Proxy.getDefaultHost()) {
-            assertEquals(-1, defaultPort);
-        } else {
-            assertTrue(defaultPort >= minValidPort && defaultPort <= maxValidPort);
-        }
-    }
-}
diff --git a/tests/cts/net/src/android/net/cts/ProxyTest.kt b/tests/cts/net/src/android/net/cts/ProxyTest.kt
new file mode 100644
index 0000000..a661b26
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/ProxyTest.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.cts
+
+import android.net.ConnectivityManager
+import android.net.Proxy
+import android.net.ProxyInfo
+import android.net.Uri
+import android.os.Build
+import android.text.TextUtils
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ProxyTest {
+    @get:Rule
+    val ignoreRule = DevSdkIgnoreRule()
+
+    @Test
+    fun testConstructor() {
+        Proxy()
+    }
+
+    @Test
+    fun testAccessProperties() {
+        val minValidPort = 0
+        val maxValidPort = 65535
+        val defaultPort = Proxy.getDefaultPort()
+        if (null == Proxy.getDefaultHost()) {
+            assertEquals(-1, defaultPort.toLong())
+        } else {
+            Assert.assertTrue(defaultPort in minValidPort..maxValidPort)
+        }
+    }
+
+    private fun verifyProxySystemProperties(info: ProxyInfo) {
+        assertEquals(info.host, System.getProperty("http.proxyHost"))
+        assertEquals(info.host, System.getProperty("https.proxyHost"))
+
+        assertEquals(info.port.toString(), System.getProperty("http.proxyPort"))
+        assertEquals(info.port.toString(), System.getProperty("https.proxyPort"))
+
+        val strExcludes = if (info.exclusionList.isEmpty()) null
+                else TextUtils.join("|", info.exclusionList)
+        assertEquals(strExcludes, System.getProperty("https.nonProxyHosts"))
+        assertEquals(strExcludes, System.getProperty("http.nonProxyHosts"))
+    }
+
+    private fun getDefaultProxy(): ProxyInfo? {
+        return InstrumentationRegistry.getInstrumentation().context
+                .getSystemService(ConnectivityManager::class.java)
+                .getDefaultProxy()
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R) // setHttpProxyConfiguration was added in S
+    fun testSetHttpProxyConfiguration_DirectProxy() {
+        val info = ProxyInfo.buildDirectProxy(
+                "testproxy.android.com",
+                12345 /* port */,
+                listOf("testexclude1.android.com", "testexclude2.android.com"))
+        val original = getDefaultProxy()
+        try {
+            Proxy.setHttpProxyConfiguration(info)
+            verifyProxySystemProperties(info)
+        } finally {
+            Proxy.setHttpProxyConfiguration(original)
+        }
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R) // setHttpProxyConfiguration was added in S
+    fun testSetHttpProxyConfiguration_PacProxy() {
+        val pacInfo = ProxyInfo.buildPacProxy(Uri.parse("http://testpac.android.com/pac.pac"))
+        val original = getDefaultProxy()
+        try {
+            Proxy.setHttpProxyConfiguration(pacInfo)
+            verifyProxySystemProperties(pacInfo)
+        } finally {
+            Proxy.setHttpProxyConfiguration(original)
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/cts/net/util/Android.bp b/tests/cts/net/util/Android.bp
index b5f1208..fffd30f 100644
--- a/tests/cts/net/util/Android.bp
+++ b/tests/cts/net/util/Android.bp
@@ -27,5 +27,6 @@
         "junit",
         "net-tests-utils",
         "modules-utils-build",
+        "net-utils-framework-common",
     ],
 }
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index b32218b..bce9880 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -56,12 +56,13 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
-import android.provider.Settings;
 import android.system.Os;
 import android.system.OsConstants;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.compatibility.common.util.SystemUtil;
+import com.android.net.module.util.ConnectivitySettingsUtils;
 
 import junit.framework.AssertionFailedError;
 
@@ -80,7 +81,6 @@
 
 public final class CtsNetUtils {
     private static final String TAG = CtsNetUtils.class.getSimpleName();
-    private static final int DURATION = 10000;
     private static final int SOCKET_TIMEOUT_MS = 2000;
     private static final int PRIVATE_DNS_PROBE_MS = 1_000;
 
@@ -104,7 +104,7 @@
     private final ContentResolver mCR;
     private final WifiManager mWifiManager;
     private TestNetworkCallback mCellNetworkCallback;
-    private String mOldPrivateDnsMode;
+    private int mOldPrivateDnsMode = 0;
     private String mOldPrivateDnsSpecifier;
 
     public CtsNetUtils(Context context) {
@@ -508,62 +508,69 @@
     }
 
     public void storePrivateDnsSetting() {
-        // Store private DNS setting
-        mOldPrivateDnsMode = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_MODE);
-        mOldPrivateDnsSpecifier = Settings.Global.getString(mCR,
-                Settings.Global.PRIVATE_DNS_SPECIFIER);
-        // It's possible that there is no private DNS default value in Settings.
-        // Give it a proper default mode which is opportunistic mode.
-        if (mOldPrivateDnsMode == null) {
-            mOldPrivateDnsSpecifier = "";
-            mOldPrivateDnsMode = PRIVATE_DNS_MODE_OPPORTUNISTIC;
-            Settings.Global.putString(mCR,
-                    Settings.Global.PRIVATE_DNS_SPECIFIER, mOldPrivateDnsSpecifier);
-            Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, mOldPrivateDnsMode);
-        }
+        mOldPrivateDnsMode = ConnectivitySettingsUtils.getPrivateDnsMode(mContext);
+        mOldPrivateDnsSpecifier = ConnectivitySettingsUtils.getPrivateDnsHostname(mContext);
     }
 
     public void restorePrivateDnsSetting() throws InterruptedException {
-        if (mOldPrivateDnsMode == null) {
+        if (mOldPrivateDnsMode == 0) {
             fail("restorePrivateDnsSetting without storing settings first");
         }
-        // restore private DNS setting
-        if (PRIVATE_DNS_MODE_STRICT.equals(mOldPrivateDnsMode)) {
-            setPrivateDnsStrictMode(mOldPrivateDnsSpecifier);
 
-            // In case of invalid setting, still restore it but fail the test
-            if (mOldPrivateDnsSpecifier == null) {
-                fail("Invalid private DNS setting: no hostname specified in strict mode");
-            }
-            awaitPrivateDnsSetting("restorePrivateDnsSetting timeout",
-                    mCm.getActiveNetwork(),
-                    mOldPrivateDnsSpecifier, true);
-        } else {
-            Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, mOldPrivateDnsMode);
+        if (mOldPrivateDnsMode != ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) {
+            ConnectivitySettingsUtils.setPrivateDnsMode(mContext, mOldPrivateDnsMode);
+            return;
         }
+        // restore private DNS setting
+        // In case of invalid setting, set to opportunistic to avoid a bad state and fail
+        if (TextUtils.isEmpty(mOldPrivateDnsSpecifier)) {
+            ConnectivitySettingsUtils.setPrivateDnsMode(mContext,
+                    ConnectivitySettingsUtils.PRIVATE_DNS_MODE_OPPORTUNISTIC);
+            fail("Invalid private DNS setting: no hostname specified in strict mode");
+        }
+        setPrivateDnsStrictMode(mOldPrivateDnsSpecifier);
+
+        // There might be a race before private DNS setting is applied and the next test is
+        // running. So waiting private DNS to be validated can reduce the flaky rate of test.
+        awaitPrivateDnsSetting("restorePrivateDnsSetting timeout",
+                mCm.getActiveNetwork(),
+                mOldPrivateDnsSpecifier, true /* requiresValidatedServer */);
     }
 
     public void setPrivateDnsStrictMode(String server) {
         // To reduce flake rate, set PRIVATE_DNS_SPECIFIER before PRIVATE_DNS_MODE. This ensures
         // that if the previous private DNS mode was not strict, the system only sees one
         // EVENT_PRIVATE_DNS_SETTINGS_CHANGED event instead of two.
-        Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_SPECIFIER, server);
-        final String mode = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_MODE);
+        ConnectivitySettingsUtils.setPrivateDnsHostname(mContext, server);
+        final int mode = ConnectivitySettingsUtils.getPrivateDnsMode(mContext);
         // If current private DNS mode is strict, we only need to set PRIVATE_DNS_SPECIFIER.
-        if (!PRIVATE_DNS_MODE_STRICT.equals(mode)) {
-            Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE,
-                    PRIVATE_DNS_MODE_STRICT);
+        if (mode != ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) {
+            ConnectivitySettingsUtils.setPrivateDnsMode(mContext,
+                    ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
         }
     }
 
+    /**
+     * Waiting for the new private DNS setting to be validated.
+     * This method is helpful when the new private DNS setting is configured and ensure the new
+     * setting is applied and workable. It can also reduce the flaky rate when the next test is
+     * running.
+     *
+     * @param msg A message that will be printed when the validation of private DNS is timeout.
+     * @param network A network which will apply the new private DNS setting.
+     * @param server The hostname of private DNS.
+     * @param requiresValidatedServer A boolean to decide if it's needed to wait private DNS to be
+     *                                 validated or not.
+     * @throws InterruptedException If the thread is interrupted.
+     */
     public void awaitPrivateDnsSetting(@NonNull String msg, @NonNull Network network,
-            @NonNull String server, boolean requiresValidatedServers) throws InterruptedException {
-        CountDownLatch latch = new CountDownLatch(1);
-        NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
+            @NonNull String server, boolean requiresValidatedServer) throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
         NetworkCallback callback = new NetworkCallback() {
             @Override
             public void onLinkPropertiesChanged(Network n, LinkProperties lp) {
-                if (requiresValidatedServers && lp.getValidatedPrivateDnsServers().isEmpty()) {
+                if (requiresValidatedServer && lp.getValidatedPrivateDnsServers().isEmpty()) {
                     return;
                 }
                 if (network.equals(n) && server.equals(lp.getPrivateDnsServerName())) {
@@ -583,7 +590,7 @@
         // private DNS probe. There is no way to know when the probe has completed: because the
         // network is likely already validated, there is no callback that we can listen to, so
         // just sleep.
-        if (requiresValidatedServers) {
+        if (requiresValidatedServer) {
             Thread.sleep(PRIVATE_DNS_PROBE_MS);
         }
     }