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