1 /* 2 * Copyright 2016, The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.managedprovisioning.provisioning; 18 19 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; 20 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE; 21 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; 22 import static android.app.admin.DevicePolicyManager.ACTION_STATE_USER_SETUP_COMPLETE; 23 import static android.support.test.espresso.Espresso.onView; 24 import static android.support.test.espresso.Espresso.pressBack; 25 import static android.support.test.espresso.action.ViewActions.click; 26 import static android.support.test.espresso.assertion.ViewAssertions.matches; 27 import static android.support.test.espresso.intent.Intents.intended; 28 import static android.support.test.espresso.intent.matcher.IntentMatchers.hasAction; 29 import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent; 30 import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 31 import static android.support.test.espresso.matcher.ViewMatchers.withId; 32 import static android.support.test.espresso.matcher.ViewMatchers.withText; 33 import static com.android.managedprovisioning.common.LogoUtils.saveOrganisationLogo; 34 import static com.android.managedprovisioning.model.CustomizationParams.DEFAULT_COLOR_ID_DO; 35 import static com.android.managedprovisioning.model.CustomizationParams.DEFAULT_COLOR_ID_MP; 36 import static java.util.Arrays.asList; 37 import static org.hamcrest.core.AllOf.allOf; 38 import static org.junit.Assert.assertFalse; 39 import static org.junit.Assert.assertTrue; 40 import static org.mockito.Matchers.any; 41 import static org.mockito.Matchers.anyString; 42 import static org.mockito.Matchers.eq; 43 import static org.mockito.Mockito.never; 44 import static org.mockito.Mockito.verify; 45 import static org.mockito.Mockito.verifyNoMoreInteractions; 46 import static org.mockito.Mockito.when; 47 48 import android.Manifest.permission; 49 import android.app.Activity; 50 import android.content.ComponentName; 51 import android.content.Context; 52 import android.content.Intent; 53 import android.content.pm.ActivityInfo; 54 import android.content.pm.PackageManager; 55 import android.content.pm.ResolveInfo; 56 import android.graphics.Color; 57 import android.os.Bundle; 58 import android.provider.Settings; 59 import android.support.test.InstrumentationRegistry; 60 import android.support.test.espresso.intent.Intents; 61 import android.support.test.espresso.intent.rule.IntentsTestRule; 62 import android.support.test.filters.SmallTest; 63 import android.support.test.runner.lifecycle.Stage; 64 import com.android.managedprovisioning.R; 65 import com.android.managedprovisioning.TestInstrumentationRunner; 66 import com.android.managedprovisioning.common.CustomizationVerifier; 67 import com.android.managedprovisioning.common.LogoUtils; 68 import com.android.managedprovisioning.common.UriBitmap; 69 import com.android.managedprovisioning.common.Utils; 70 import com.android.managedprovisioning.model.ProvisioningParams; 71 import com.android.managedprovisioning.testcommon.ActivityLifecycleWaiter; 72 import java.util.ArrayList; 73 import java.util.List; 74 import org.junit.After; 75 import org.junit.AfterClass; 76 import org.junit.Before; 77 import org.junit.BeforeClass; 78 import org.junit.Rule; 79 import org.junit.Test; 80 import org.mockito.Mock; 81 import org.mockito.MockitoAnnotations; 82 import org.mockito.hamcrest.MockitoHamcrest; 83 84 /** 85 * Unit tests for {@link ProvisioningActivity}. 86 */ 87 @SmallTest 88 public class ProvisioningActivityTest { 89 private static final String ADMIN_PACKAGE = "com.test.admin"; 90 private static final String TEST_PACKAGE = "com.android.managedprovisioning.tests"; 91 private static final ComponentName ADMIN = new ComponentName(ADMIN_PACKAGE, ".Receiver"); 92 private static final ComponentName TEST_ACTIVITY = new ComponentName(TEST_PACKAGE, 93 EmptyActivity.class.getCanonicalName()); 94 public static final ProvisioningParams PROFILE_OWNER_PARAMS = new ProvisioningParams.Builder() 95 .setProvisioningAction(ACTION_PROVISION_MANAGED_PROFILE) 96 .setDeviceAdminComponentName(ADMIN) 97 .build(); 98 public static final ProvisioningParams DEVICE_OWNER_PARAMS = new ProvisioningParams.Builder() 99 .setProvisioningAction(ACTION_PROVISION_MANAGED_DEVICE) 100 .setDeviceAdminComponentName(ADMIN) 101 .build(); 102 private static final ProvisioningParams NFC_PARAMS = new ProvisioningParams.Builder() 103 .setProvisioningAction(ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE) 104 .setDeviceAdminComponentName(ADMIN) 105 .setStartedByTrustedSource(true) 106 .setIsNfc(true) 107 .build(); 108 private static final Intent PROFILE_OWNER_INTENT = new Intent() 109 .putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, PROFILE_OWNER_PARAMS); 110 private static final Intent DEVICE_OWNER_INTENT = new Intent() 111 .putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, DEVICE_OWNER_PARAMS); 112 private static final Intent NFC_INTENT = new Intent() 113 .putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, NFC_PARAMS); 114 115 private static class CustomIntentsTestRule extends IntentsTestRule<ProvisioningActivity> { 116 private boolean mIsActivityRunning = false; CustomIntentsTestRule()117 private CustomIntentsTestRule() { 118 super(ProvisioningActivity.class, true /* Initial touch mode */, 119 false /* Lazily launch activity */); 120 } 121 122 @Override afterActivityLaunched()123 protected synchronized void afterActivityLaunched() { 124 mIsActivityRunning = true; 125 super.afterActivityLaunched(); 126 } 127 128 @Override afterActivityFinished()129 public synchronized void afterActivityFinished() { 130 // Temp fix for b/37663530 131 if (mIsActivityRunning) { 132 super.afterActivityFinished(); 133 mIsActivityRunning = false; 134 } 135 } 136 } 137 138 @Rule 139 public CustomIntentsTestRule mActivityRule = new CustomIntentsTestRule(); 140 141 @Mock private ProvisioningManager mProvisioningManager; 142 @Mock private PackageManager mPackageManager; 143 @Mock private Utils mUtils; 144 private static int mRotationLocked; 145 146 @BeforeClass setUpClass()147 public static void setUpClass() { 148 // Stop the activity from rotating in order to keep hold of the context 149 Context context = InstrumentationRegistry.getTargetContext(); 150 151 mRotationLocked = Settings.System.getInt(context.getContentResolver(), 152 Settings.System.ACCELEROMETER_ROTATION, 0); 153 Settings.System.putInt(context.getContentResolver(), 154 Settings.System.ACCELEROMETER_ROTATION, 0); 155 } 156 157 @AfterClass tearDownClass()158 public static void tearDownClass() { 159 // Reset the rotation value back to what it was before the test 160 Context context = InstrumentationRegistry.getTargetContext(); 161 162 Settings.System.putInt(context.getContentResolver(), 163 Settings.System.ACCELEROMETER_ROTATION, mRotationLocked); 164 } 165 166 @Before setUp()167 public void setUp() { 168 MockitoAnnotations.initMocks(this); 169 170 TestInstrumentationRunner.registerReplacedActivity(ProvisioningActivity.class, 171 (classLoader, className, intent) -> 172 new ProvisioningActivity(mProvisioningManager, mUtils) { 173 @Override 174 public PackageManager getPackageManager() { 175 return mPackageManager; 176 } 177 }); 178 // LogoUtils cached icon globally. Clean-up the cache 179 LogoUtils.cleanUp(InstrumentationRegistry.getTargetContext()); 180 } 181 182 @After tearDown()183 public void tearDown() { 184 TestInstrumentationRunner.unregisterReplacedActivity(ProvisioningActivity.class); 185 } 186 187 @Test testLaunch()188 public void testLaunch() { 189 // GIVEN the activity was launched with a profile owner intent 190 launchActivityAndWait(PROFILE_OWNER_INTENT); 191 192 // THEN the provisioning process should be initiated 193 verify(mProvisioningManager).maybeStartProvisioning(PROFILE_OWNER_PARAMS); 194 195 // THEN the activity should start listening for provisioning updates 196 verify(mProvisioningManager).registerListener(any(ProvisioningManagerCallback.class)); 197 verifyNoMoreInteractions(mProvisioningManager); 198 } 199 200 @Test testColors()201 public void testColors() throws Throwable { 202 Context context = InstrumentationRegistry.getTargetContext(); 203 204 // default color Managed Profile (MP) 205 assertColorsCorrect(PROFILE_OWNER_INTENT, context.getColor(DEFAULT_COLOR_ID_MP)); 206 207 // default color Device Owner (DO) 208 assertColorsCorrect(DEVICE_OWNER_INTENT, context.getColor(DEFAULT_COLOR_ID_DO)); 209 210 // custom color for both cases (MP, DO) 211 int targetColor = Color.parseColor("#d40000"); // any color (except default) would do 212 for (String action : asList(ACTION_PROVISION_MANAGED_PROFILE, 213 ACTION_PROVISION_MANAGED_DEVICE)) { 214 ProvisioningParams provisioningParams = new ProvisioningParams.Builder() 215 .setProvisioningAction(action) 216 .setDeviceAdminComponentName(ADMIN) 217 .setMainColor(targetColor) 218 .build(); 219 assertColorsCorrect(new Intent().putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, 220 provisioningParams), targetColor); 221 } 222 } 223 assertColorsCorrect(Intent intent, int color)224 private void assertColorsCorrect(Intent intent, int color) throws Throwable { 225 launchActivityAndWait(intent); 226 Activity activity = mActivityRule.getActivity(); 227 228 CustomizationVerifier customizationVerifier = new CustomizationVerifier(activity); 229 customizationVerifier.assertStatusBarColorCorrect(color); 230 customizationVerifier.assertDefaultLogoCorrect(color); 231 customizationVerifier.assertProgressBarColorCorrect(color); 232 233 finishAndWait(); 234 } 235 finishAndWait()236 private void finishAndWait() throws Throwable { 237 Activity activity = mActivityRule.getActivity(); 238 ActivityLifecycleWaiter waiter = new ActivityLifecycleWaiter(activity, Stage.DESTROYED); 239 mActivityRule.runOnUiThread(() -> activity.finish()); 240 waiter.waitForStage(); 241 mActivityRule.afterActivityFinished(); 242 } 243 244 @Test testCustomLogo_profileOwner()245 public void testCustomLogo_profileOwner() throws Throwable { 246 assertCustomLogoCorrect(PROFILE_OWNER_INTENT); 247 } 248 249 @Test testCustomLogo_deviceOwner()250 public void testCustomLogo_deviceOwner() throws Throwable { 251 assertCustomLogoCorrect(PROFILE_OWNER_INTENT); 252 } 253 assertCustomLogoCorrect(Intent intent)254 private void assertCustomLogoCorrect(Intent intent) throws Throwable { 255 UriBitmap targetLogo = UriBitmap.createSimpleInstance(); 256 saveOrganisationLogo(InstrumentationRegistry.getTargetContext(), targetLogo.getUri()); 257 launchActivityAndWait(intent); 258 ProvisioningActivity activity = mActivityRule.getActivity(); 259 new CustomizationVerifier(activity).assertCustomLogoCorrect(targetLogo.getBitmap()); 260 finishAndWait(); 261 } 262 263 @Test testSavedInstanceState()264 public void testSavedInstanceState() throws Throwable { 265 // GIVEN the activity was launched with a profile owner intent 266 launchActivityAndWait(PROFILE_OWNER_INTENT); 267 268 // THEN the provisioning process should be initiated 269 verify(mProvisioningManager).maybeStartProvisioning(PROFILE_OWNER_PARAMS); 270 271 // WHEN the activity is recreated with a saved instance state 272 mActivityRule.runOnUiThread(() -> { 273 Bundle bundle = new Bundle(); 274 InstrumentationRegistry.getInstrumentation() 275 .callActivityOnSaveInstanceState(mActivityRule.getActivity(), bundle); 276 InstrumentationRegistry.getInstrumentation() 277 .callActivityOnCreate(mActivityRule.getActivity(), bundle); 278 }); 279 280 // THEN provisioning should not be initiated again 281 verify(mProvisioningManager).maybeStartProvisioning(PROFILE_OWNER_PARAMS); 282 } 283 284 @Test testPause()285 public void testPause() throws Throwable { 286 // GIVEN the activity was launched with a profile owner intent 287 launchActivityAndWait(PROFILE_OWNER_INTENT); 288 289 // WHEN the activity is paused 290 mActivityRule.runOnUiThread(() -> { 291 InstrumentationRegistry.getInstrumentation() 292 .callActivityOnPause(mActivityRule.getActivity()); 293 }); 294 295 // THEN the listener is unregistered 296 verify(mProvisioningManager).unregisterListener(any(ProvisioningManagerCallback.class)); 297 } 298 299 @Test testErrorNoFactoryReset()300 public void testErrorNoFactoryReset() throws Throwable { 301 // GIVEN the activity was launched with a profile owner intent 302 launchActivityAndWait(PROFILE_OWNER_INTENT); 303 304 // WHEN an error occurred that does not require factory reset 305 final int errorMsgId = R.string.managed_provisioning_error_text; 306 mActivityRule.runOnUiThread(() -> mActivityRule.getActivity().error(R.string.cant_set_up_device, errorMsgId, false)); 307 308 // THEN the UI should show an error dialog 309 onView(withText(errorMsgId)).check(matches(isDisplayed())); 310 311 // WHEN clicking ok 312 onView(withId(android.R.id.button1)) 313 .check(matches(withText(R.string.device_owner_error_ok))) 314 .perform(click()); 315 316 // THEN the activity should be finishing 317 assertTrue(mActivityRule.getActivity().isFinishing()); 318 } 319 320 @Test testErrorFactoryReset()321 public void testErrorFactoryReset() throws Throwable { 322 // GIVEN the activity was launched with a device owner intent 323 launchActivityAndWait(DEVICE_OWNER_INTENT); 324 325 // WHEN an error occurred that does not require factory reset 326 final int errorMsgId = R.string.managed_provisioning_error_text; 327 mActivityRule.runOnUiThread(() -> mActivityRule.getActivity().error(R.string.cant_set_up_device, errorMsgId, true)); 328 329 // THEN the UI should show an error dialog 330 onView(withText(errorMsgId)).check(matches(isDisplayed())); 331 332 // WHEN clicking the ok button that says that factory reset is required 333 onView(withId(android.R.id.button1)) 334 .check(matches(withText(R.string.reset))) 335 .perform(click()); 336 337 // THEN factory reset should be invoked 338 verify(mUtils).sendFactoryResetBroadcast(any(Context.class), anyString()); 339 } 340 341 @Test testCancelProfileOwner()342 public void testCancelProfileOwner() throws Throwable { 343 // GIVEN the activity was launched with a profile owner intent 344 launchActivityAndWait(PROFILE_OWNER_INTENT); 345 346 // WHEN the user tries to cancel 347 pressBack(); 348 349 // THEN the cancel dialog should be shown 350 onView(withText(R.string.profile_owner_cancel_message)).check(matches(isDisplayed())); 351 352 // WHEN deciding not to cancel 353 onView(withId(android.R.id.button2)) 354 .check(matches(withText(R.string.profile_owner_cancel_cancel))) 355 .perform(click()); 356 357 // THEN the activity should not be finished 358 assertFalse(mActivityRule.getActivity().isFinishing()); 359 360 // WHEN the user tries to cancel 361 pressBack(); 362 363 // THEN the cancel dialog should be shown 364 onView(withText(R.string.profile_owner_cancel_message)).check(matches(isDisplayed())); 365 366 // WHEN deciding to cancel 367 onView(withId(android.R.id.button1)) 368 .check(matches(withText(R.string.profile_owner_cancel_ok))) 369 .perform(click()); 370 371 // THEN the manager should be informed 372 verify(mProvisioningManager).cancelProvisioning(); 373 374 // THEN the activity should be finished 375 assertTrue(mActivityRule.getActivity().isFinishing()); 376 } 377 378 @Test testCancelProfileOwner_CompProvisioningWithSkipConsent()379 public void testCancelProfileOwner_CompProvisioningWithSkipConsent() throws Throwable { 380 // GIVEN launching profile intent with skipping user consent 381 ProvisioningParams params = new ProvisioningParams.Builder() 382 .setProvisioningAction(ACTION_PROVISION_MANAGED_PROFILE) 383 .setDeviceAdminComponentName(ADMIN) 384 .setSkipUserConsent(true) 385 .build(); 386 Intent intent = new Intent() 387 .putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, params); 388 launchActivityAndWait(new Intent(intent)); 389 390 // WHEN the user tries to cancel 391 mActivityRule.runOnUiThread(() -> mActivityRule.getActivity().onBackPressed()); 392 393 // THEN never unregistering ProvisioningManager 394 verify(mProvisioningManager, never()).unregisterListener( 395 any(ProvisioningManagerCallback.class)); 396 } 397 398 @Test testCancelProfileOwner_CompProvisioningWithoutSkipConsent()399 public void testCancelProfileOwner_CompProvisioningWithoutSkipConsent() throws Throwable { 400 // GIVEN launching profile intent without skipping user consent 401 ProvisioningParams params = new ProvisioningParams.Builder() 402 .setProvisioningAction(ACTION_PROVISION_MANAGED_PROFILE) 403 .setDeviceAdminComponentName(ADMIN) 404 .setSkipUserConsent(false) 405 .build(); 406 Intent intent = new Intent() 407 .putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, params); 408 launchActivityAndWait(new Intent(intent)); 409 410 // WHEN the user tries to cancel 411 mActivityRule.runOnUiThread(() -> mActivityRule.getActivity().onBackPressed()); 412 413 // THEN unregistering ProvisioningManager 414 verify(mProvisioningManager).unregisterListener(any(ProvisioningManagerCallback.class)); 415 416 // THEN the cancel dialog should be shown 417 onView(withText(R.string.profile_owner_cancel_message)).check(matches(isDisplayed())); 418 } 419 420 @Test testCancelDeviceOwner()421 public void testCancelDeviceOwner() throws Throwable { 422 // GIVEN the activity was launched with a device owner intent 423 launchActivityAndWait(DEVICE_OWNER_INTENT); 424 425 // WHEN the user tries to cancel 426 pressBack(); 427 428 // THEN the cancel dialog should be shown 429 onView(withText(R.string.stop_setup_reset_device_question)).check(matches(isDisplayed())); 430 onView(withText(R.string.this_will_reset_take_back_first_screen)) 431 .check(matches(isDisplayed())); 432 433 // WHEN deciding not to cancel 434 onView(withId(android.R.id.button2)) 435 .check(matches(withText(R.string.device_owner_cancel_cancel))) 436 .perform(click()); 437 438 // THEN the activity should not be finished 439 assertFalse(mActivityRule.getActivity().isFinishing()); 440 441 // WHEN the user tries to cancel 442 pressBack(); 443 444 // THEN the cancel dialog should be shown 445 onView(withText(R.string.stop_setup_reset_device_question)).check(matches(isDisplayed())); 446 447 // WHEN deciding to cancel 448 onView(withId(android.R.id.button1)) 449 .check(matches(withText(R.string.reset))) 450 .perform(click()); 451 452 // THEN factory reset should be invoked 453 verify(mUtils).sendFactoryResetBroadcast(any(Context.class), anyString()); 454 } 455 456 @Test testSuccess()457 public void testSuccess() throws Throwable { 458 // GIVEN the activity was launched with a profile owner intent 459 launchActivityAndWait(PROFILE_OWNER_INTENT); 460 461 // WHEN preFinalization is completed 462 mActivityRule.runOnUiThread(() -> mActivityRule.getActivity().preFinalizationCompleted()); 463 464 // THEN the activity should finish 465 assertTrue(mActivityRule.getActivity().isFinishing()); 466 } 467 468 @Test testSuccess_Nfc()469 public void testSuccess_Nfc() throws Throwable { 470 // GIVEN queryIntentActivities return test_activity 471 ActivityInfo activityInfo = new ActivityInfo(); 472 activityInfo.packageName = TEST_ACTIVITY.getPackageName(); 473 activityInfo.name = TEST_ACTIVITY.getClassName(); 474 activityInfo.permission = permission.BIND_DEVICE_ADMIN; 475 ResolveInfo resolveInfo = new ResolveInfo(); 476 resolveInfo.activityInfo = activityInfo; 477 List<ResolveInfo> resolveInfoList = new ArrayList(); 478 resolveInfoList.add(resolveInfo); 479 when(mPackageManager.queryIntentActivities( 480 MockitoHamcrest.argThat(hasAction(ACTION_STATE_USER_SETUP_COMPLETE)), 481 eq(0))).thenReturn(resolveInfoList); 482 when(mPackageManager.checkPermission(eq(permission.DISPATCH_PROVISIONING_MESSAGE), 483 eq(activityInfo.packageName))).thenReturn(PackageManager.PERMISSION_GRANTED); 484 485 // GIVEN the activity was launched with a nfc intent 486 launchActivityAndWait(NFC_INTENT); 487 488 // WHEN preFinalization is completed 489 mActivityRule.runOnUiThread(() -> mActivityRule.getActivity().preFinalizationCompleted()); 490 // THEN verify starting TEST_ACTIVITY 491 intended(allOf(hasComponent(TEST_ACTIVITY), hasAction(ACTION_STATE_USER_SETUP_COMPLETE))); 492 // THEN the activity should finish 493 assertTrue(mActivityRule.getActivity().isFinishing()); 494 } 495 launchActivityAndWait(Intent intent)496 private void launchActivityAndWait(Intent intent) { 497 mActivityRule.launchActivity(intent); 498 onView(withId(R.id.setup_wizard_layout)); 499 } 500 } 501