Managed profile creation/deletion stress test.

This test duplicates UserLifecycleStressTest to a degree, but it is
implemented as a host side test and is not subject to device test
timeout.

Bug: 141115315
Test: atest ManagedProfileLifecycleStressTest
Change-Id: I680a2f6965a49ba81b48591e4dcabc87091ac860
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 1e0b58e..55fa5ed 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -8,5 +8,10 @@
         }
       ]
     }
+  ],
+  "postsubmit-managedprofile-stress": [
+    {
+      "name": "ManagedProfileLifecycleStressTest"
+    }
   ]
 }
diff --git a/tests/ManagedProfileLifecycleStressTest/Android.bp b/tests/ManagedProfileLifecycleStressTest/Android.bp
new file mode 100644
index 0000000..639ce3c
--- /dev/null
+++ b/tests/ManagedProfileLifecycleStressTest/Android.bp
@@ -0,0 +1,23 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_host {
+    name: "ManagedProfileLifecycleStressTest",
+    srcs: ["src/**/*.java"],
+    libs: ["tradefed"],
+    test_suites: ["device-tests"],
+    target_required: [
+        "DummyDPC",
+    ],
+}
diff --git a/tests/ManagedProfileLifecycleStressTest/AndroidTest.xml b/tests/ManagedProfileLifecycleStressTest/AndroidTest.xml
new file mode 100644
index 0000000..e7dbc51
--- /dev/null
+++ b/tests/ManagedProfileLifecycleStressTest/AndroidTest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Stress test for managed profile lifecycle">
+  <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+    <option name="jar" value="ManagedProfileLifecycleStressTest.jar" />
+  </test>
+</configuration>
diff --git a/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp
new file mode 100644
index 0000000..d95af34
--- /dev/null
+++ b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp
@@ -0,0 +1,20 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "DummyDPC",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+}
diff --git a/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/AndroidManifest.xml b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/AndroidManifest.xml
new file mode 100644
index 0000000..860940d
--- /dev/null
+++ b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.dummydpc">
+
+    <application
+        android:testOnly="true">
+        <receiver
+            android:name="com.android.dummydpc.DummyDeviceAdminReceiver"
+            android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data android:name="android.app.device_admin"
+                       android:resource="@xml/device_admin" />
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
diff --git a/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/res/xml/device_admin.xml b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/res/xml/device_admin.xml
new file mode 100644
index 0000000..4b3581e
--- /dev/null
+++ b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/res/xml/device_admin.xml
@@ -0,0 +1,4 @@
+<device-admin>
+    <uses-policies>
+    </uses-policies>
+</device-admin>
diff --git a/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/src/com/android/dummydpc/DummyDeviceAdminReceiver.java b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/src/com/android/dummydpc/DummyDeviceAdminReceiver.java
new file mode 100644
index 0000000..92190b7
--- /dev/null
+++ b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/src/com/android/dummydpc/DummyDeviceAdminReceiver.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dummydpc;
+
+import android.app.admin.DeviceAdminReceiver;
+
+/**
+ * Empty admin to use as a managed profile owner.
+ */
+public class DummyDeviceAdminReceiver extends DeviceAdminReceiver {
+}
+
diff --git a/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java b/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java
new file mode 100644
index 0000000..e323592
--- /dev/null
+++ b/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.stress;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A test to exercise Android Framework parts related to creating, starting, stopping, and deleting
+ * a managed profile as much as possible. The aim is to catch any issues in this code before it
+ * affects managed profile CTS tests.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ManagedProfileLifecycleStressTest extends BaseHostJUnit4Test {
+    // Stop the test once this time limit has been reached. 25 minutes used as a limit to make total
+    // test time less than 30 minutes, so that it can be put into presubmit.
+    private static final int TIME_LIMIT_MINUTES = 25;
+
+    private static final String DUMMY_DPC_APK = "DummyDPC.apk";
+    private static final String DUMMY_DPC_COMPONENT =
+            "com.android.dummydpc/com.android.dummydpc.DummyDeviceAdminReceiver";
+    private static final Pattern CREATE_USER_OUTPUT_REGEX =
+            Pattern.compile("Success: created user id (\\d+)");
+
+    /**
+     * Create, start, and kill managed profiles in a loop.
+     */
+    @Test
+    public void testCreateStartDelete() throws Exception {
+        int iteration = 0;
+        final long deadline = System.nanoTime() + TimeUnit.MINUTES.toNanos(TIME_LIMIT_MINUTES);
+        while (System.nanoTime() < deadline) {
+            iteration++;
+            CLog.w("Iteration N" + iteration);
+            final int userId = createManagedProfile();
+            startUser(userId);
+            installPackageAsUser(DUMMY_DPC_APK, true /* grantPermissions */, userId, "-t");
+            setProfileOwner(DUMMY_DPC_COMPONENT, userId);
+            removeUser(userId);
+        }
+        CLog.w("Completed " + iteration + " iterations.");
+    }
+
+    private int createManagedProfile() throws Exception {
+        final String output = getDevice().executeShellCommand(
+                "pm create-user --profileOf 0 --managed TestProfile");
+        final Matcher matcher = CREATE_USER_OUTPUT_REGEX.matcher(output.trim());
+        if (!matcher.matches() || matcher.groupCount() != 1) {
+            fail("user creation failed, output: " + output);
+        }
+        return Integer.parseInt(matcher.group(1));
+    }
+
+    private void setProfileOwner(String componentName, int userId) throws Exception {
+        String command = "dpm set-profile-owner --user " + userId + " '" + componentName + "'";
+        String commandOutput = getDevice().executeShellCommand(command);
+        assertTrue("Unexpected dpm output: " + commandOutput, commandOutput.startsWith("Success:"));
+    }
+
+    private void removeUser(int userId) throws Exception {
+        final String output = getDevice().executeShellCommand("pm remove-user " + userId).trim();
+        assertEquals("Unexpected pm output: " + output, "Success: removed user", output);
+    }
+
+    private void startUser(int userId) throws Exception {
+        final String output = getDevice().executeShellCommand("am start-user -w " + userId).trim();
+        assertEquals("Unexpected am output: " + output, "Success: user started", output);
+    }
+}