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)));
}
}