1 /* 2 * Copyright (C) 2017 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.settingslib.applications; 18 19 import static android.os.UserHandle.MU_ENABLED; 20 import static android.os.UserHandle.USER_SYSTEM; 21 22 import static com.google.common.truth.Truth.assertThat; 23 24 import static org.mockito.ArgumentMatchers.eq; 25 import static org.mockito.Mockito.any; 26 import static org.mockito.Mockito.anyInt; 27 import static org.mockito.Mockito.anyLong; 28 import static org.mockito.Mockito.anyString; 29 import static org.mockito.Mockito.mock; 30 import static org.mockito.Mockito.never; 31 import static org.mockito.Mockito.spy; 32 import static org.mockito.Mockito.verify; 33 import static org.mockito.Mockito.when; 34 import static org.robolectric.shadow.api.Shadow.extract; 35 36 import android.annotation.UserIdInt; 37 import android.app.Application; 38 import android.app.ApplicationPackageManager; 39 import android.app.usage.StorageStats; 40 import android.app.usage.StorageStatsManager; 41 import android.content.ComponentName; 42 import android.content.Context; 43 import android.content.Intent; 44 import android.content.IntentFilter; 45 import android.content.pm.ActivityInfo; 46 import android.content.pm.ApplicationInfo; 47 import android.content.pm.IPackageManager; 48 import android.content.pm.ModuleInfo; 49 import android.content.pm.PackageManager; 50 import android.content.pm.ParceledListSlice; 51 import android.content.pm.ResolveInfo; 52 import android.content.pm.UserProperties; 53 import android.content.res.Resources; 54 import android.graphics.drawable.ColorDrawable; 55 import android.graphics.drawable.Drawable; 56 import android.os.Build; 57 import android.os.Handler; 58 import android.os.RemoteException; 59 import android.os.UserHandle; 60 import android.os.UserManager; 61 import android.text.TextUtils; 62 import android.util.IconDrawableFactory; 63 64 import androidx.test.core.app.ApplicationProvider; 65 66 import com.android.settingslib.applications.ApplicationsState.AppEntry; 67 import com.android.settingslib.applications.ApplicationsState.Callbacks; 68 import com.android.settingslib.applications.ApplicationsState.Session; 69 import com.android.settingslib.testutils.shadow.ShadowUserManager; 70 71 import org.junit.After; 72 import org.junit.Before; 73 import org.junit.Test; 74 import org.junit.runner.RunWith; 75 import org.mockito.ArgumentCaptor; 76 import org.mockito.Captor; 77 import org.mockito.Mock; 78 import org.mockito.MockitoAnnotations; 79 import org.mockito.Spy; 80 import org.robolectric.RobolectricTestRunner; 81 import org.robolectric.RuntimeEnvironment; 82 import org.robolectric.annotation.Config; 83 import org.robolectric.annotation.Implementation; 84 import org.robolectric.annotation.Implements; 85 import org.robolectric.shadow.api.Shadow; 86 import org.robolectric.shadows.ShadowContextImpl; 87 import org.robolectric.shadows.ShadowLooper; 88 import org.robolectric.util.ReflectionHelpers; 89 90 import java.util.ArrayList; 91 import java.util.Arrays; 92 import java.util.HashMap; 93 import java.util.List; 94 import java.util.UUID; 95 96 @RunWith(RobolectricTestRunner.class) 97 @Config(shadows = {ShadowUserManager.class, 98 ApplicationsStateRoboTest.ShadowIconDrawableFactory.class, 99 ApplicationsStateRoboTest.ShadowPackageManager.class}) 100 public class ApplicationsStateRoboTest { 101 102 private final static String HOME_PACKAGE_NAME = "com.android.home"; 103 private final static String LAUNCHABLE_PACKAGE_NAME = "com.android.launchable"; 104 105 private static final int PROFILE_USERID = 10; 106 private static final int PROFILE_USERID2 = 11; 107 108 private static final String PKG_1 = "PKG1"; 109 private static final int OWNER_UID_1 = 1001; 110 private static final int PROFILE_UID_1 = UserHandle.getUid(PROFILE_USERID, OWNER_UID_1); 111 112 private static final String PKG_2 = "PKG2"; 113 private static final int OWNER_UID_2 = 1002; 114 private static final int PROFILE_UID_2 = UserHandle.getUid(PROFILE_USERID, OWNER_UID_2); 115 116 private static final String PKG_3 = "PKG3"; 117 private static final int OWNER_UID_3 = 1003; 118 private static final int PROFILE_UID_3 = UserHandle.getUid(PROFILE_USERID2, OWNER_UID_3); 119 120 private static final String CLONE_USER = "clone_user"; 121 private static final String RANDOM_USER = "random_user"; 122 123 /** Class under test */ 124 private ApplicationsState mApplicationsState; 125 private Session mSession; 126 127 private Application mApplication; 128 129 @Spy 130 Context mContext = ApplicationProvider.getApplicationContext(); 131 @Mock 132 private Callbacks mCallbacks; 133 @Captor 134 private ArgumentCaptor<ArrayList<AppEntry>> mAppEntriesCaptor; 135 @Mock 136 private StorageStatsManager mStorageStatsManager; 137 @Mock 138 private IPackageManager mPackageManagerService; 139 140 @Implements(value = IconDrawableFactory.class) 141 public static class ShadowIconDrawableFactory { 142 143 @Implementation getBadgedIcon(ApplicationInfo appInfo)144 protected Drawable getBadgedIcon(ApplicationInfo appInfo) { 145 return new ColorDrawable(0); 146 } 147 } 148 149 @Implements(value = ApplicationPackageManager.class) 150 public static class ShadowPackageManager extends 151 org.robolectric.shadows.ShadowApplicationPackageManager { 152 153 // test installed modules, 2 regular, 2 hidden 154 private final String[] mModuleNames = { 155 "test.module.1", "test.hidden.module.2", "test.hidden.module.3", "test.module.4"}; 156 private final List<ModuleInfo> mInstalledModules = new ArrayList<>(); 157 158 @Implementation getHomeActivities(List<ResolveInfo> outActivities)159 protected ComponentName getHomeActivities(List<ResolveInfo> outActivities) { 160 ResolveInfo resolveInfo = new ResolveInfo(); 161 resolveInfo.activityInfo = new ActivityInfo(); 162 resolveInfo.activityInfo.packageName = HOME_PACKAGE_NAME; 163 resolveInfo.activityInfo.enabled = true; 164 outActivities.add(resolveInfo); 165 return ComponentName.createRelative(resolveInfo.activityInfo.packageName, "foo"); 166 } 167 168 @Implementation getInstalledModules(int flags)169 public List<ModuleInfo> getInstalledModules(int flags) { 170 if (mInstalledModules.isEmpty()) { 171 for (String moduleName : mModuleNames) { 172 mInstalledModules.add(createModuleInfo(moduleName)); 173 } 174 } 175 return mInstalledModules; 176 } 177 queryIntentActivitiesAsUser(Intent intent, @PackageManager.ResolveInfoFlagsBits int flags, @UserIdInt int userId)178 public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, 179 @PackageManager.ResolveInfoFlagsBits int flags, @UserIdInt int userId) { 180 List<ResolveInfo> resolveInfos = new ArrayList<>(); 181 ResolveInfo resolveInfo = new ResolveInfo(); 182 resolveInfo.activityInfo = new ActivityInfo(); 183 resolveInfo.activityInfo.packageName = LAUNCHABLE_PACKAGE_NAME; 184 resolveInfo.activityInfo.enabled = true; 185 resolveInfo.filter = new IntentFilter(); 186 resolveInfo.filter.addCategory(Intent.CATEGORY_LAUNCHER); 187 resolveInfos.add(resolveInfo); 188 return resolveInfos; 189 } 190 createModuleInfo(String packageName)191 private ModuleInfo createModuleInfo(String packageName) { 192 final ModuleInfo info = new ModuleInfo(); 193 info.setName(packageName); 194 info.setPackageName(packageName); 195 // will treat any app with package name that contains "hidden" as hidden module 196 info.setHidden(!TextUtils.isEmpty(packageName) && packageName.contains("hidden")); 197 return info; 198 } 199 } 200 201 @Before setUp()202 public void setUp() throws Exception { 203 MockitoAnnotations.initMocks(this); 204 205 // Robolectric does not know about the StorageStatsManager as a system service. 206 // Registering a mock of this service as a replacement. 207 ShadowContextImpl shadowContext = Shadow.extract( 208 RuntimeEnvironment.application.getBaseContext()); 209 shadowContext.setSystemService(Context.STORAGE_STATS_SERVICE, mStorageStatsManager); 210 mApplication = spy(RuntimeEnvironment.application); 211 StorageStats storageStats = new StorageStats(); 212 storageStats.codeBytes = 10; 213 storageStats.cacheBytes = 30; 214 // Data bytes are a superset of cache bytes. 215 storageStats.dataBytes = storageStats.cacheBytes + 20; 216 when(mStorageStatsManager.queryStatsForPackage(any(UUID.class), 217 anyString(), any(UserHandle.class))).thenReturn(storageStats); 218 219 // Set up 3 installed apps, in which 1 is hidden module 220 final List<ApplicationInfo> infos = new ArrayList<>(); 221 infos.add(createApplicationInfo("test.package.1")); 222 infos.add(createApplicationInfo("test.hidden.module.2")); 223 infos.add(createApplicationInfo("test.package.3")); 224 when(mPackageManagerService.getInstalledApplications( 225 anyLong() /* flags */, anyInt() /* userId */)).thenReturn(new ParceledListSlice(infos)); 226 227 ApplicationsState.sInstance = null; 228 mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService); 229 mApplicationsState.clearEntries(); 230 231 mSession = mApplicationsState.newSession(mCallbacks); 232 } 233 234 @After tearDown()235 public void tearDown() { 236 mSession.onDestroy(); 237 } 238 createApplicationInfo(String packageName)239 private ApplicationInfo createApplicationInfo(String packageName) { 240 return createApplicationInfo(packageName, 0); 241 } 242 createApplicationInfo(String packageName, int uid)243 private ApplicationInfo createApplicationInfo(String packageName, int uid) { 244 ApplicationInfo appInfo = new ApplicationInfo(); 245 appInfo.sourceDir = "foo"; 246 appInfo.flags |= ApplicationInfo.FLAG_INSTALLED; 247 appInfo.storageUuid = UUID.randomUUID(); 248 appInfo.packageName = packageName; 249 appInfo.uid = uid; 250 return appInfo; 251 } 252 createAppEntry(ApplicationInfo appInfo, int id)253 private AppEntry createAppEntry(ApplicationInfo appInfo, int id) { 254 AppEntry appEntry = new AppEntry(RuntimeEnvironment.application, appInfo, id); 255 appEntry.label = "label"; 256 appEntry.mounted = true; 257 return appEntry; 258 } 259 addApp(String packageName, int id)260 private void addApp(String packageName, int id) { 261 addApp(packageName, id, 0); 262 } 263 addApp(String packageName, int id, int userId)264 private void addApp(String packageName, int id, int userId) { 265 ApplicationInfo appInfo = createApplicationInfo(packageName, id); 266 AppEntry appEntry = createAppEntry(appInfo, id); 267 mApplicationsState.mAppEntries.add(appEntry); 268 mApplicationsState.mEntriesMap.get(userId).put(appInfo.packageName, appEntry); 269 } 270 processAllMessages()271 private void processAllMessages() { 272 Handler mainHandler = mApplicationsState.mMainHandler; 273 Handler bkgHandler = mApplicationsState.mBackgroundHandler; 274 ShadowLooper shadowBkgLooper = extract(bkgHandler.getLooper()); 275 ShadowLooper shadowMainLooper = extract(mainHandler.getLooper()); 276 shadowBkgLooper.idle(); 277 shadowMainLooper.idle(); 278 } 279 findAppEntry(List<AppEntry> appEntries, long id)280 private AppEntry findAppEntry(List<AppEntry> appEntries, long id) { 281 for (AppEntry appEntry : appEntries) { 282 if (appEntry.id == id) { 283 return appEntry; 284 } 285 } 286 return null; 287 } 288 289 @Test testDefaultSession_isResumed_LoadsAll()290 public void testDefaultSession_isResumed_LoadsAll() { 291 mSession.onResume(); 292 293 addApp(HOME_PACKAGE_NAME, 1); 294 addApp(LAUNCHABLE_PACKAGE_NAME, 2); 295 mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); 296 processAllMessages(); 297 verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); 298 299 List<AppEntry> appEntries = mAppEntriesCaptor.getValue(); 300 assertThat(appEntries.size()).isEqualTo(2); 301 302 for (AppEntry appEntry : appEntries) { 303 assertThat(appEntry.size).isGreaterThan(0L); 304 assertThat(appEntry.icon).isNotNull(); 305 } 306 307 AppEntry homeEntry = findAppEntry(appEntries, 1); 308 assertThat(homeEntry.isHomeApp).isTrue(); 309 assertThat(homeEntry.hasLauncherEntry).isFalse(); 310 311 AppEntry launchableEntry = findAppEntry(appEntries, 2); 312 assertThat(launchableEntry.hasLauncherEntry).isTrue(); 313 assertThat(launchableEntry.launcherEntryEnabled).isTrue(); 314 } 315 316 @Test testDefaultSession_isPaused_NotLoadsAll()317 public void testDefaultSession_isPaused_NotLoadsAll() { 318 mSession.onResume(); 319 320 addApp(HOME_PACKAGE_NAME, 1); 321 addApp(LAUNCHABLE_PACKAGE_NAME, 2); 322 mSession.mResumed = false; 323 mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); 324 processAllMessages(); 325 326 verify(mCallbacks, never()).onRebuildComplete(mAppEntriesCaptor.capture()); 327 } 328 329 @Test testCustomSessionLoadsIconsOnly()330 public void testCustomSessionLoadsIconsOnly() { 331 mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_ICONS); 332 mSession.onResume(); 333 334 addApp(LAUNCHABLE_PACKAGE_NAME, 1); 335 mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); 336 processAllMessages(); 337 verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); 338 339 List<AppEntry> appEntries = mAppEntriesCaptor.getValue(); 340 assertThat(appEntries.size()).isEqualTo(1); 341 342 AppEntry launchableEntry = findAppEntry(appEntries, 1); 343 assertThat(launchableEntry.icon).isNotNull(); 344 assertThat(launchableEntry.size).isEqualTo(-1); 345 assertThat(launchableEntry.hasLauncherEntry).isFalse(); 346 } 347 348 @Test testCustomSessionLoadsSizesOnly()349 public void testCustomSessionLoadsSizesOnly() { 350 mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_SIZES); 351 mSession.onResume(); 352 353 addApp(LAUNCHABLE_PACKAGE_NAME, 1); 354 mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); 355 processAllMessages(); 356 verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); 357 358 List<AppEntry> appEntries = mAppEntriesCaptor.getValue(); 359 assertThat(appEntries.size()).isEqualTo(1); 360 361 AppEntry launchableEntry = findAppEntry(appEntries, 1); 362 assertThat(launchableEntry.hasLauncherEntry).isFalse(); 363 assertThat(launchableEntry.size).isGreaterThan(0L); 364 } 365 366 @Test testCustomSessionLoadsHomeOnly()367 public void testCustomSessionLoadsHomeOnly() { 368 mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_HOME_APP); 369 mSession.onResume(); 370 371 addApp(HOME_PACKAGE_NAME, 1); 372 mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); 373 processAllMessages(); 374 verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); 375 376 List<AppEntry> appEntries = mAppEntriesCaptor.getValue(); 377 assertThat(appEntries.size()).isEqualTo(1); 378 379 AppEntry launchableEntry = findAppEntry(appEntries, 1); 380 assertThat(launchableEntry.hasLauncherEntry).isFalse(); 381 assertThat(launchableEntry.size).isEqualTo(-1); 382 assertThat(launchableEntry.isHomeApp).isTrue(); 383 } 384 385 @Test testCustomSessionLoadsLeanbackOnly()386 public void testCustomSessionLoadsLeanbackOnly() { 387 mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER); 388 mSession.onResume(); 389 390 addApp(LAUNCHABLE_PACKAGE_NAME, 1); 391 mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); 392 processAllMessages(); 393 verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); 394 395 List<AppEntry> appEntries = mAppEntriesCaptor.getValue(); 396 assertThat(appEntries.size()).isEqualTo(1); 397 398 AppEntry launchableEntry = findAppEntry(appEntries, 1); 399 assertThat(launchableEntry.size).isEqualTo(-1); 400 assertThat(launchableEntry.isHomeApp).isFalse(); 401 assertThat(launchableEntry.hasLauncherEntry).isTrue(); 402 assertThat(launchableEntry.launcherEntryEnabled).isTrue(); 403 } 404 405 @Test onResume_shouldNotIncludeSystemHiddenModule()406 public void onResume_shouldNotIncludeSystemHiddenModule() { 407 mSession.onResume(); 408 409 final List<ApplicationInfo> mApplications = mApplicationsState.mApplications; 410 assertThat(mApplications).hasSize(2); 411 assertThat(mApplications.get(0).packageName).isEqualTo("test.package.1"); 412 assertThat(mApplications.get(1).packageName).isEqualTo("test.package.3"); 413 } 414 415 @Test removeAndInstall_noWorkprofile_doResumeIfNeededLocked_shouldClearEntries()416 public void removeAndInstall_noWorkprofile_doResumeIfNeededLocked_shouldClearEntries() 417 throws RemoteException { 418 // scenario: only owner user 419 // (PKG_1, PKG_2) -> (PKG_2, PKG_3) 420 // PKG_1 is removed and PKG_3 is installed before app is resumed. 421 ApplicationsState.sInstance = null; 422 mApplicationsState = spy( 423 ApplicationsState 424 .getInstance(RuntimeEnvironment.application, mock(IPackageManager.class))); 425 426 // Previous Applications: 427 ApplicationInfo appInfo; 428 final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>(); 429 appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); 430 prevAppList.add(appInfo); 431 appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); 432 prevAppList.add(appInfo); 433 mApplicationsState.mApplications = prevAppList; 434 435 // Previous Entries: 436 // (PKG_1, PKG_2) 437 addApp(PKG_1, OWNER_UID_1, 0); 438 addApp(PKG_2, OWNER_UID_2, 0); 439 440 // latest Applications: 441 // (PKG_2, PKG_3) 442 final ArrayList<ApplicationInfo> appList = new ArrayList<>(); 443 appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); 444 appList.add(appInfo); 445 appInfo = createApplicationInfo(PKG_3, OWNER_UID_3); 446 appList.add(appInfo); 447 setupDoResumeIfNeededLocked(appList, null); 448 449 mApplicationsState.doResumeIfNeededLocked(); 450 451 verify(mApplicationsState).clearEntries(); 452 } 453 454 @Test noAppRemoved_noWorkprofile_doResumeIfNeededLocked_shouldNotClearEntries()455 public void noAppRemoved_noWorkprofile_doResumeIfNeededLocked_shouldNotClearEntries() 456 throws RemoteException { 457 // scenario: only owner user 458 // (PKG_1, PKG_2) 459 ApplicationsState.sInstance = null; 460 mApplicationsState = spy( 461 ApplicationsState 462 .getInstance(RuntimeEnvironment.application, mock(IPackageManager.class))); 463 464 ApplicationInfo appInfo; 465 // Previous Applications 466 final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>(); 467 appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); 468 prevAppList.add(appInfo); 469 appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); 470 prevAppList.add(appInfo); 471 mApplicationsState.mApplications = prevAppList; 472 473 // Previous Entries: 474 // (pk1, PKG_2) 475 addApp(PKG_1, OWNER_UID_1, 0); 476 addApp(PKG_2, OWNER_UID_2, 0); 477 478 // latest Applications: 479 // (PKG_2, PKG_3) 480 final ArrayList<ApplicationInfo> appList = new ArrayList<>(); 481 appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); 482 appList.add(appInfo); 483 appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); 484 appList.add(appInfo); 485 setupDoResumeIfNeededLocked(appList, null); 486 487 mApplicationsState.doResumeIfNeededLocked(); 488 489 verify(mApplicationsState, never()).clearEntries(); 490 } 491 492 @Test removeProfileApp_workprofileExists_doResumeIfNeededLocked_shouldClearEntries()493 public void removeProfileApp_workprofileExists_doResumeIfNeededLocked_shouldClearEntries() 494 throws RemoteException { 495 if (!MU_ENABLED) { 496 return; 497 } 498 // [Preconditions] 499 // 2 apps (PKG_1, PKG_2) for owner, PKG_1 is not in installed state 500 // 2 apps (PKG_1, PKG_2) for non-owner. 501 // 502 // [Actions] 503 // profile user's PKG_2 is removed before resume 504 // 505 // Applications: 506 // owner - (PKG_1 - uninstalled, PKG_2) -> (PKG_1 - uninstalled, PKG_2) 507 // profile - (PKG_1, PKG_2) -> (PKG_1) 508 // 509 // Previous Entries: 510 // owner - (PKG_2) 511 // profile - (PKG_1, PKG_2) 512 513 ShadowUserManager shadowUserManager = Shadow 514 .extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); 515 shadowUserManager.addProfile(PROFILE_USERID, "profile"); 516 517 ApplicationsState.sInstance = null; 518 mApplicationsState = spy( 519 ApplicationsState 520 .getInstance(RuntimeEnvironment.application, mock(IPackageManager.class))); 521 522 ApplicationInfo appInfo; 523 // Previous Applications 524 // owner - (PKG_1 - uninstalled, PKG_2) 525 // profile - (PKG_1, PKG_2) 526 final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>(); 527 appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); 528 appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED; 529 prevAppList.add(appInfo); 530 appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); 531 prevAppList.add(appInfo); 532 533 appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1); 534 prevAppList.add(appInfo); 535 appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2); 536 prevAppList.add(appInfo); 537 538 mApplicationsState.mApplications = prevAppList; 539 // Previous Entries: 540 // owner (PKG_2), profile (pk1, PKG_2) 541 // PKG_1 is not installed for owner, hence it's removed from entries 542 addApp(PKG_2, OWNER_UID_2, 0); 543 addApp(PKG_1, PROFILE_UID_1, PROFILE_USERID); 544 addApp(PKG_2, PROFILE_UID_2, PROFILE_USERID); 545 546 // latest Applications: 547 // owner (PKG_1, PKG_2), profile (PKG_1) 548 // owner's PKG_1 is still listed and is in non-installed state 549 // profile user's PKG_2 is removed by a user before resume 550 //owner 551 final ArrayList<ApplicationInfo> ownerAppList = new ArrayList<>(); 552 appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); 553 appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED; 554 ownerAppList.add(appInfo); 555 appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); 556 ownerAppList.add(appInfo); 557 //profile 558 appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1); 559 setupDoResumeIfNeededLocked(ownerAppList, new ArrayList<>(Arrays.asList(appInfo))); 560 561 mApplicationsState.doResumeIfNeededLocked(); 562 563 verify(mApplicationsState).clearEntries(); 564 } 565 566 @Test removeOwnerApp_workprofileExists_doResumeIfNeededLocked_shouldClearEntries()567 public void removeOwnerApp_workprofileExists_doResumeIfNeededLocked_shouldClearEntries() 568 throws RemoteException { 569 if (!MU_ENABLED) { 570 return; 571 } 572 // [Preconditions] 573 // 2 apps (PKG_1, PKG_2) for owner, PKG_1 is not in installed state 574 // 2 apps (PKG_1, PKG_2) for non-owner. 575 // 576 // [Actions] 577 // Owner user's PKG_2 is removed before resume 578 // 579 // Applications: 580 // owner - (PKG_1 - uninstalled, PKG_2) -> (PKG_1 - uninstalled, PKG_2 - uninstalled) 581 // profile - (PKG_1, PKG_2) -> (PKG_1, PKG_2) 582 // 583 // Previous Entries: 584 // owner - (PKG_2) 585 // profile - (PKG_1, PKG_2) 586 587 ShadowUserManager shadowUserManager = Shadow 588 .extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); 589 shadowUserManager.addProfile(PROFILE_USERID, "profile"); 590 591 ApplicationsState.sInstance = null; 592 mApplicationsState = spy( 593 ApplicationsState 594 .getInstance(RuntimeEnvironment.application, mock(IPackageManager.class))); 595 596 ApplicationInfo appInfo; 597 // Previous Applications: 598 // owner - (PKG_1 - uninstalled, PKG_2) 599 // profile - (PKG_1, PKG_2) 600 final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>(); 601 appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); 602 appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED; 603 prevAppList.add(appInfo); 604 appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); 605 prevAppList.add(appInfo); 606 607 appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1); 608 prevAppList.add(appInfo); 609 appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2); 610 prevAppList.add(appInfo); 611 612 mApplicationsState.mApplications = prevAppList; 613 614 // Previous Entries: 615 // owner (PKG_2), profile (pk1, PKG_2) 616 // PKG_1 is not installed for owner, hence it's removed from entries 617 addApp(PKG_2, OWNER_UID_2, 0); 618 addApp(PKG_1, PROFILE_UID_1, PROFILE_USERID); 619 addApp(PKG_2, PROFILE_UID_2, PROFILE_USERID); 620 621 // latest Applications: 622 // owner (PKG_1 - uninstalled, PKG_2 - uninstalled), profile (PKG_1, PKG_2) 623 // owner's PKG_1, PKG_2 is still listed and is in non-installed state 624 // profile user's PKG_2 is removed before resume 625 //owner 626 final ArrayList<ApplicationInfo> ownerAppList = new ArrayList<>(); 627 appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); 628 appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED; 629 ownerAppList.add(appInfo); 630 appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); 631 appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED; 632 ownerAppList.add(appInfo); 633 634 //profile 635 final ArrayList<ApplicationInfo> profileAppList = new ArrayList<>(); 636 appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1); 637 profileAppList.add(appInfo); 638 appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2); 639 profileAppList.add(appInfo); 640 setupDoResumeIfNeededLocked(ownerAppList, profileAppList); 641 642 mApplicationsState.doResumeIfNeededLocked(); 643 644 verify(mApplicationsState).clearEntries(); 645 } 646 647 @Test noAppRemoved_workprofileExists_doResumeIfNeededLocked_shouldNotClearEntries()648 public void noAppRemoved_workprofileExists_doResumeIfNeededLocked_shouldNotClearEntries() 649 throws RemoteException { 650 if (!MU_ENABLED) { 651 return; 652 } 653 // [Preconditions] 654 // 2 apps (PKG_1, PKG_2) for owner, PKG_1 is not in installed state 655 // 2 apps (PKG_1, PKG_2) for non-owner. 656 // 657 // Applications: 658 // owner - (PKG_1 - uninstalled, PKG_2) 659 // profile - (PKG_1, PKG_2) 660 // 661 // Previous Entries: 662 // owner - (PKG_2) 663 // profile - (PKG_1, PKG_2) 664 665 ShadowUserManager shadowUserManager = Shadow 666 .extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); 667 shadowUserManager.addProfile(PROFILE_USERID, "profile"); 668 669 ApplicationsState.sInstance = null; 670 mApplicationsState = spy( 671 ApplicationsState 672 .getInstance(RuntimeEnvironment.application, mock(IPackageManager.class))); 673 674 ApplicationInfo appInfo; 675 // Previous Applications: 676 // owner - (PKG_1 - uninstalled, PKG_2) 677 // profile - (PKG_1, PKG_2) 678 final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>(); 679 appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); 680 appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED; 681 prevAppList.add(appInfo); 682 appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); 683 prevAppList.add(appInfo); 684 685 appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1); 686 prevAppList.add(appInfo); 687 appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2); 688 prevAppList.add(appInfo); 689 690 mApplicationsState.mApplications = prevAppList; 691 // Previous Entries: 692 // owner (PKG_2), profile (pk1, PKG_2) 693 // PKG_1 is not installed for owner, hence it's removed from entries 694 addApp(PKG_2, OWNER_UID_2, 0); 695 addApp(PKG_1, PROFILE_UID_1, PROFILE_USERID); 696 addApp(PKG_2, PROFILE_UID_2, PROFILE_USERID); 697 698 // latest Applications: 699 // owner (PKG_1 - uninstalled, PKG_2), profile (PKG_1, PKG_2) 700 // owner's PKG_1 is still listed and is in non-installed state 701 702 // owner 703 final ArrayList<ApplicationInfo> ownerAppList = new ArrayList<>(); 704 appInfo = createApplicationInfo(PKG_1, OWNER_UID_1); 705 appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED; 706 ownerAppList.add(appInfo); 707 appInfo = createApplicationInfo(PKG_2, OWNER_UID_2); 708 ownerAppList.add(appInfo); 709 710 // profile 711 final ArrayList<ApplicationInfo> profileAppList = new ArrayList<>(); 712 appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1); 713 profileAppList.add(appInfo); 714 appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2); 715 profileAppList.add(appInfo); 716 setupDoResumeIfNeededLocked(ownerAppList, profileAppList); 717 718 mApplicationsState.doResumeIfNeededLocked(); 719 720 verify(mApplicationsState, never()).clearEntries(); 721 } 722 723 @Test testDefaultSession_enabledAppIconCache_shouldSkipPreloadIcon()724 public void testDefaultSession_enabledAppIconCache_shouldSkipPreloadIcon() { 725 when(mApplication.getPackageName()).thenReturn("com.android.settings"); 726 mSession.onResume(); 727 728 addApp(HOME_PACKAGE_NAME, 1); 729 addApp(LAUNCHABLE_PACKAGE_NAME, 2); 730 mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); 731 processAllMessages(); 732 verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); 733 734 List<AppEntry> appEntries = mAppEntriesCaptor.getValue(); 735 for (AppEntry appEntry : appEntries) { 736 assertThat(appEntry.icon).isNull(); 737 } 738 } 739 setupDoResumeIfNeededLocked(ArrayList<ApplicationInfo> ownerApps, ArrayList<ApplicationInfo> profileApps)740 private void setupDoResumeIfNeededLocked(ArrayList<ApplicationInfo> ownerApps, 741 ArrayList<ApplicationInfo> profileApps) 742 throws RemoteException { 743 744 if (ownerApps != null) { 745 when(mApplicationsState.mIpm.getInstalledApplications(anyLong(), eq(0))) 746 .thenReturn(new ParceledListSlice<>(ownerApps)); 747 } 748 if (profileApps != null) { 749 when(mApplicationsState.mIpm.getInstalledApplications(anyLong(), eq(PROFILE_USERID))) 750 .thenReturn(new ParceledListSlice<>(profileApps)); 751 } 752 final InterestingConfigChanges configChanges = mock(InterestingConfigChanges.class); 753 when(configChanges.applyNewConfig(any(Resources.class))).thenReturn(false); 754 mApplicationsState.setInterestingConfigChanges(configChanges); 755 } 756 757 @Test shouldShowInPersonalTab_forCurrentUser_returnsTrue()758 public void shouldShowInPersonalTab_forCurrentUser_returnsTrue() { 759 UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class); 760 ApplicationInfo appInfo = createApplicationInfo(PKG_1); 761 AppEntry primaryUserApp = createAppEntry(appInfo, 1); 762 763 assertThat(primaryUserApp.shouldShowInPersonalTab(um, appInfo.uid)).isTrue(); 764 } 765 766 @Test shouldShowInPersonalTab_userProfilePreU_returnsFalse()767 public void shouldShowInPersonalTab_userProfilePreU_returnsFalse() { 768 UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class); 769 ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 770 Build.VERSION_CODES.TIRAMISU); 771 // Create an app (and subsequent AppEntry) in a non-primary user profile. 772 ApplicationInfo appInfo1 = createApplicationInfo(PKG_1, PROFILE_UID_1); 773 AppEntry nonPrimaryUserApp = createAppEntry(appInfo1, 1); 774 775 assertThat(nonPrimaryUserApp.shouldShowInPersonalTab(um, appInfo1.uid)).isFalse(); 776 } 777 778 @Test shouldShowInPersonalTab_currentUserIsParent_returnsAsPerUserPropertyOfProfile1()779 public void shouldShowInPersonalTab_currentUserIsParent_returnsAsPerUserPropertyOfProfile1() { 780 // Mark system user as parent for both profile users. 781 UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class); 782 ShadowUserManager shadowUserManager = Shadow.extract(um); 783 shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID, 784 CLONE_USER, 0); 785 shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID2, 786 RANDOM_USER, 0); 787 shadowUserManager.setupUserProperty(PROFILE_USERID, 788 /*showInSettings*/ UserProperties.SHOW_IN_SETTINGS_WITH_PARENT); 789 shadowUserManager.setupUserProperty(PROFILE_USERID2, 790 /*showInSettings*/ UserProperties.SHOW_IN_SETTINGS_NO); 791 792 ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 793 Build.VERSION_CODES.UPSIDE_DOWN_CAKE); 794 795 // Treat PROFILE_USERID as a clone user profile and create an app PKG_1 in it. 796 ApplicationInfo appInfo1 = createApplicationInfo(PKG_1, PROFILE_UID_1); 797 // Treat PROFILE_USERID2 as a random non-primary profile and create an app PKG_3 in it. 798 ApplicationInfo appInfo2 = createApplicationInfo(PKG_3, PROFILE_UID_3); 799 AppEntry nonPrimaryUserApp1 = createAppEntry(appInfo1, 1); 800 AppEntry nonPrimaryUserApp2 = createAppEntry(appInfo2, 2); 801 802 assertThat(nonPrimaryUserApp1.shouldShowInPersonalTab(um, appInfo1.uid)).isTrue(); 803 assertThat(nonPrimaryUserApp2.shouldShowInPersonalTab(um, appInfo2.uid)).isFalse(); 804 } 805 806 @Test getEntry_hasCache_shouldReturnCacheEntry()807 public void getEntry_hasCache_shouldReturnCacheEntry() { 808 mApplicationsState.mEntriesMap.put(/* userId= */ 0, new HashMap<>()); 809 addApp(PKG_1, /* id= */ 1); 810 811 assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName) 812 .isEqualTo(PKG_1); 813 } 814 815 @Test getEntry_hasNoCache_shouldReturnEntry()816 public void getEntry_hasNoCache_shouldReturnEntry() { 817 mApplicationsState.mEntriesMap.clear(); 818 ApplicationInfo appInfo = createApplicationInfo(PKG_1, /* uid= */ 0); 819 mApplicationsState.mApplications.add(appInfo); 820 mApplicationsState.mSystemModules.put(PKG_1, /* value= */ false); 821 822 assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName) 823 .isEqualTo(PKG_1); 824 } 825 } 826