blob: 15ea9c623d01d6abb6b8a22c02921283fbe01e02 [file] [log] [blame]
/*
* 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 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.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.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.
*/
@RunWith(AndroidJUnit4.class)
public class DeviceAdminActivationTest {
@Rule
public ActivityTestRule<CtsDeviceAdminActivationTestActivity> mActivityRule =
new ActivityTestRule<>(CtsDeviceAdminActivationTestActivity.class);
private static final String TAG = DeviceAdminActivationTest.class.getSimpleName();
// 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;
private boolean mHasFeature;
private Instrumentation mInstrumentation;
@Mock private OnActivityResultListener mMockOnActivityResultListener;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mActivityRule.getActivity().setOnActivityResultListener(mMockOnActivityResultListener);
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mHasFeature = mInstrumentation.getContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_DEVICE_ADMIN);
}
@After
public void tearDown() throws Exception {
finishActivateDeviceAdminActivity();
}
@Test
public void testActivateGoodReceiverDisplaysActivationUi() throws Exception {
if (!mHasFeature) {
Log.w(TAG, "Skipping testActivateGoodReceiverDisplaysActivationUi");
return;
}
assertDeviceAdminDeactivated(CtsDeviceAdminDeactivatedReceiver.class);
startAddDeviceAdminActivityForResult(CtsDeviceAdminDeactivatedReceiver.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);
assertDeviceAdminDeactivated(CtsDeviceAdminDeactivatedReceiver.class);
}
@Test
public void testActivateBrokenReceiverFails() throws Exception {
if (!mHasFeature) {
Log.w(TAG, "Skipping testActivateBrokenReceiverFails");
return;
}
assertDeviceAdminDeactivated(CtsDeviceAdminBrokenReceiver.class);
startAddDeviceAdminActivityForResult(CtsDeviceAdminBrokenReceiver.class);
assertWithTimeoutOnActivityResultInvokedWithResultCode(Activity.RESULT_CANCELED);
assertDeviceAdminDeactivated(CtsDeviceAdminBrokenReceiver.class);
}
@Test
public void testActivateBrokenReceiver2Fails() throws Exception {
if (!mHasFeature) {
Log.w(TAG, "Skipping testActivateBrokenReceiver2Fails");
return;
}
assertDeviceAdminDeactivated(CtsDeviceAdminBrokenReceiver2.class);
startAddDeviceAdminActivityForResult(CtsDeviceAdminBrokenReceiver2.class);
assertWithTimeoutOnActivityResultInvokedWithResultCode(Activity.RESULT_CANCELED);
assertDeviceAdminDeactivated(CtsDeviceAdminBrokenReceiver2.class);
}
@Test
public void testActivateBrokenReceiver3Fails() throws Exception {
if (!mHasFeature) {
Log.w(TAG, "Skipping testActivateBrokenReceiver3Fails");
return;
}
assertDeviceAdminDeactivated(CtsDeviceAdminBrokenReceiver3.class);
startAddDeviceAdminActivityForResult(CtsDeviceAdminBrokenReceiver3.class);
assertWithTimeoutOnActivityResultInvokedWithResultCode(Activity.RESULT_CANCELED);
assertDeviceAdminDeactivated(CtsDeviceAdminBrokenReceiver3.class);
}
@Test
public void testActivateBrokenReceiver4Fails() throws Exception {
if (!mHasFeature) {
Log.w(TAG, "Skipping testActivateBrokenReceiver4Fails");
return;
}
assertDeviceAdminDeactivated(CtsDeviceAdminBrokenReceiver4.class);
startAddDeviceAdminActivityForResult(CtsDeviceAdminBrokenReceiver4.class);
assertWithTimeoutOnActivityResultInvokedWithResultCode(Activity.RESULT_CANCELED);
assertDeviceAdminDeactivated(CtsDeviceAdminBrokenReceiver4.class);
}
@Test
public void testActivateBrokenReceiver5Fails() throws Exception {
if (!mHasFeature) {
Log.w(TAG, "Skipping testActivateBrokenReceiver5Fails");
return;
}
assertDeviceAdminDeactivated(CtsDeviceAdminBrokenReceiver5.class);
startAddDeviceAdminActivityForResult(CtsDeviceAdminBrokenReceiver5.class);
assertWithTimeoutOnActivityResultInvokedWithResultCode(Activity.RESULT_CANCELED);
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 = mActivityRule.getActivity();
Intent intent = getAddDeviceAdminIntent(receiverClass);
Log.d(TAG, "starting activity " + intent + " from " + activity + " on user "
+ activity.getUser());
activity.startActivityForResult(intent, REQUEST_CODE_ACTIVATE_ADMIN);
}
private Intent getAddDeviceAdminIntent(Class<?> receiverClass) {
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)
.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin);
}
private void assertWithTimeoutOnActivityResultNotInvoked() {
SystemClock.sleep(UI_EFFECT_TIMEOUT_MILLIS);
Mockito.verify(mMockOnActivityResultListener, Mockito.never())
.onActivityResult(
Mockito.eq(REQUEST_CODE_ACTIVATE_ADMIN),
Mockito.anyInt(),
Mockito.nullable(Intent.class));
}
private void assertWithTimeoutOnActivityResultInvokedWithResultCode(int expectedResultCode) {
ArgumentCaptor<Integer> resultCodeCaptor = ArgumentCaptor.forClass(int.class);
Mockito.verify(mMockOnActivityResultListener, Mockito.timeout(UI_EFFECT_TIMEOUT_MILLIS))
.onActivityResult(
Mockito.eq(REQUEST_CODE_ACTIVATE_ADMIN),
resultCodeCaptor.capture(),
Mockito.nullable(Intent.class));
assertEquals(expectedResultCode, (int) resultCodeCaptor.getValue());
}
private void finishActivateDeviceAdminActivity() {
mActivityRule.getActivity().finishActivity(REQUEST_CODE_ACTIVATE_ADMIN);
}
private void assertDeviceAdminDeactivated(Class<?> receiverClass) {
Log.v(TAG, "assertDeviceAdminDeactivated(" + receiverClass + ")");
DevicePolicyManager devicePolicyManager =
(DevicePolicyManager) mActivityRule.getActivity().getSystemService(
Context.DEVICE_POLICY_SERVICE);
assertFalse(devicePolicyManager.isAdminActive(
new ComponentName(mInstrumentation.getTargetContext(), receiverClass)));
}
}