diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java index 03c3600414..4644e4f483 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_HANDOVER; 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.text.TextUtils; 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 @@ public class ConnectivitySettingsManager { /** * 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 class ConnectivitySettingsManager { }) 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 @@ public class ConnectivitySettingsManager { 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 @@ public class ConnectivitySettingsManager { */ @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 @@ public class ConnectivitySettingsManager { * @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 @@ public class ConnectivitySettingsManager { */ @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 @@ public class ConnectivitySettingsManager { * @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 5b95eea332..5352a604b3 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 @@ public abstract class AbstractRestrictBackgroundNetworkTestCase { */ 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 0000000000..ddc5fd4357 --- /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 7da1a212ad..4f9ce7cf0d 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; 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 androidx.test.platform.app.InstrumentationRegistry; 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 @@ public class NetworkPolicyTestUtils { 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 @@ public class NetworkPolicyTestUtils { 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 class NetworkPolicyTestUtils { 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 29d3c6e1ba..5f0f6d6bea 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 @@ public final class RestrictedModeTest extends AbstractRestrictBackgroundNetworkT @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 @@ public final class RestrictedModeTest extends AbstractRestrictBackgroundNetworkT 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 84852636fc..62aa493c6f 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 @@ public class VpnTest extends InstrumentationTestCase { 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 0000000000..fdb8876a36 --- /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 a889c41f53..9f079c42ed 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.CONNECTIVITY_INTERNAL 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 fi.iki.elonen.NanoHTTPD.Response.Status 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 @@ class CaptivePortalTest { 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 @@ class CaptivePortalTest { 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 d649518528..eb9c3054cb 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 @@ package android.net.cts; 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_IMS; 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.CtsNetUtils.TestNetworkCallback; 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.assertNotEquals; 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.TelephonyNetworkSpecifier; 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.SystemProperties; 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.DevSdkIgnoreRule.IgnoreUpTo; 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.ArrayList; 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.function.Supplier; 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 @@ public class ConnectivityManagerTest { 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 @@ public class ConnectivityManagerTest { // Used for cleanup purposes. private final List> mVpnRequiredUidRanges = new ArrayList<>(); + private final TestHttpServer mHttpServer = new TestHttpServer(LOCALHOST_HOSTNAME); + @Before public void setUp() throws Exception { mInstrumentation = InstrumentationRegistry.getInstrumentation(); @@ -667,12 +687,14 @@ public class ConnectivityManagerTest { 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 @@ public class ConnectivityManagerTest { 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 @@ public class ConnectivityManagerTest { } 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 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 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 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 f6fc75b5f4..dde14ac585 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 @@ internal object NetworkValidationTestUtil { /** * Clear the test network validation URLs. */ - fun clearValidationTestUrlsDeviceConfig() { + @JvmStatic fun clearValidationTestUrlsDeviceConfig() { setHttpsUrlDeviceConfig(null) setHttpUrlDeviceConfig(null) setUrlExpirationDeviceConfig(null) @@ -40,7 +40,7 @@ internal object NetworkValidationTestUtil { * * @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 @@ internal object NetworkValidationTestUtil { * * @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 @@ internal object NetworkValidationTestUtil { * * @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 0000000000..7d5e9ff02e --- /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 headers = new HashMap(); + 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 467d12f9dc..0000000000 --- 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 0000000000..a661b26f33 --- /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 b5f1208a4d..fffd30f7e1 100644 --- a/tests/cts/net/util/Android.bp +++ b/tests/cts/net/util/Android.bp @@ -27,5 +27,6 @@ java_library { "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 b32218bc3f..bce9880db8 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.net.wifi.WifiManager; 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 @@ import java.util.concurrent.TimeoutException; 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 @@ public final class CtsNetUtils { 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 final class CtsNetUtils { } 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 @@ public final class CtsNetUtils { // 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); } }