/* * Copyright 2016, 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.managedprovisioning.provisioning; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; import static android.app.admin.DevicePolicyManager .ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.ACTION_STATE_USER_SETUP_COMPLETE; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.Espresso.pressBack; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.intent.Intents.intended; import static android.support.test.espresso.intent.matcher.IntentMatchers.hasAction; import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static com.android.managedprovisioning.common.LogoUtils.saveOrganisationLogo; import static com.android.managedprovisioning.model.CustomizationParams.DEFAULT_STATUS_BAR_COLOR_ID; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.core.AllOf.allOf; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static java.util.Arrays.asList; import android.Manifest.permission; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Color; import android.os.Bundle; import android.provider.Settings; import android.support.test.InstrumentationRegistry; import android.support.test.espresso.intent.rule.IntentsTestRule; import android.support.test.filters.SmallTest; import android.support.test.runner.lifecycle.Stage; import com.android.managedprovisioning.R; import com.android.managedprovisioning.TestInstrumentationRunner; import com.android.managedprovisioning.common.CustomizationVerifier; import com.android.managedprovisioning.common.LogoUtils; import com.android.managedprovisioning.common.UriBitmap; import com.android.managedprovisioning.common.Utils; import com.android.managedprovisioning.model.ProvisioningParams; import com.android.managedprovisioning.testcommon.ActivityLifecycleWaiter; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.hamcrest.MockitoHamcrest; import org.mockito.junit.MockitoJUnitRunner; import java.util.ArrayList; import java.util.List; /** * Unit tests for {@link ProvisioningActivity}. */ @SmallTest @RunWith(MockitoJUnitRunner.class) public class ProvisioningActivityTest { private static final String ADMIN_PACKAGE = "com.test.admin"; private static final String TEST_PACKAGE = "com.android.managedprovisioning.tests"; private static final ComponentName ADMIN = new ComponentName(ADMIN_PACKAGE, ".Receiver"); private static final ComponentName TEST_ACTIVITY = new ComponentName(TEST_PACKAGE, EmptyActivity.class.getCanonicalName()); public static final ProvisioningParams PROFILE_OWNER_PARAMS = new ProvisioningParams.Builder() .setProvisioningAction(ACTION_PROVISION_MANAGED_PROFILE) .setDeviceAdminComponentName(ADMIN) .build(); public static final ProvisioningParams DEVICE_OWNER_PARAMS = new ProvisioningParams.Builder() .setProvisioningAction(ACTION_PROVISION_MANAGED_DEVICE) .setDeviceAdminComponentName(ADMIN) .build(); private static final ProvisioningParams NFC_PARAMS = new ProvisioningParams.Builder() .setProvisioningAction(ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE) .setDeviceAdminComponentName(ADMIN) .setStartedByTrustedSource(true) .setIsNfc(true) .build(); private static final Intent PROFILE_OWNER_INTENT = new Intent() .putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, PROFILE_OWNER_PARAMS); private static final Intent DEVICE_OWNER_INTENT = new Intent() .putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, DEVICE_OWNER_PARAMS); private static final Intent NFC_INTENT = new Intent() .putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, NFC_PARAMS); private static final int DEFAULT_MAIN_COLOR = Color.rgb(1, 2, 3); private static class CustomIntentsTestRule extends IntentsTestRule { private boolean mIsActivityRunning = false; private CustomIntentsTestRule() { super(ProvisioningActivity.class, true /* Initial touch mode */, false /* Lazily launch activity */); } @Override protected synchronized void afterActivityLaunched() { mIsActivityRunning = true; super.afterActivityLaunched(); } @Override public synchronized void afterActivityFinished() { // Temp fix for b/37663530 if (mIsActivityRunning) { super.afterActivityFinished(); mIsActivityRunning = false; } } } @Rule public CustomIntentsTestRule mActivityRule = new CustomIntentsTestRule(); @Mock private ProvisioningManager mProvisioningManager; @Mock private PackageManager mPackageManager; @Mock private Utils mUtils; private static int mRotationLocked; @BeforeClass public static void setUpClass() { // Stop the activity from rotating in order to keep hold of the context Context context = InstrumentationRegistry.getTargetContext(); mRotationLocked = Settings.System.getInt(context.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0); Settings.System.putInt(context.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0); } @Before public void setup() { when(mUtils.getAccentColor(any())).thenReturn(DEFAULT_MAIN_COLOR); } @AfterClass public static void tearDownClass() { // Reset the rotation value back to what it was before the test Context context = InstrumentationRegistry.getTargetContext(); Settings.System.putInt(context.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, mRotationLocked); } @Before public void setUp() { TestInstrumentationRunner.registerReplacedActivity(ProvisioningActivity.class, (classLoader, className, intent) -> new ProvisioningActivity(mProvisioningManager, mUtils) { @Override public PackageManager getPackageManager() { return mPackageManager; } }); // LogoUtils cached icon globally. Clean-up the cache LogoUtils.cleanUp(InstrumentationRegistry.getTargetContext()); } @After public void tearDown() { TestInstrumentationRunner.unregisterReplacedActivity(ProvisioningActivity.class); } @Test public void testLaunch() { // GIVEN the activity was launched with a profile owner intent launchActivityAndWait(PROFILE_OWNER_INTENT); // THEN the provisioning process should be initiated verify(mProvisioningManager).maybeStartProvisioning(PROFILE_OWNER_PARAMS); // THEN the activity should start listening for provisioning updates verify(mProvisioningManager).registerListener(any(ProvisioningManagerCallback.class)); verifyNoMoreInteractions(mProvisioningManager); } @Test public void testColors() throws Throwable { Context context = InstrumentationRegistry.getTargetContext(); // default color Managed Profile (MP) assertColorsCorrect( PROFILE_OWNER_INTENT, DEFAULT_MAIN_COLOR, context.getColor(DEFAULT_STATUS_BAR_COLOR_ID)); // default color Device Owner (DO) assertColorsCorrect( DEVICE_OWNER_INTENT, DEFAULT_MAIN_COLOR, context.getColor(DEFAULT_STATUS_BAR_COLOR_ID)); // custom color for both cases (MP, DO) int targetColor = Color.parseColor("#d40000"); // any color (except default) would do for (String action : asList(ACTION_PROVISION_MANAGED_PROFILE, ACTION_PROVISION_MANAGED_DEVICE)) { ProvisioningParams provisioningParams = new ProvisioningParams.Builder() .setProvisioningAction(action) .setDeviceAdminComponentName(ADMIN) .setMainColor(targetColor) .build(); Intent intent = new Intent(); intent.putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, provisioningParams); assertColorsCorrect(intent, targetColor, targetColor); } } private void assertColorsCorrect(Intent intent, int mainColor, int statusBarColor) throws Throwable { launchActivityAndWait(intent); Activity activity = mActivityRule.getActivity(); CustomizationVerifier customizationVerifier = new CustomizationVerifier(activity); customizationVerifier.assertStatusBarColorCorrect(statusBarColor); customizationVerifier.assertDefaultLogoCorrect(mainColor); finishAndWait(); } private void finishAndWait() throws Throwable { Activity activity = mActivityRule.getActivity(); ActivityLifecycleWaiter waiter = new ActivityLifecycleWaiter(activity, Stage.DESTROYED); mActivityRule.runOnUiThread(() -> activity.finish()); waiter.waitForStage(); mActivityRule.afterActivityFinished(); } @Test public void testCustomLogo_profileOwner() throws Throwable { assertCustomLogoCorrect(PROFILE_OWNER_INTENT); } @Test public void testCustomLogo_deviceOwner() throws Throwable { assertCustomLogoCorrect(PROFILE_OWNER_INTENT); } private void assertCustomLogoCorrect(Intent intent) throws Throwable { UriBitmap targetLogo = UriBitmap.createSimpleInstance(); saveOrganisationLogo(InstrumentationRegistry.getTargetContext(), targetLogo.getUri()); launchActivityAndWait(intent); ProvisioningActivity activity = mActivityRule.getActivity(); new CustomizationVerifier(activity).assertCustomLogoCorrect(targetLogo.getBitmap()); finishAndWait(); } @Test public void testSavedInstanceState() throws Throwable { // GIVEN the activity was launched with a profile owner intent launchActivityAndWait(PROFILE_OWNER_INTENT); // THEN the provisioning process should be initiated verify(mProvisioningManager).maybeStartProvisioning(PROFILE_OWNER_PARAMS); // WHEN the activity is recreated with a saved instance state mActivityRule.runOnUiThread(() -> { Bundle bundle = new Bundle(); InstrumentationRegistry.getInstrumentation() .callActivityOnSaveInstanceState(mActivityRule.getActivity(), bundle); InstrumentationRegistry.getInstrumentation() .callActivityOnCreate(mActivityRule.getActivity(), bundle); }); // THEN provisioning should not be initiated again verify(mProvisioningManager).maybeStartProvisioning(PROFILE_OWNER_PARAMS); } @Test public void testPause() throws Throwable { // GIVEN the activity was launched with a profile owner intent launchActivityAndWait(PROFILE_OWNER_INTENT); // WHEN the activity is paused mActivityRule.runOnUiThread(() -> { InstrumentationRegistry.getInstrumentation() .callActivityOnPause(mActivityRule.getActivity()); }); // THEN the listener is unregistered verify(mProvisioningManager).unregisterListener(any(ProvisioningManagerCallback.class)); } @Test public void testErrorNoFactoryReset() throws Throwable { // GIVEN the activity was launched with a profile owner intent launchActivityAndWait(PROFILE_OWNER_INTENT); // WHEN an error occurred that does not require factory reset final int errorMsgId = R.string.managed_provisioning_error_text; mActivityRule.runOnUiThread(() -> mActivityRule.getActivity().error(R.string.cant_set_up_device, errorMsgId, false)); // THEN the UI should show an error dialog onView(withText(errorMsgId)).check(matches(isDisplayed())); // WHEN clicking ok onView(withId(android.R.id.button1)) .check(matches(withText(R.string.device_owner_error_ok))) .perform(click()); // THEN the activity should be finishing assertTrue(mActivityRule.getActivity().isFinishing()); } @Test public void testErrorFactoryReset() throws Throwable { // GIVEN the activity was launched with a device owner intent launchActivityAndWait(DEVICE_OWNER_INTENT); // WHEN an error occurred that does not require factory reset final int errorMsgId = R.string.managed_provisioning_error_text; mActivityRule.runOnUiThread(() -> mActivityRule.getActivity().error(R.string.cant_set_up_device, errorMsgId, true)); // THEN the UI should show an error dialog onView(withText(errorMsgId)).check(matches(isDisplayed())); // WHEN clicking the ok button that says that factory reset is required onView(withId(android.R.id.button1)) .check(matches(withText(R.string.reset))) .perform(click()); // THEN factory reset should be invoked verify(mUtils).sendFactoryResetBroadcast(any(Context.class), anyString()); } @Test public void testCancelProfileOwner() throws Throwable { // GIVEN the activity was launched with a profile owner intent launchActivityAndWait(PROFILE_OWNER_INTENT); // WHEN the user tries to cancel pressBack(); // THEN the cancel dialog should be shown onView(withText(R.string.profile_owner_cancel_message)).check(matches(isDisplayed())); // WHEN deciding not to cancel onView(withId(android.R.id.button2)) .check(matches(withText(R.string.profile_owner_cancel_cancel))) .perform(click()); // THEN the activity should not be finished assertFalse(mActivityRule.getActivity().isFinishing()); // WHEN the user tries to cancel pressBack(); // THEN the cancel dialog should be shown onView(withText(R.string.profile_owner_cancel_message)).check(matches(isDisplayed())); // WHEN deciding to cancel onView(withId(android.R.id.button1)) .check(matches(withText(R.string.profile_owner_cancel_ok))) .perform(click()); // THEN the manager should be informed verify(mProvisioningManager).cancelProvisioning(); // THEN the activity should be finished assertTrue(mActivityRule.getActivity().isFinishing()); } @Test public void testCancelProfileOwner_CompProvisioningWithSkipConsent() throws Throwable { // GIVEN launching profile intent with skipping user consent ProvisioningParams params = new ProvisioningParams.Builder() .setProvisioningAction(ACTION_PROVISION_MANAGED_PROFILE) .setDeviceAdminComponentName(ADMIN) .setSkipUserConsent(true) .build(); Intent intent = new Intent() .putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, params); launchActivityAndWait(new Intent(intent)); // WHEN the user tries to cancel mActivityRule.runOnUiThread(() -> mActivityRule.getActivity().onBackPressed()); // THEN never unregistering ProvisioningManager verify(mProvisioningManager, never()).unregisterListener( any(ProvisioningManagerCallback.class)); } @Test public void testCancelProfileOwner_CompProvisioningWithoutSkipConsent() throws Throwable { // GIVEN launching profile intent without skipping user consent ProvisioningParams params = new ProvisioningParams.Builder() .setProvisioningAction(ACTION_PROVISION_MANAGED_PROFILE) .setDeviceAdminComponentName(ADMIN) .setSkipUserConsent(false) .build(); Intent intent = new Intent() .putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, params); launchActivityAndWait(new Intent(intent)); // WHEN the user tries to cancel mActivityRule.runOnUiThread(() -> mActivityRule.getActivity().onBackPressed()); // THEN unregistering ProvisioningManager verify(mProvisioningManager).unregisterListener(any(ProvisioningManagerCallback.class)); // THEN the cancel dialog should be shown onView(withText(R.string.profile_owner_cancel_message)).check(matches(isDisplayed())); } @Test public void testCancelDeviceOwner() throws Throwable { // GIVEN the activity was launched with a device owner intent launchActivityAndWait(DEVICE_OWNER_INTENT); // WHEN the user tries to cancel pressBack(); // THEN the cancel dialog should be shown onView(withText(R.string.stop_setup_reset_device_question)).check(matches(isDisplayed())); onView(withText(R.string.this_will_reset_take_back_first_screen)) .check(matches(isDisplayed())); // WHEN deciding not to cancel onView(withId(android.R.id.button2)) .check(matches(withText(R.string.device_owner_cancel_cancel))) .perform(click()); // THEN the activity should not be finished assertFalse(mActivityRule.getActivity().isFinishing()); // WHEN the user tries to cancel pressBack(); // THEN the cancel dialog should be shown onView(withText(R.string.stop_setup_reset_device_question)).check(matches(isDisplayed())); // WHEN deciding to cancel onView(withId(android.R.id.button1)) .check(matches(withText(R.string.reset))) .perform(click()); // THEN factory reset should be invoked verify(mUtils).sendFactoryResetBroadcast(any(Context.class), anyString()); } @Test public void testSuccess() throws Throwable { // GIVEN the activity was launched with a profile owner intent launchActivityAndWait(PROFILE_OWNER_INTENT); // WHEN preFinalization is completed mActivityRule.runOnUiThread(() -> mActivityRule.getActivity().preFinalizationCompleted()); // THEN the activity should finish assertTrue(mActivityRule.getActivity().isFinishing()); } @Test public void testSuccess_Nfc() throws Throwable { // GIVEN queryIntentActivities return test_activity ActivityInfo activityInfo = new ActivityInfo(); activityInfo.packageName = TEST_ACTIVITY.getPackageName(); activityInfo.name = TEST_ACTIVITY.getClassName(); activityInfo.permission = permission.BIND_DEVICE_ADMIN; ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.activityInfo = activityInfo; List resolveInfoList = new ArrayList(); resolveInfoList.add(resolveInfo); when(mPackageManager.queryIntentActivities( MockitoHamcrest.argThat(hasAction(ACTION_STATE_USER_SETUP_COMPLETE)), eq(0))).thenReturn(resolveInfoList); when(mPackageManager.checkPermission(eq(permission.DISPATCH_PROVISIONING_MESSAGE), eq(activityInfo.packageName))).thenReturn(PackageManager.PERMISSION_GRANTED); // GIVEN the activity was launched with a nfc intent launchActivityAndWait(NFC_INTENT); // WHEN preFinalization is completed mActivityRule.runOnUiThread(() -> mActivityRule.getActivity().preFinalizationCompleted()); // THEN verify starting TEST_ACTIVITY intended(allOf(hasComponent(TEST_ACTIVITY), hasAction(ACTION_STATE_USER_SETUP_COMPLETE))); // THEN the activity should finish assertTrue(mActivityRule.getActivity().isFinishing()); } @Test public void testInitializeUi_profileOwner() throws Throwable { // GIVEN the activity was launched with a profile owner intent launchActivityAndWait(PROFILE_OWNER_INTENT); // THEN the profile owner description should be present onView(withId(R.id.description)) .check(matches(withText(R.string.work_profile_description))); // THEN the animation is shown. onView(withId(R.id.animation)).check(matches(isDisplayed())); } @Test public void testInitializeUi_deviceOwner() throws Throwable { // GIVEN the activity was launched with a device owner intent launchActivityAndWait(DEVICE_OWNER_INTENT); // THEN the description should be empty onView(withId(R.id.description)) .check(matches(withText(R.string.device_owner_description))); // THEN the animation is shown. onView(withId(R.id.animation)).check(matches(isDisplayed())); } private void launchActivityAndWait(Intent intent) { mActivityRule.launchActivity(intent); onView(withId(R.id.setup_wizard_layout)); } }