1 /* 2 * Copyright (C) 2022 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.intentresolver; 18 19 import static android.testing.PollingCheck.waitFor; 20 21 import static androidx.test.espresso.Espresso.onView; 22 import static androidx.test.espresso.action.ViewActions.click; 23 import static androidx.test.espresso.action.ViewActions.swipeUp; 24 import static androidx.test.espresso.assertion.ViewAssertions.matches; 25 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; 26 import static androidx.test.espresso.matcher.ViewMatchers.isSelected; 27 import static androidx.test.espresso.matcher.ViewMatchers.withId; 28 import static androidx.test.espresso.matcher.ViewMatchers.withText; 29 30 import static com.android.intentresolver.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.NO_BLOCKER; 31 import static com.android.intentresolver.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_ACCESS_BLOCKER; 32 import static com.android.intentresolver.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_SHARE_BLOCKER; 33 import static com.android.intentresolver.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_ACCESS_BLOCKER; 34 import static com.android.intentresolver.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_SHARE_BLOCKER; 35 import static com.android.intentresolver.ChooserActivityWorkProfileTest.TestCase.Tab.PERSONAL; 36 import static com.android.intentresolver.ChooserActivityWorkProfileTest.TestCase.Tab.WORK; 37 import static com.android.intentresolver.ChooserWrapperActivity.sOverrides; 38 39 import static org.hamcrest.CoreMatchers.not; 40 import static org.mockito.ArgumentMatchers.eq; 41 import static org.mockito.Mockito.when; 42 43 import android.companion.DeviceFilter; 44 import android.content.Intent; 45 import android.os.UserHandle; 46 47 import androidx.test.espresso.NoMatchingViewException; 48 import androidx.test.platform.app.InstrumentationRegistry; 49 import androidx.test.rule.ActivityTestRule; 50 51 import com.android.intentresolver.ChooserActivityWorkProfileTest.TestCase.Tab; 52 import com.android.intentresolver.data.repository.FakeUserRepository; 53 import com.android.intentresolver.data.repository.UserRepository; 54 import com.android.intentresolver.data.repository.UserRepositoryModule; 55 import com.android.intentresolver.inject.ApplicationUser; 56 import com.android.intentresolver.inject.ProfileParent; 57 import com.android.intentresolver.shared.model.User; 58 59 import dagger.hilt.android.testing.BindValue; 60 import dagger.hilt.android.testing.HiltAndroidRule; 61 import dagger.hilt.android.testing.HiltAndroidTest; 62 import dagger.hilt.android.testing.UninstallModules; 63 64 import junit.framework.AssertionFailedError; 65 66 import org.junit.Before; 67 import org.junit.Rule; 68 import org.junit.Test; 69 import org.junit.runner.RunWith; 70 import org.junit.runners.Parameterized; 71 import org.mockito.Mockito; 72 73 import java.util.ArrayList; 74 import java.util.Arrays; 75 import java.util.Collection; 76 import java.util.List; 77 78 @DeviceFilter.MediumType 79 @RunWith(Parameterized.class) 80 @HiltAndroidTest 81 @UninstallModules(UserRepositoryModule.class) 82 public class ChooserActivityWorkProfileTest { 83 84 private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry 85 .getInstrumentation().getTargetContext().getUser(); 86 private static final UserHandle WORK_USER_HANDLE = UserHandle.of(10); 87 88 @Rule(order = 0) 89 public HiltAndroidRule mHiltAndroidRule = new HiltAndroidRule(this); 90 91 @Rule(order = 1) 92 public ActivityTestRule<ChooserWrapperActivity> mActivityRule = 93 new ActivityTestRule<>(ChooserWrapperActivity.class, false, 94 false); 95 96 @BindValue 97 @ApplicationUser 98 public final UserHandle mApplicationUser; 99 100 @BindValue 101 @ProfileParent 102 public final UserHandle mProfileParent; 103 104 /** For setup of test state, a mutable reference of mUserRepository */ 105 private final FakeUserRepository mFakeUserRepo = new FakeUserRepository( 106 List.of(new User(PERSONAL_USER_HANDLE.getIdentifier(), User.Role.PERSONAL))); 107 108 @BindValue 109 public final UserRepository mUserRepository; 110 111 private final TestCase mTestCase; 112 ChooserActivityWorkProfileTest(TestCase testCase)113 public ChooserActivityWorkProfileTest(TestCase testCase) { 114 mTestCase = testCase; 115 mApplicationUser = mTestCase.getMyUserHandle(); 116 mProfileParent = PERSONAL_USER_HANDLE; 117 mUserRepository = new FakeUserRepository(List.of( 118 new User(PERSONAL_USER_HANDLE.getIdentifier(), User.Role.PERSONAL), 119 new User(WORK_USER_HANDLE.getIdentifier(), User.Role.WORK))); 120 } 121 122 @Before cleanOverrideData()123 public void cleanOverrideData() { 124 // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the 125 // permissions we require (which we'll read from the manifest at runtime). 126 InstrumentationRegistry 127 .getInstrumentation() 128 .getUiAutomation() 129 .adoptShellPermissionIdentity(); 130 131 sOverrides.reset(); 132 } 133 134 @Test testBlocker()135 public void testBlocker() { 136 setUpPersonalAndWorkComponentInfos(); 137 sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents(); 138 139 launchActivity(mTestCase.getIsSendAction()); 140 switchToTab(mTestCase.getTab()); 141 142 switch (mTestCase.getExpectedBlocker()) { 143 case NO_BLOCKER: 144 assertNoBlockerDisplayed(); 145 break; 146 case PERSONAL_PROFILE_SHARE_BLOCKER: 147 assertCantSharePersonalAppsBlockerDisplayed(); 148 break; 149 case WORK_PROFILE_SHARE_BLOCKER: 150 assertCantShareWorkAppsBlockerDisplayed(); 151 break; 152 case PERSONAL_PROFILE_ACCESS_BLOCKER: 153 assertCantAccessPersonalAppsBlockerDisplayed(); 154 break; 155 case WORK_PROFILE_ACCESS_BLOCKER: 156 assertCantAccessWorkAppsBlockerDisplayed(); 157 break; 158 } 159 } 160 161 @Parameterized.Parameters(name = "{0}") tests()162 public static Collection tests() { 163 return Arrays.asList( 164 new TestCase( 165 /* isSendAction= */ true, 166 /* hasCrossProfileIntents= */ true, 167 /* myUserHandle= */ WORK_USER_HANDLE, 168 /* tab= */ WORK, 169 /* expectedBlocker= */ NO_BLOCKER 170 ), 171 new TestCase( 172 /* isSendAction= */ true, 173 /* hasCrossProfileIntents= */ false, 174 /* myUserHandle= */ WORK_USER_HANDLE, 175 /* tab= */ WORK, 176 /* expectedBlocker= */ NO_BLOCKER 177 ), 178 new TestCase( 179 /* isSendAction= */ true, 180 /* hasCrossProfileIntents= */ true, 181 /* myUserHandle= */ PERSONAL_USER_HANDLE, 182 /* tab= */ WORK, 183 /* expectedBlocker= */ NO_BLOCKER 184 ), 185 new TestCase( 186 /* isSendAction= */ true, 187 /* hasCrossProfileIntents= */ false, 188 /* myUserHandle= */ PERSONAL_USER_HANDLE, 189 /* tab= */ WORK, 190 /* expectedBlocker= */ WORK_PROFILE_SHARE_BLOCKER 191 ), 192 new TestCase( 193 /* isSendAction= */ true, 194 /* hasCrossProfileIntents= */ true, 195 /* myUserHandle= */ WORK_USER_HANDLE, 196 /* tab= */ PERSONAL, 197 /* expectedBlocker= */ NO_BLOCKER 198 ), 199 new TestCase( 200 /* isSendAction= */ true, 201 /* hasCrossProfileIntents= */ false, 202 /* myUserHandle= */ WORK_USER_HANDLE, 203 /* tab= */ PERSONAL, 204 /* expectedBlocker= */ PERSONAL_PROFILE_SHARE_BLOCKER 205 ), 206 new TestCase( 207 /* isSendAction= */ true, 208 /* hasCrossProfileIntents= */ true, 209 /* myUserHandle= */ PERSONAL_USER_HANDLE, 210 /* tab= */ PERSONAL, 211 /* expectedBlocker= */ NO_BLOCKER 212 ), 213 new TestCase( 214 /* isSendAction= */ true, 215 /* hasCrossProfileIntents= */ false, 216 /* myUserHandle= */ PERSONAL_USER_HANDLE, 217 /* tab= */ PERSONAL, 218 /* expectedBlocker= */ NO_BLOCKER 219 ), 220 new TestCase( 221 /* isSendAction= */ false, 222 /* hasCrossProfileIntents= */ true, 223 /* myUserHandle= */ WORK_USER_HANDLE, 224 /* tab= */ WORK, 225 /* expectedBlocker= */ NO_BLOCKER 226 ), 227 new TestCase( 228 /* isSendAction= */ false, 229 /* hasCrossProfileIntents= */ false, 230 /* myUserHandle= */ WORK_USER_HANDLE, 231 /* tab= */ WORK, 232 /* expectedBlocker= */ NO_BLOCKER 233 ), 234 new TestCase( 235 /* isSendAction= */ false, 236 /* hasCrossProfileIntents= */ true, 237 /* myUserHandle= */ PERSONAL_USER_HANDLE, 238 /* tab= */ WORK, 239 /* expectedBlocker= */ NO_BLOCKER 240 ), 241 new TestCase( 242 /* isSendAction= */ false, 243 /* hasCrossProfileIntents= */ false, 244 /* myUserHandle= */ PERSONAL_USER_HANDLE, 245 /* tab= */ WORK, 246 /* expectedBlocker= */ WORK_PROFILE_ACCESS_BLOCKER 247 ), 248 new TestCase( 249 /* isSendAction= */ false, 250 /* hasCrossProfileIntents= */ true, 251 /* myUserHandle= */ WORK_USER_HANDLE, 252 /* tab= */ PERSONAL, 253 /* expectedBlocker= */ NO_BLOCKER 254 ), 255 new TestCase( 256 /* isSendAction= */ false, 257 /* hasCrossProfileIntents= */ false, 258 /* myUserHandle= */ WORK_USER_HANDLE, 259 /* tab= */ PERSONAL, 260 /* expectedBlocker= */ PERSONAL_PROFILE_ACCESS_BLOCKER 261 ), 262 new TestCase( 263 /* isSendAction= */ false, 264 /* hasCrossProfileIntents= */ true, 265 /* myUserHandle= */ PERSONAL_USER_HANDLE, 266 /* tab= */ PERSONAL, 267 /* expectedBlocker= */ NO_BLOCKER 268 ), 269 new TestCase( 270 /* isSendAction= */ false, 271 /* hasCrossProfileIntents= */ false, 272 /* myUserHandle= */ PERSONAL_USER_HANDLE, 273 /* tab= */ PERSONAL, 274 /* expectedBlocker= */ NO_BLOCKER 275 ) 276 ); 277 } 278 createResolvedComponentsForTestWithOtherProfile( int numberOfResults, int userId, UserHandle resolvedForUser)279 private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile( 280 int numberOfResults, int userId, UserHandle resolvedForUser) { 281 List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); 282 for (int i = 0; i < numberOfResults; i++) { 283 infoList.add( 284 ResolverDataProvider 285 .createResolvedComponentInfoWithOtherId(i, userId, resolvedForUser)); 286 } 287 return infoList; 288 } 289 createResolvedComponentsForTest(int numberOfResults, UserHandle resolvedForUser)290 private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults, 291 UserHandle resolvedForUser) { 292 List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); 293 for (int i = 0; i < numberOfResults; i++) { 294 infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser)); 295 } 296 return infoList; 297 } 298 setUpPersonalAndWorkComponentInfos()299 private void setUpPersonalAndWorkComponentInfos() { 300 int workProfileTargets = 4; 301 List<ResolvedComponentInfo> personalResolvedComponentInfos = 302 createResolvedComponentsForTestWithOtherProfile(3, 303 /* userId */ WORK_USER_HANDLE.getIdentifier(), PERSONAL_USER_HANDLE); 304 List<ResolvedComponentInfo> workResolvedComponentInfos = 305 createResolvedComponentsForTest(workProfileTargets, WORK_USER_HANDLE); 306 setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); 307 } 308 setupResolverControllers( List<ResolvedComponentInfo> personalResolvedComponentInfos, List<ResolvedComponentInfo> workResolvedComponentInfos)309 private void setupResolverControllers( 310 List<ResolvedComponentInfo> personalResolvedComponentInfos, 311 List<ResolvedComponentInfo> workResolvedComponentInfos) { 312 when(sOverrides.resolverListController.getResolversForIntentAsUser( 313 Mockito.anyBoolean(), 314 Mockito.anyBoolean(), 315 Mockito.anyBoolean(), 316 Mockito.isA(List.class), 317 eq(UserHandle.SYSTEM))) 318 .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); 319 when(sOverrides.workResolverListController.getResolversForIntentAsUser( 320 Mockito.anyBoolean(), 321 Mockito.anyBoolean(), 322 Mockito.anyBoolean(), 323 Mockito.isA(List.class), 324 eq(UserHandle.SYSTEM))) 325 .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); 326 when(sOverrides.workResolverListController.getResolversForIntentAsUser( 327 Mockito.anyBoolean(), 328 Mockito.anyBoolean(), 329 Mockito.anyBoolean(), 330 Mockito.isA(List.class), 331 eq(WORK_USER_HANDLE))) 332 .thenReturn(new ArrayList<>(workResolvedComponentInfos)); 333 } 334 waitForIdle()335 private void waitForIdle() { 336 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 337 } 338 assertCantAccessWorkAppsBlockerDisplayed()339 private void assertCantAccessWorkAppsBlockerDisplayed() { 340 onView(withText(R.string.resolver_cross_profile_blocked)) 341 .check(matches(isDisplayed())); 342 onView(withText(R.string.resolver_cant_access_work_apps_explanation)) 343 .check(matches(isDisplayed())); 344 } 345 assertCantAccessPersonalAppsBlockerDisplayed()346 private void assertCantAccessPersonalAppsBlockerDisplayed() { 347 onView(withText(R.string.resolver_cross_profile_blocked)) 348 .check(matches(isDisplayed())); 349 onView(withText(R.string.resolver_cant_access_personal_apps_explanation)) 350 .check(matches(isDisplayed())); 351 } 352 assertCantShareWorkAppsBlockerDisplayed()353 private void assertCantShareWorkAppsBlockerDisplayed() { 354 onView(withText(R.string.resolver_cross_profile_blocked)) 355 .check(matches(isDisplayed())); 356 onView(withText(R.string.resolver_cant_share_with_work_apps_explanation)) 357 .check(matches(isDisplayed())); 358 } 359 assertCantSharePersonalAppsBlockerDisplayed()360 private void assertCantSharePersonalAppsBlockerDisplayed() { 361 onView(withText(R.string.resolver_cross_profile_blocked)) 362 .check(matches(isDisplayed())); 363 onView(withText(R.string.resolver_cant_share_with_personal_apps_explanation)) 364 .check(matches(isDisplayed())); 365 } 366 assertNoBlockerDisplayed()367 private void assertNoBlockerDisplayed() { 368 try { 369 onView(withText(R.string.resolver_cross_profile_blocked)) 370 .check(matches(not(isDisplayed()))); 371 } catch (NoMatchingViewException ignored) { 372 } 373 } 374 switchToTab(Tab tab)375 private void switchToTab(Tab tab) { 376 final int stringId = tab == Tab.WORK ? R.string.resolver_work_tab 377 : R.string.resolver_personal_tab; 378 379 waitFor(() -> { 380 onView(withText(stringId)).perform(click()); 381 waitForIdle(); 382 383 try { 384 onView(withText(stringId)).check(matches(isSelected())); 385 return true; 386 } catch (AssertionFailedError e) { 387 return false; 388 } 389 }); 390 391 onView(withId(com.android.internal.R.id.contentPanel)) 392 .perform(swipeUp()); 393 waitForIdle(); 394 } 395 createTextIntent(boolean isSendAction)396 private Intent createTextIntent(boolean isSendAction) { 397 Intent sendIntent = new Intent(); 398 if (isSendAction) { 399 sendIntent.setAction(Intent.ACTION_SEND); 400 } 401 sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); 402 sendIntent.setType("text/plain"); 403 return sendIntent; 404 } 405 launchActivity(boolean isSendAction)406 private void launchActivity(boolean isSendAction) { 407 Intent sendIntent = createTextIntent(isSendAction); 408 mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Test")); 409 waitForIdle(); 410 } 411 412 public static class TestCase { 413 private final boolean mIsSendAction; 414 private final boolean mHasCrossProfileIntents; 415 private final UserHandle mMyUserHandle; 416 private final Tab mTab; 417 private final ExpectedBlocker mExpectedBlocker; 418 419 public enum ExpectedBlocker { 420 NO_BLOCKER, 421 PERSONAL_PROFILE_SHARE_BLOCKER, 422 WORK_PROFILE_SHARE_BLOCKER, 423 PERSONAL_PROFILE_ACCESS_BLOCKER, 424 WORK_PROFILE_ACCESS_BLOCKER 425 } 426 427 public enum Tab { 428 WORK, 429 PERSONAL 430 } 431 TestCase(boolean isSendAction, boolean hasCrossProfileIntents, UserHandle myUserHandle, Tab tab, ExpectedBlocker expectedBlocker)432 public TestCase(boolean isSendAction, boolean hasCrossProfileIntents, 433 UserHandle myUserHandle, Tab tab, ExpectedBlocker expectedBlocker) { 434 mIsSendAction = isSendAction; 435 mHasCrossProfileIntents = hasCrossProfileIntents; 436 mMyUserHandle = myUserHandle; 437 mTab = tab; 438 mExpectedBlocker = expectedBlocker; 439 } 440 getIsSendAction()441 public boolean getIsSendAction() { 442 return mIsSendAction; 443 } 444 hasCrossProfileIntents()445 public boolean hasCrossProfileIntents() { 446 return mHasCrossProfileIntents; 447 } 448 getMyUserHandle()449 public UserHandle getMyUserHandle() { 450 return mMyUserHandle; 451 } 452 getTab()453 public Tab getTab() { 454 return mTab; 455 } 456 getExpectedBlocker()457 public ExpectedBlocker getExpectedBlocker() { 458 return mExpectedBlocker; 459 } 460 461 @Override toString()462 public String toString() { 463 StringBuilder result = new StringBuilder("test"); 464 465 if (mTab == WORK) { 466 result.append("WorkTab_"); 467 } else { 468 result.append("PersonalTab_"); 469 } 470 471 if (mIsSendAction) { 472 result.append("sendAction_"); 473 } else { 474 result.append("notSendAction_"); 475 } 476 477 if (mHasCrossProfileIntents) { 478 result.append("hasCrossProfileIntents_"); 479 } else { 480 result.append("doesNotHaveCrossProfileIntents_"); 481 } 482 483 if (mMyUserHandle.equals(PERSONAL_USER_HANDLE)) { 484 result.append("myUserIsPersonal_"); 485 } else { 486 result.append("myUserIsWork_"); 487 } 488 489 if (mExpectedBlocker == ExpectedBlocker.NO_BLOCKER) { 490 result.append("thenNoBlocker"); 491 } else if (mExpectedBlocker == PERSONAL_PROFILE_ACCESS_BLOCKER) { 492 result.append("thenAccessBlockerOnPersonalProfile"); 493 } else if (mExpectedBlocker == PERSONAL_PROFILE_SHARE_BLOCKER) { 494 result.append("thenShareBlockerOnPersonalProfile"); 495 } else if (mExpectedBlocker == WORK_PROFILE_ACCESS_BLOCKER) { 496 result.append("thenAccessBlockerOnWorkProfile"); 497 } else if (mExpectedBlocker == WORK_PROFILE_SHARE_BLOCKER) { 498 result.append("thenShareBlockerOnWorkProfile"); 499 } 500 501 return result.toString(); 502 } 503 } 504 } 505