Add CTS tests for activating malformed Device Admins.

Bug: 9224129
Change-Id: I74293a2f8b46f4bd5e83bdf9810b56a6dfee9c8d
diff --git a/tests/deviceadmin/AndroidManifest.xml b/tests/deviceadmin/AndroidManifest.xml
index f851e6c..69bc74d 100644
--- a/tests/deviceadmin/AndroidManifest.xml
+++ b/tests/deviceadmin/AndroidManifest.xml
@@ -40,6 +40,60 @@
             </intent-filter>
         </receiver>
 
+        <!-- Helper Activity used by Device Admin activation tests -->
+        <activity android:name="android.deviceadmin.cts.CtsDeviceAdminActivationTestActivity"
+                android:label="Device Admin activation test" />
+
+        <!-- Broken device admin: meta-data missing -->
+        <receiver android:name="android.deviceadmin.cts.CtsDeviceAdminBrokenReceiver"
+                android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+            </intent-filter>
+        </receiver>
+
+        <!-- Broken device admin: filter doesn't match an Intent with action
+             android.app.action.DEVICE_ADMIN_ENABLED and nothing else (e.g.,
+             data) set -->
+        <receiver android:name="android.deviceadmin.cts.CtsDeviceAdminBrokenReceiver2"
+                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" />
+                <data android:scheme="https" />
+            </intent-filter>
+        </receiver>
+
+        <!-- Broken device admin: meta-data element doesn't point to valid
+             Device Admin configuration/description -->
+        <receiver android:name="android.deviceadmin.cts.CtsDeviceAdminBrokenReceiver3"
+                android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data android:name="android.app.device_admin"
+                    android:resource="@xml/broken_device_admin" />
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+            </intent-filter>
+        </receiver>
+
+        <!-- Broken device admin: filter doesn't match Intents with action
+             android.app.action.DEVICE_ADMIN_ENABLED -->
+        <receiver android:name="android.deviceadmin.cts.CtsDeviceAdminBrokenReceiver4"
+                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_DISABLED" />
+            </intent-filter>
+        </receiver>
+
+        <!-- Broken device admin: no intent-filter -->
+        <receiver android:name="android.deviceadmin.cts.CtsDeviceAdminBrokenReceiver5"
+                android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data android:name="android.app.device_admin"
+                    android:resource="@xml/device_admin" />
+        </receiver>
+
     </application>
 
     <instrumentation android:name="android.test.InstrumentationTestRunner"
diff --git a/tests/deviceadmin/res/xml/broken_device_admin.xml b/tests/deviceadmin/res/xml/broken_device_admin.xml
new file mode 100644
index 0000000..937dabd
--- /dev/null
+++ b/tests/deviceadmin/res/xml/broken_device_admin.xml
@@ -0,0 +1,18 @@
+<!--
+ * Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" />
+
diff --git a/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminActivationTestActivity.java b/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminActivationTestActivity.java
new file mode 100644
index 0000000..a8c5051
--- /dev/null
+++ b/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminActivationTestActivity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 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.deviceadmin.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+/**
+ * Helper {@link Activity} for CTS tests of Device Admin activation. The {@code Activity}
+ * enables tests to capture the invocations of its {@link #onActivityResult(int, int, Intent)} by
+ * providing a {@link OnActivityResultListener}.
+ */
+public class CtsDeviceAdminActivationTestActivity extends Activity {
+    public interface OnActivityResultListener {
+        void onActivityResult(int requestCode, int resultCode, Intent data);
+    }
+
+    private OnActivityResultListener mOnActivityResultListener;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Dismiss keyguard and keep screen on while this Activity is displayed.
+        // This is needed because on older platforms, when the keyguard is on, onActivityResult is
+        // not invoked when a Device Admin activation is rejected without user interaction.
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    public void setOnActivityResultListener(OnActivityResultListener listener) {
+        mOnActivityResultListener = listener;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (mOnActivityResultListener != null) {
+            mOnActivityResultListener.onActivityResult(requestCode, resultCode, data);
+            return;
+        }
+
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+}
diff --git a/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminBrokenReceiver.java b/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminBrokenReceiver.java
new file mode 100644
index 0000000..f64c6b6
--- /dev/null
+++ b/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminBrokenReceiver.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2013 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.deviceadmin.cts;
+
+import android.app.admin.DeviceAdminReceiver;
+
+public class CtsDeviceAdminBrokenReceiver extends DeviceAdminReceiver {
+}
diff --git a/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminBrokenReceiver2.java b/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminBrokenReceiver2.java
new file mode 100644
index 0000000..492a7cb
--- /dev/null
+++ b/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminBrokenReceiver2.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2013 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.deviceadmin.cts;
+
+import android.app.admin.DeviceAdminReceiver;
+
+public class CtsDeviceAdminBrokenReceiver2 extends DeviceAdminReceiver {
+}
diff --git a/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminBrokenReceiver3.java b/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminBrokenReceiver3.java
new file mode 100644
index 0000000..92bc878
--- /dev/null
+++ b/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminBrokenReceiver3.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2013 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.deviceadmin.cts;
+
+import android.app.admin.DeviceAdminReceiver;
+
+public class CtsDeviceAdminBrokenReceiver3 extends DeviceAdminReceiver {
+}
diff --git a/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminBrokenReceiver4.java b/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminBrokenReceiver4.java
new file mode 100644
index 0000000..53f15a7
--- /dev/null
+++ b/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminBrokenReceiver4.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2013 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.deviceadmin.cts;
+
+import android.app.admin.DeviceAdminReceiver;
+
+public class CtsDeviceAdminBrokenReceiver4 extends DeviceAdminReceiver {
+}
diff --git a/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminBrokenReceiver5.java b/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminBrokenReceiver5.java
new file mode 100644
index 0000000..2dc7100
--- /dev/null
+++ b/tests/deviceadmin/src/android/deviceadmin/cts/CtsDeviceAdminBrokenReceiver5.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2013 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.deviceadmin.cts;
+
+import android.app.admin.DeviceAdminReceiver;
+
+public class CtsDeviceAdminBrokenReceiver5 extends DeviceAdminReceiver {
+}
diff --git a/tests/tests/admin/src/android/admin/cts/DeviceAdminActivationTest.java b/tests/tests/admin/src/android/admin/cts/DeviceAdminActivationTest.java
new file mode 100644
index 0000000..7e09989
--- /dev/null
+++ b/tests/tests/admin/src/android/admin/cts/DeviceAdminActivationTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2013 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.admin.cts;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.deviceadmin.cts.CtsDeviceAdminBrokenReceiver;
+import android.deviceadmin.cts.CtsDeviceAdminBrokenReceiver2;
+import android.deviceadmin.cts.CtsDeviceAdminBrokenReceiver3;
+import android.deviceadmin.cts.CtsDeviceAdminBrokenReceiver4;
+import android.deviceadmin.cts.CtsDeviceAdminBrokenReceiver5;
+import android.deviceadmin.cts.CtsDeviceAdminReceiver;
+import android.deviceadmin.cts.CtsDeviceAdminActivationTestActivity;
+import android.deviceadmin.cts.CtsDeviceAdminActivationTestActivity.OnActivityResultListener;
+import android.os.SystemClock;
+import android.test.ActivityInstrumentationTestCase2;
+
+/**
+ * Tests for the standard way of activating a Device Admin: by starting system UI via an
+ * {@link Intent} with {@link DevicePolicyManager#ACTION_ADD_DEVICE_ADMIN}. The test requires that
+ * the {@code CtsDeviceAdmin.apk} be installed.
+ */
+public class DeviceAdminActivationTest
+    extends ActivityInstrumentationTestCase2<CtsDeviceAdminActivationTestActivity> {
+
+    // IMPLEMENTATION NOTE: Because Device Admin activation requires the use of
+    // Activity.startActivityForResult, this test creates an empty Activity which then invokes
+    // startActivityForResult.
+
+    private static final int REQUEST_CODE_ACTIVATE_ADMIN = 1;
+
+    /**
+     * Maximum duration of time (milliseconds) after which the effects of programmatic actions in
+     * this test should have affected the UI.
+     */
+    private static final int UI_EFFECT_TIMEOUT_MILLIS = 5000;
+
+    /**
+     * Monitor guarding access to {@link #mLastOnActivityResultResultCode} and which is notified
+     * every time {@code onActivityResult} of the {@code CtsDeviceAdminActivationTestActivity} is
+     * invoked.
+     */
+    private final Object mOnActivityResultListenerLock = new Object();
+
+    /**
+     * Result code of the most recent invocation of
+     * {@code CtsDeviceAdminActivationTestActivity.onActivityResult} or {@code null} if no
+     * invocations have occured yet.
+     *
+     * @GuardedBy {@link #mOnActivityResultListenerLock}
+     */
+    private Integer mLastOnActivityResultResultCode;
+
+    public DeviceAdminActivationTest() {
+        super(CtsDeviceAdminActivationTestActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        getActivity().setOnActivityResultListener(new OnActivityResultListener() {
+            @Override
+            public void onActivityResult(int requestCode, int resultCode, Intent data) {
+                if (requestCode != REQUEST_CODE_ACTIVATE_ADMIN) {
+                    return;
+                }
+                synchronized (mOnActivityResultListenerLock) {
+                    mLastOnActivityResultResultCode = resultCode;
+                    mOnActivityResultListenerLock.notifyAll();
+                }
+            }
+        });
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        try {
+            finishActivateDeviceAdminActivity();
+        } finally {
+            super.tearDown();
+        }
+    }
+
+    public void testActivateGoodReceiverDisplaysActivationUi() throws Exception {
+        startAddDeviceAdminActivityForResult(CtsDeviceAdminReceiver.class);
+        assertWithTimeoutOnActivityResultNotInvoked();
+        // The UI is up and running. Assert that dismissing the UI returns the corresponding result
+        // to the test activity.
+        finishActivateDeviceAdminActivity();
+        assertWithTimeoutOnActivityResultInvokedWithResultCode(Activity.RESULT_CANCELED);
+    }
+
+    public void testActivateBrokenReceiverFails() throws Exception {
+        startAddDeviceAdminActivityForResult(CtsDeviceAdminBrokenReceiver.class);
+        assertWithTimeoutOnActivityResultInvokedWithResultCode(Activity.RESULT_CANCELED);
+    }
+
+    public void testActivateBrokenReceiver2Fails() throws Exception {
+        startAddDeviceAdminActivityForResult(CtsDeviceAdminBrokenReceiver2.class);
+        assertWithTimeoutOnActivityResultInvokedWithResultCode(Activity.RESULT_CANCELED);
+    }
+
+    public void testActivateBrokenReceiver3Fails() throws Exception {
+        startAddDeviceAdminActivityForResult(CtsDeviceAdminBrokenReceiver3.class);
+        assertWithTimeoutOnActivityResultInvokedWithResultCode(Activity.RESULT_CANCELED);
+    }
+
+    public void testActivateBrokenReceiver4Fails() throws Exception {
+        startAddDeviceAdminActivityForResult(CtsDeviceAdminBrokenReceiver4.class);
+        assertWithTimeoutOnActivityResultInvokedWithResultCode(Activity.RESULT_CANCELED);
+    }
+
+    public void testActivateBrokenReceiver5Fails() throws Exception {
+        startAddDeviceAdminActivityForResult(CtsDeviceAdminBrokenReceiver5.class);
+        assertWithTimeoutOnActivityResultInvokedWithResultCode(Activity.RESULT_CANCELED);
+    }
+
+    private void startAddDeviceAdminActivityForResult(Class<?> receiverClass) {
+        getActivity().startActivityForResult(
+                getAddDeviceAdminIntent(receiverClass),
+                REQUEST_CODE_ACTIVATE_ADMIN);
+    }
+
+    private Intent getAddDeviceAdminIntent(Class<?> receiverClass) {
+        return new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
+            .putExtra(
+                    DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+                    new ComponentName(
+                            getInstrumentation().getTargetContext(),
+                            receiverClass));
+    }
+
+    private void assertWithTimeoutOnActivityResultNotInvoked() {
+        SystemClock.sleep(UI_EFFECT_TIMEOUT_MILLIS);
+        synchronized (mOnActivityResultListenerLock) {
+            assertNull(mLastOnActivityResultResultCode);
+        }
+    }
+
+    private void assertWithTimeoutOnActivityResultInvokedWithResultCode(int expectedResultCode)
+            throws Exception {
+        long deadlineMillis = SystemClock.elapsedRealtime() + UI_EFFECT_TIMEOUT_MILLIS;
+        synchronized (mOnActivityResultListenerLock) {
+            while (true) {
+                if (mLastOnActivityResultResultCode != null) {
+                    // onActivityResult has been invoked -- check the arguments
+                    assertEquals(expectedResultCode, (int) mLastOnActivityResultResultCode);
+                    break;
+                }
+
+                // onActivityResult has not yet been invoked -- wait until it is
+                long millisTillDeadline = deadlineMillis - SystemClock.elapsedRealtime();
+                if (millisTillDeadline <= 0) {
+                    fail("onActivityResult not invoked within " + UI_EFFECT_TIMEOUT_MILLIS + " ms");
+                    break;
+                }
+
+                mOnActivityResultListenerLock.wait(millisTillDeadline);
+            }
+        }
+    }
+
+    private void finishActivateDeviceAdminActivity() {
+        getActivity().finishActivity(REQUEST_CODE_ACTIVATE_ADMIN);
+    }
+}