DO NOT MERGE: CTS to ensure non-system IME becoming device admin

CTS test that non-system app/IME cant injects KeyEvents on the Device
admin screen to become the device admin.

For the sake of simplicity, using CTS class to inject KeyEvent is
equivalent to using non-system IME.

Bug: 280793427
Test: atest DeviceAdminActivationTest

Change-Id: Ife4398691c56c47c0f25fbd94cbb4b6ccfcae18e
(cherry picked from commit 18c6a7cebb4544099d5b81d224fe8e8a34daef27)
diff --git a/tests/admin/src/android/admin/cts/DeviceAdminActivationTest.java b/tests/admin/src/android/admin/cts/DeviceAdminActivationTest.java
index a48b7a6..15ea9c6 100644
--- a/tests/admin/src/android/admin/cts/DeviceAdminActivationTest.java
+++ b/tests/admin/src/android/admin/cts/DeviceAdminActivationTest.java
@@ -16,36 +16,61 @@
 
 package android.admin.cts;
 
-import android.app.Activity;
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.admin.app.CtsDeviceAdminActivationTestActivity;
+import android.admin.app.CtsDeviceAdminActivationTestActivity.OnActivityResultListener;
 import android.admin.app.CtsDeviceAdminBrokenReceiver;
 import android.admin.app.CtsDeviceAdminBrokenReceiver2;
 import android.admin.app.CtsDeviceAdminBrokenReceiver3;
 import android.admin.app.CtsDeviceAdminBrokenReceiver4;
 import android.admin.app.CtsDeviceAdminBrokenReceiver5;
 import android.admin.app.CtsDeviceAdminDeactivatedReceiver;
-import android.admin.app.CtsDeviceAdminActivationTestActivity;
-import android.admin.app.CtsDeviceAdminActivationTestActivity.OnActivityResultListener;
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.SystemClock;
-import android.test.ActivityInstrumentationTestCase2;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
 import android.util.Log;
+import android.view.InputDevice;
+import android.view.KeyEvent;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.regex.Pattern;
+
 /**
  * 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> {
+@RunWith(AndroidJUnit4.class)
+public class DeviceAdminActivationTest {
+
+    @Rule
+    public ActivityTestRule<CtsDeviceAdminActivationTestActivity> mActivityRule =
+            new ActivityTestRule<>(CtsDeviceAdminActivationTestActivity.class);
 
     private static final String TAG = DeviceAdminActivationTest.class.getSimpleName();
 
@@ -63,30 +88,26 @@
 
     private boolean mHasFeature;
 
+    private Instrumentation mInstrumentation;
+
     @Mock private OnActivityResultListener mMockOnActivityResultListener;
 
-    public DeviceAdminActivationTest() {
-        super(CtsDeviceAdminActivationTestActivity.class);
-    }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        getActivity().setOnActivityResultListener(mMockOnActivityResultListener);
-        mHasFeature = getInstrumentation().getContext().getPackageManager().hasSystemFeature(
+        mActivityRule.getActivity().setOnActivityResultListener(mMockOnActivityResultListener);
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mHasFeature = mInstrumentation.getContext().getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_DEVICE_ADMIN);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        try {
-            finishActivateDeviceAdminActivity();
-        } finally {
-            super.tearDown();
-        }
+    @After
+    public void tearDown() throws Exception {
+        finishActivateDeviceAdminActivity();
     }
 
+    @Test
     public void testActivateGoodReceiverDisplaysActivationUi() throws Exception {
         if (!mHasFeature) {
             Log.w(TAG, "Skipping testActivateGoodReceiverDisplaysActivationUi");
@@ -102,6 +123,7 @@
         assertDeviceAdminDeactivated(CtsDeviceAdminDeactivatedReceiver.class);
     }
 
+    @Test
     public void testActivateBrokenReceiverFails() throws Exception {
         if (!mHasFeature) {
             Log.w(TAG, "Skipping testActivateBrokenReceiverFails");
@@ -113,6 +135,7 @@
         assertDeviceAdminDeactivated(CtsDeviceAdminBrokenReceiver.class);
     }
 
+    @Test
     public void testActivateBrokenReceiver2Fails() throws Exception {
         if (!mHasFeature) {
             Log.w(TAG, "Skipping testActivateBrokenReceiver2Fails");
@@ -124,6 +147,7 @@
         assertDeviceAdminDeactivated(CtsDeviceAdminBrokenReceiver2.class);
     }
 
+    @Test
     public void testActivateBrokenReceiver3Fails() throws Exception {
         if (!mHasFeature) {
             Log.w(TAG, "Skipping testActivateBrokenReceiver3Fails");
@@ -135,6 +159,7 @@
         assertDeviceAdminDeactivated(CtsDeviceAdminBrokenReceiver3.class);
     }
 
+    @Test
     public void testActivateBrokenReceiver4Fails() throws Exception {
         if (!mHasFeature) {
             Log.w(TAG, "Skipping testActivateBrokenReceiver4Fails");
@@ -146,6 +171,7 @@
         assertDeviceAdminDeactivated(CtsDeviceAdminBrokenReceiver4.class);
     }
 
+    @Test
     public void testActivateBrokenReceiver5Fails() throws Exception {
         if (!mHasFeature) {
             Log.w(TAG, "Skipping testActivateBrokenReceiver5Fails");
@@ -157,8 +183,77 @@
         assertDeviceAdminDeactivated(CtsDeviceAdminBrokenReceiver5.class);
     }
 
+    @Test
+    public void testNonSystemAppCantBecomeAdmin_withKeyEvent() throws Exception {
+        if (!mHasFeature) {
+            Log.w(TAG, "Skipping testActivateGoodReceiverDisplaysActivationUi");
+            return;
+        }
+        assertDeviceAdminDeactivated(CtsDeviceAdminDeactivatedReceiver.class);
+        startAddDeviceAdminActivityForResult(CtsDeviceAdminDeactivatedReceiver.class);
+        assertWithTimeoutOnActivityResultNotInvoked();
+
+        // Find the "Activate this device admin app" button.
+        UiDevice device = UiDevice.getInstance(mInstrumentation);
+        Pattern patternToMatch = Pattern.compile("Activate.*", Pattern.CASE_INSENSITIVE);
+        UiObject2 activateButton = device.findObject(By.text(patternToMatch));
+
+        // If button doesn't exist, likely Device admin dialog is non-AOSP, skip the test.
+        Assume.assumeTrue(activateButton != null);
+
+        // inject KEYCODE_TAB and then KEYCODE_ENTER on "Activate" button should fail.
+        injectUntrustedKeyEventToActivateDeviceAdmin(activateButton);
+        assertWithTimeoutOnActivityResultNotInvoked();
+
+        finishActivateDeviceAdminActivity();
+        assertDeviceAdminDeactivated(CtsDeviceAdminDeactivatedReceiver.class);
+    }
+
+    /**
+     * Inject KeyEvents to tap the "Activate this device admin app" button.
+     * Sends the "tab" KeyEvent to selects the first button, then the "enter" KeyEvent presses it.
+      */
+    private void injectUntrustedKeyEventToActivateDeviceAdmin(UiObject2 activateButton) {
+        sendUntrustedDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
+        // Check if "Activate this device admin app" button was actually focused.
+        // R.id.restricted_action's parent R.id.buttonPanel should be selected by pressing TAB.
+        // If item is not focused, likely Device admin dialog is non-AOSP, skip the test.
+        Assume.assumeTrue(
+                activateButton.getParent() != null && activateButton.getParent().isFocused());
+
+        sendUntrustedDownUpKeyEvents(KeyEvent.KEYCODE_ENTER);
+    }
+
+    /**
+     * Creates a untrusted KeyEvent with KEYCODE_ENTER that appears to originate from a physical
+     * keyboard.
+     */
+    private KeyEvent createUntrustedKeyEvent(int action, int keyCode) {
+        // Note: FLAG_FROM_SYSTEM will be stripped by system.
+        return new KeyEvent(
+                SystemClock.uptimeMillis(),
+                SystemClock.uptimeMillis(),
+                action,
+                keyCode,
+                0,
+                0,
+                0,
+                0,
+                0 /* flags: not setting FLAG_FROM_SYSTEM */,
+                InputDevice.SOURCE_KEYBOARD
+        );
+    }
+
+    void sendUntrustedDownUpKeyEvents(int keyCode) {
+        UiAutomation uiAutomation = mInstrumentation.getUiAutomation();
+        uiAutomation.injectInputEvent(
+                createUntrustedKeyEvent(KeyEvent.ACTION_DOWN, keyCode), true /* sync */);
+        uiAutomation.injectInputEvent(
+                createUntrustedKeyEvent(KeyEvent.ACTION_UP, keyCode), true /* sync */);
+    }
+
     private void startAddDeviceAdminActivityForResult(Class<?> receiverClass) {
-        Activity activity = getActivity();
+        Activity activity = mActivityRule.getActivity();
         Intent intent = getAddDeviceAdminIntent(receiverClass);
         Log.d(TAG, "starting activity " + intent + " from " + activity + " on user "
                 + activity.getUser());
@@ -166,7 +261,7 @@
     }
 
     private Intent getAddDeviceAdminIntent(Class<?> receiverClass) {
-        ComponentName admin = new ComponentName(getInstrumentation().getTargetContext(),
+        ComponentName admin = new ComponentName(mInstrumentation.getTargetContext(),
                 receiverClass);
         Log.v(TAG, "admin on " + DevicePolicyManager.EXTRA_DEVICE_ADMIN + " extra: " + admin);
         return new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
@@ -193,15 +288,15 @@
     }
 
     private void finishActivateDeviceAdminActivity() {
-        getActivity().finishActivity(REQUEST_CODE_ACTIVATE_ADMIN);
+        mActivityRule.getActivity().finishActivity(REQUEST_CODE_ACTIVATE_ADMIN);
     }
 
     private void assertDeviceAdminDeactivated(Class<?> receiverClass) {
         Log.v(TAG, "assertDeviceAdminDeactivated(" + receiverClass + ")");
         DevicePolicyManager devicePolicyManager =
-                (DevicePolicyManager) getActivity().getSystemService(
+                (DevicePolicyManager) mActivityRule.getActivity().getSystemService(
                         Context.DEVICE_POLICY_SERVICE);
         assertFalse(devicePolicyManager.isAdminActive(
-                new ComponentName(getInstrumentation().getTargetContext(), receiverClass)));
+                new ComponentName(mInstrumentation.getTargetContext(), receiverClass)));
     }
 }