Add test for always-on VPN working across reboot.
In N, we added the always-on VPN feature which allows VPN apps'
connection to be persisted across system reboots. We now add CTS tests
to verify this behavior.
More specifically, we set a VPN app as always-on, then reboot the
device, and finally verify that the VPN app is started and that network
connection is tunneled through the VPN after the credential-encrypted
storage of the device / work profile is unlocked.
Bug: 27714439
Test: run cts-dev --module CtsDevicePolicyManagerTestCases -t com.android.cts.devicepolicy.MixedDeviceOwnerTest#testAlwaysOnVpnAcrossReboot
Test: run cts-dev --module CtsDevicePolicyManagerTestCases -t com.android.cts.devicepolicy.MixedProfileOwnerTest#testAlwaysOnVpnAcrossReboot
Test: run cts-dev --module CtsDevicePolicyManagerTestCases -t com.android.cts.devicepolicy.MixedManagedProfileOwnerTest#testAlwaysOnVpnAcrossReboot
Change-Id: I277d621030a49e78c89077d29c320fe90b52aed7
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnMultiStageTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnMultiStageTest.java
index 0b20c64..e445347c 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnMultiStageTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnMultiStageTest.java
@@ -16,15 +16,15 @@
package com.android.cts.deviceandprofileowner;
+import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.TEST_ADDRESS;
+import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.VPN_PACKAGE;
+
import android.content.pm.PackageManager;
import android.system.ErrnoException;
import android.system.OsConstants;
import com.android.cts.deviceandprofileowner.vpn.VpnTestHelper;
-import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.VPN_PACKAGE;
-import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.TEST_ADDRESS;
-
/**
* Contains methods to test always-on VPN invoked by DeviceAndProfileOwnerTest
*/
@@ -32,11 +32,16 @@
public void testAlwaysOnSet() throws Exception {
// Setup always-on vpn
- VpnTestHelper.setAndWaitForVpn(mContext, VPN_PACKAGE, /* usable */ true);
+ VpnTestHelper.waitForVpn(mContext, VPN_PACKAGE, /* usable */ true);
assertTrue(VpnTestHelper.isNetworkVpn(mContext));
VpnTestHelper.checkPing(TEST_ADDRESS);
}
+ public void testAlwaysOnSetAfterReboot() throws Exception {
+ VpnTestHelper.waitForVpn(mContext, null, /* usable */ true);
+ VpnTestHelper.checkPing(TEST_ADDRESS);
+ }
+
public void testNetworkBlocked() throws Exception {
// After the vpn app being force-stop, expect that always-on package stays the same
assertEquals(VPN_PACKAGE, mDevicePolicyManager.getAlwaysOnVpnPackage(
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
index 84330ab..fff701e 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
@@ -62,7 +62,7 @@
// test always-on is null by default
assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
- VpnTestHelper.setAndWaitForVpn(mContext, VPN_PACKAGE, /* usable */ true);
+ VpnTestHelper.waitForVpn(mContext, VPN_PACKAGE, /* usable */ true);
VpnTestHelper.checkPing(TEST_ADDRESS);
}
@@ -83,7 +83,7 @@
restrictions.putStringArray(RESTRICTION_ALLOWED, new String[] {mPackageName});
mDevicePolicyManager.setApplicationRestrictions(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE,
restrictions);
- VpnTestHelper.setAndWaitForVpn(mContext, VPN_PACKAGE, /* usable */ true);
+ VpnTestHelper.waitForVpn(mContext, VPN_PACKAGE, /* usable */ true);
assertTrue(VpnTestHelper.isNetworkVpn(mContext));
}
@@ -92,7 +92,7 @@
restrictions.putStringArray(RESTRICTION_DISALLOWED, new String[] {mPackageName});
mDevicePolicyManager.setApplicationRestrictions(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE,
restrictions);
- VpnTestHelper.setAndWaitForVpn(mContext, VPN_PACKAGE, /* usable */ false);
+ VpnTestHelper.waitForVpn(mContext, VPN_PACKAGE, /* usable */ false);
assertFalse(VpnTestHelper.isNetworkVpn(mContext));
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/vpn/VpnTestHelper.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/vpn/VpnTestHelper.java
index 91b710e..6aeff7b 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/vpn/VpnTestHelper.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/vpn/VpnTestHelper.java
@@ -16,6 +16,16 @@
package com.android.cts.deviceandprofileowner.vpn;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.IPPROTO_ICMP;
+import static android.system.OsConstants.POLLIN;
+import static android.system.OsConstants.SOCK_DGRAM;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
import android.annotation.TargetApi;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
@@ -43,14 +53,6 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.IPPROTO_ICMP;
-import static android.system.OsConstants.POLLIN;
-import static android.system.OsConstants.SOCK_DGRAM;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
-
/**
* Helper class to test vpn status
*/
@@ -70,47 +72,62 @@
private static final int NETWORK_TIMEOUT_MS = 5000;
private static final ComponentName ADMIN_RECEIVER_COMPONENT =
BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT;
+ private static final NetworkRequest VPN_NETWORK_REQUEST = new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build();
- public static void setAndWaitForVpn(Context context, String packageName, boolean usable) {
- ConnectivityManager connectivityManager =
- context.getSystemService(ConnectivityManager.class);
+ /**
+ * Wait for a VPN app to establish VPN.
+ *
+ * @param context Caller's context.
+ * @param packageName {@code null} if waiting for the existing VPN to connect. Otherwise we set
+ * this package as the new always-on VPN app and wait for it to connect.
+ * @param usable Whether the resulting VPN tunnel is expected to be usable.
+ */
+ public static void waitForVpn(Context context, String packageName, boolean usable) {
DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+ if (packageName == null) {
+ assertNotNull(dpm.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+ }
+
+ ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
final CountDownLatch vpnLatch = new CountDownLatch(1);
- final NetworkRequest request = new NetworkRequest.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
- .build();
- final ConnectivityManager.NetworkCallback callback
- = new ConnectivityManager.NetworkCallback() {
- @Override
- public void onAvailable(Network net) {
- vpnLatch.countDown();
- }
- };
- connectivityManager.registerNetworkCallback(request, callback);
+ final ConnectivityManager.NetworkCallback callback =
+ new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(Network net) {
+ vpnLatch.countDown();
+ }
+ };
+ cm.registerNetworkCallback(VPN_NETWORK_REQUEST, callback);
+
try {
- dpm.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, packageName, true);
- assertEquals(packageName, dpm.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+ if (packageName != null) {
+ dpm.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, packageName, true);
+ assertEquals(packageName, dpm.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+ }
if (!vpnLatch.await(NETWORK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- fail("Took too long waiting to establish a VPN-backed connection");
+ if (!isNetworkVpn(context)) {
+ fail("Took too long waiting to establish a VPN-backed connection");
+ }
}
Thread.sleep(NETWORK_SETTLE_GRACE_MS);
} catch (InterruptedException | PackageManager.NameNotFoundException e) {
- fail("Failed to send ping: " + e);
+ fail("Failed while waiting for VPN: " + e);
} finally {
- connectivityManager.unregisterNetworkCallback(callback);
+ cm.unregisterNetworkCallback(callback);
}
// Do we have a network?
- NetworkInfo vpnInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_VPN);
- assertTrue(vpnInfo != null);
+ NetworkInfo vpnInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_VPN);
+ assertNotNull(vpnInfo);
// Is it usable?
assertEquals(usable, vpnInfo.isConnected());
}
-
public static boolean isNetworkVpn(Context context) {
ConnectivityManager connectivityManager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index 2063104..31e90c3 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -769,6 +769,23 @@
}
}
+ /**
+ * Verifies the lock credential for the given user, which unlocks the user.
+ *
+ * @param credential The credential to verify.
+ * @param userId The id of the user.
+ */
+ protected void verifyUserCredential(String credential, int userId)
+ throws DeviceNotAvailableException {
+ final String credentialArgument = (credential == null || credential.isEmpty())
+ ? "" : ("--old " + credential);
+ String commandOutput = getDevice().executeShellCommand(String.format(
+ "cmd lock_settings verify --user %d %s", userId, credentialArgument));
+ if (!commandOutput.startsWith("Lock credential verified")) {
+ fail("Failed to verify user credential: " + commandOutput);
+ }
+ }
+
protected void wakeupAndDismissKeyguard() throws Exception {
executeShellCommand("input keyevent KEYCODE_WAKEUP");
executeShellCommand("wm dismiss-keyguard");
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 54c86f6..4b4a575 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -284,6 +284,32 @@
}
@RequiresDevice
+ public void testAlwaysOnVpnAcrossReboot() throws Exception {
+ // Note: Always-on VPN is supported on non-FBE devices as well, and the behavior should be
+ // the same. However we're only testing the FBE case here as we need to set a device
+ // password during the test. This would cause FDE devices (e.g. angler) to prompt for the
+ // password during reboot, which we can't handle easily.
+ if (!mHasFeature || !mSupportsFbe) {
+ return;
+ }
+
+ // Set a password to encrypt the user
+ final String testPassword = "1234";
+ changeUserCredential(testPassword, null /*oldCredential*/, mUserId);
+
+ try {
+ installAppAsUser(VPN_APP_APK, mUserId);
+ executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testAlwaysOnSet");
+ rebootAndWaitUntilReady();
+ verifyUserCredential(testPassword, mUserId);
+ executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testAlwaysOnSetAfterReboot");
+ } finally {
+ changeUserCredential(null /*newCredential*/, testPassword, mUserId);
+ executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testCleanup");
+ }
+ }
+
+ @RequiresDevice
public void testAlwaysOnVpnPackageUninstalled() throws Exception {
if (!mHasFeature) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index 4bdeae9..730592a 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -119,29 +119,37 @@
// and profile owners on the primary user.
}
- /**
- * Don't require a device for tests that use the network stack on secondary users.
- */
+ /** VPN tests don't require physical device for managed profile, thus overriding. */
@Override
public void testAlwaysOnVpn() throws Exception {
super.testAlwaysOnVpn();
}
+ /** VPN tests don't require physical device for managed profile, thus overriding. */
@Override
public void testAlwaysOnVpnLockDown() throws Exception {
super.testAlwaysOnVpnLockDown();
}
+ /** VPN tests don't require physical device for managed profile, thus overriding. */
+ @Override
+ public void testAlwaysOnVpnAcrossReboot() throws Exception {
+ super.testAlwaysOnVpnAcrossReboot();
+ }
+
+ /** VPN tests don't require physical device for managed profile, thus overriding. */
@Override
public void testAlwaysOnVpnPackageUninstalled() throws Exception {
super.testAlwaysOnVpnPackageUninstalled();
}
+ /** VPN tests don't require physical device for managed profile, thus overriding. */
@Override
public void testAlwaysOnVpnUnsupportedPackage() throws Exception {
super.testAlwaysOnVpnUnsupportedPackage();
}
+ /** VPN tests don't require physical device for managed profile, thus overriding. */
@Override
public void testAlwaysOnVpnUnsupportedPackageReplaced() throws Exception {
super.testAlwaysOnVpnUnsupportedPackageReplaced();