• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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