1 /*
2  * Copyright (C) 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.settings.applications;
18 
19 import static com.android.settings.testutils.ApplicationTestUtils.buildInfo;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.mockito.ArgumentMatchers.any;
24 import static org.mockito.ArgumentMatchers.anyInt;
25 import static org.mockito.ArgumentMatchers.argThat;
26 import static org.mockito.ArgumentMatchers.eq;
27 import static org.mockito.Mockito.atLeast;
28 import static org.mockito.Mockito.verify;
29 import static org.mockito.Mockito.verifyNoMoreInteractions;
30 import static org.mockito.Mockito.when;
31 
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.pm.ApplicationInfo;
35 import android.content.pm.FakeFeatureFlagsImpl;
36 import android.content.pm.FeatureFlags;
37 import android.content.pm.Flags;
38 import android.content.pm.PackageManager;
39 import android.content.pm.PackageManager.ApplicationInfoFlags;
40 import android.content.pm.ResolveInfo;
41 import android.content.pm.UserInfo;
42 import android.os.UserHandle;
43 import android.os.UserManager;
44 import android.platform.test.flag.junit.SetFlagsRule;
45 
46 import org.junit.Before;
47 import org.junit.Ignore;
48 import org.junit.Rule;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 import org.mockito.ArgumentMatcher;
52 import org.mockito.Mock;
53 import org.mockito.junit.MockitoJUnit;
54 import org.mockito.junit.MockitoRule;
55 import org.robolectric.RobolectricTestRunner;
56 import org.robolectric.shadows.ShadowApplication;
57 
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.Collections;
61 import java.util.List;
62 import java.util.Set;
63 
64 @RunWith(RobolectricTestRunner.class)
65 public final class InstalledAppCounterTest {
66 
67     @Rule
68     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
69 
70     private static final String APP_1 = "app1";
71     private static final String APP_2 = "app2";
72     private static final String APP_3 = "app3";
73     private static final String APP_4 = "app4";
74     private static final String APP_5 = "app5";
75     private static final String APP_6 = "app6";
76     private static final String APP_7 = "app7";
77 
78     private final int MAIN_USER_ID = 0;
79     private final int MANAGED_PROFILE_ID = 10;
80 
81     private final int PER_USER_UID_RANGE = 100000;
82     private final int MAIN_USER_APP_UID = MAIN_USER_ID * PER_USER_UID_RANGE;
83     private final int MANAGED_PROFILE_APP_UID = MANAGED_PROFILE_ID * PER_USER_UID_RANGE;
84 
85     @Mock
86     private UserManager mUserManager;
87     @Mock
88     private Context mContext;
89     @Mock
90     private PackageManager mPackageManager;
91 
92     @Rule
93     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
94 
95     private int mInstalledAppCount = -1;
96     private ApplicationInfo mApp1;
97     private ApplicationInfo mApp2;
98     private ApplicationInfo mApp3;
99     private ApplicationInfo mApp4;
100     private ApplicationInfo mApp5;
101     private ApplicationInfo mApp6;
102     private ApplicationInfo mApp7;
103 
104     private FakeFeatureFlagsImpl mFakeFeatureFlags;
105 
106     @Before
setUp()107     public void setUp() {
108         when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
109         mFakeFeatureFlags = new FakeFeatureFlagsImpl();
110         mFakeFeatureFlags.setFlag(Flags.FLAG_ARCHIVING, true);
111 
112         mApp1 = buildInfo(MAIN_USER_APP_UID, APP_1,
113                 ApplicationInfo.FLAG_UPDATED_SYSTEM_APP, 0 /* targetSdkVersion */);
114         mApp2 = buildInfo(MAIN_USER_APP_UID, APP_2, 0 /* flags */,
115                 0 /* targetSdkVersion */);
116         mApp3 = buildInfo(MAIN_USER_APP_UID, APP_3, ApplicationInfo.FLAG_SYSTEM,
117                 0 /* targetSdkVersion */);
118         mApp4 = buildInfo(MAIN_USER_APP_UID, APP_4, ApplicationInfo.FLAG_SYSTEM,
119                 0 /* targetSdkVersion */);
120         mApp5 = buildInfo(MANAGED_PROFILE_APP_UID, APP_5, 0 /* flags */,
121                 0 /* targetSdkVersion */);
122         mApp6 = buildInfo(MANAGED_PROFILE_APP_UID, APP_6, ApplicationInfo.FLAG_SYSTEM,
123                 0 /* targetSdkVersion */);
124         mApp7 = buildInfo(MAIN_USER_APP_UID, APP_7, 0 /* flags */,
125                 0 /* targetSdkVersion */);
126         mApp7.isArchived = true;
127     }
128 
expectQueryIntentActivities(int userId, String packageName, boolean launchable)129     private void expectQueryIntentActivities(int userId, String packageName, boolean launchable) {
130         when(mPackageManager.queryIntentActivitiesAsUser(
131                 argThat(isLaunchIntentFor(packageName)),
132                 eq(PackageManager.GET_DISABLED_COMPONENTS | PackageManager.MATCH_DIRECT_BOOT_AWARE
133                         | PackageManager.MATCH_DIRECT_BOOT_UNAWARE),
134                 eq(userId))).thenReturn(launchable
135                         ? Collections.singletonList(new ResolveInfo())
136                         : new ArrayList<>());
137     }
138 
testCountInstalledAppsAcrossAllUsers(boolean async)139     private void testCountInstalledAppsAcrossAllUsers(boolean async) {
140         // There are two users.
141         when(mUserManager.getProfiles(UserHandle.myUserId())).thenReturn(Arrays.asList(
142                 new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_ADMIN),
143                 new UserInfo(MANAGED_PROFILE_ID, "managed profile", 0)));
144         configurePackageManager();
145 
146         // Count the number of all apps installed, irrespective of install reason.
147         count(InstalledAppCounter.IGNORE_INSTALL_REASON, async);
148         assertThat(mInstalledAppCount).isEqualTo(5);
149 
150         // Verify that installed packages were retrieved the current user and the user's managed
151         // profile only.
152         verify(mPackageManager)
153                 .getInstalledApplicationsAsUser(
154                         any(ApplicationInfoFlags.class),
155                         eq(MAIN_USER_ID));
156         verify(mPackageManager)
157                 .getInstalledApplicationsAsUser(
158                         any(ApplicationInfoFlags.class),
159                         eq(MANAGED_PROFILE_ID));
160         verify(mPackageManager, atLeast(0))
161             .queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt());
162         verifyNoMoreInteractions(mPackageManager);
163 
164         // Count once more, considering apps installed by enterprise policy only.
165         count(PackageManager.INSTALL_REASON_POLICY, async);
166         assertThat(mInstalledAppCount).isEqualTo(3);
167     }
168 
169     @Test
testIncludeInCount()170     public void testIncludeInCount() {
171         configurePackageManager();
172         assertThat(InstalledAppCounter.includeInCount(InstalledAppCounter.IGNORE_INSTALL_REASON,
173                 mPackageManager, mApp1)).isTrue();
174         assertThat(InstalledAppCounter.includeInCount(InstalledAppCounter.IGNORE_INSTALL_REASON,
175                 mPackageManager, mApp2)).isTrue();
176         assertThat(InstalledAppCounter.includeInCount(InstalledAppCounter.IGNORE_INSTALL_REASON,
177                 mPackageManager, mApp3)).isTrue();
178         assertThat(InstalledAppCounter.includeInCount(InstalledAppCounter.IGNORE_INSTALL_REASON,
179                 mPackageManager, mApp4)).isFalse();
180         assertThat(InstalledAppCounter.includeInCount(InstalledAppCounter.IGNORE_INSTALL_REASON,
181                 mPackageManager, mApp5)).isTrue();
182         assertThat(InstalledAppCounter.includeInCount(InstalledAppCounter.IGNORE_INSTALL_REASON,
183                 mPackageManager, mApp6)).isTrue();
184 
185         assertThat(InstalledAppCounter.includeInCount(PackageManager.INSTALL_REASON_POLICY,
186                 mPackageManager, mApp1)).isTrue();
187         assertThat(InstalledAppCounter.includeInCount(PackageManager.INSTALL_REASON_POLICY,
188                 mPackageManager, mApp2)).isFalse();
189         assertThat(InstalledAppCounter.includeInCount(PackageManager.INSTALL_REASON_POLICY,
190                 mPackageManager, mApp3)).isTrue();
191         assertThat(InstalledAppCounter.includeInCount(PackageManager.INSTALL_REASON_POLICY,
192                 mPackageManager, mApp4)).isFalse();
193         assertThat(InstalledAppCounter.includeInCount(PackageManager.INSTALL_REASON_POLICY,
194                 mPackageManager, mApp5)).isTrue();
195         assertThat(InstalledAppCounter.includeInCount(PackageManager.INSTALL_REASON_POLICY,
196                 mPackageManager, mApp6)).isFalse();
197     }
198 
199     @Ignore("b/313578776")
200     @Test
testCountInstalledAppsAcrossAllUsersSync()201     public void testCountInstalledAppsAcrossAllUsersSync() {
202         testCountInstalledAppsAcrossAllUsers(false /* async */);
203     }
204 
205     @Ignore("b/313578776")
206     @Test
testCountInstalledAppsAcrossAllUsersAsync()207     public void testCountInstalledAppsAcrossAllUsersAsync() {
208         testCountInstalledAppsAcrossAllUsers(true /* async */);
209     }
210 
211     @Test
testCountInstalledApps_archivingDisabled()212     public void testCountInstalledApps_archivingDisabled() {
213         when(mUserManager.getProfiles(UserHandle.myUserId())).thenReturn(List.of(
214                 new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_ADMIN)));
215         // The user has four apps installed:
216         // * app2 is a user-installed app. It should be counted.
217         // * app7 is a user-archived app. It should not be counted.
218         when(mPackageManager.getInstalledApplicationsAsUser(
219                 argThat(isApplicationInfoFlagsEqualTo(
220                         ApplicationInfoFlags.of(
221                                 PackageManager.GET_DISABLED_COMPONENTS
222                                         | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS
223                                         | PackageManager.MATCH_ANY_USER))),
224                 eq(MAIN_USER_ID))).thenReturn(Arrays.asList(mApp2));
225 
226         mFakeFeatureFlags.setFlag(Flags.FLAG_ARCHIVING, false);
227         mSetFlagsRule.disableFlags(com.android.settings.flags.Flags.FLAG_APP_ARCHIVING);
228         // Count the number of all apps installed, irrespective of install reason.
229         count(InstalledAppCounter.IGNORE_INSTALL_REASON, mFakeFeatureFlags);
230         assertThat(mInstalledAppCount).isEqualTo(1);
231     }
232 
233     @Test
testCountInstalledApps_archivingEnabled()234     public void testCountInstalledApps_archivingEnabled() {
235         when(mUserManager.getProfiles(UserHandle.myUserId())).thenReturn(List.of(
236                 new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_ADMIN)));
237         // The user has four apps installed:
238         // * app2 is a user-installed app. It should be counted.
239         // * app7 is a user-archived app. It should be counted.
240         when(mPackageManager.getInstalledApplicationsAsUser(
241                 argThat(isApplicationInfoFlagsEqualTo(
242                         ApplicationInfoFlags.of(
243                                 PackageManager.GET_DISABLED_COMPONENTS
244                                         | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS
245                                         | PackageManager.MATCH_ANY_USER
246                                         | PackageManager.MATCH_ARCHIVED_PACKAGES))),
247                 eq(MAIN_USER_ID))).thenReturn(Arrays.asList(mApp2, mApp7));
248 
249         // Count the number of all apps installed, irrespective of install reason.
250         count(InstalledAppCounter.IGNORE_INSTALL_REASON, mFakeFeatureFlags);
251         assertThat(mInstalledAppCount).isEqualTo(2);
252     }
253 
count(int installReason, boolean async)254     private void count(int installReason, boolean async) {
255         mInstalledAppCount = -1;
256         final InstalledAppCounterTestable counter = new InstalledAppCounterTestable(installReason);
257         if (async) {
258             counter.execute();
259             // Wait for the background task to finish.
260             ShadowApplication.runBackgroundTasks();
261         } else {
262             counter.executeInForeground();
263         }
264     }
265 
count(int installReason, FeatureFlags featureFlags)266     private void count(int installReason, FeatureFlags featureFlags) {
267         mInstalledAppCount = -1;
268         final InstalledAppCounterTestable counter =
269                 new InstalledAppCounterTestable(installReason, featureFlags);
270         counter.executeInForeground();
271     }
272 
configurePackageManager()273     private void configurePackageManager() {
274         // The first user has four apps installed:
275         // * app1 is an updated system app. It should be counted.
276         // * app2 is a user-installed app. It should be counted.
277         // * app3 is a system app that provides a launcher icon. It should be counted.
278         // * app4 is a system app that provides no launcher icon. It should not be counted.
279         ApplicationInfoFlags infoFlags1 = ApplicationInfoFlags.of(
280                 PackageManager.GET_DISABLED_COMPONENTS
281                         | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS
282                         | PackageManager.MATCH_ANY_USER);
283         when(mPackageManager.getInstalledApplicationsAsUser(
284                 argThat(isApplicationInfoFlagsEqualTo(infoFlags1)),
285                 eq(MAIN_USER_ID))
286         ).thenReturn(Arrays.asList(mApp1, mApp2, mApp3, mApp4));
287         // For system apps, InstalledAppCounter checks whether they handle the default launcher
288         // intent to decide whether to include them in the count of installed apps or not.
289         expectQueryIntentActivities(MAIN_USER_ID, APP_3, true /* launchable */);
290         expectQueryIntentActivities(MAIN_USER_ID, APP_4, false /* launchable */);
291 
292         // app1, app3 and app4 are installed by enterprise policy.
293         final UserHandle mainUser = new UserHandle(MAIN_USER_ID);
294         when(mPackageManager.getInstallReason(APP_1, mainUser))
295                 .thenReturn(PackageManager.INSTALL_REASON_POLICY);
296         when(mPackageManager.getInstallReason(APP_2, mainUser))
297                 .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN);
298         when(mPackageManager.getInstallReason(APP_3, mainUser))
299                 .thenReturn(PackageManager.INSTALL_REASON_POLICY);
300         when(mPackageManager.getInstallReason(APP_4, mainUser))
301                 .thenReturn(PackageManager.INSTALL_REASON_POLICY);
302 
303         // The second user has two apps installed:
304         // * app5 is a user-installed app. It should be counted.
305         // * app6 is a system app that provides a launcher icon. It should be counted.
306         ApplicationInfoFlags infoFlags2 = ApplicationInfoFlags.of(
307                 PackageManager.GET_DISABLED_COMPONENTS
308                         | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
309         when(mPackageManager.getInstalledApplicationsAsUser(
310                 argThat(isApplicationInfoFlagsEqualTo(infoFlags2)), eq(MANAGED_PROFILE_ID))
311         ).thenReturn(Arrays.asList(mApp5, mApp6));
312         expectQueryIntentActivities(MANAGED_PROFILE_ID, APP_6, true /* launchable */);
313 
314         // app5 is installed by enterprise policy.
315         final UserHandle managedProfileUser = new UserHandle(MANAGED_PROFILE_ID);
316         when(mPackageManager.getInstallReason(APP_5, managedProfileUser))
317                 .thenReturn(PackageManager.INSTALL_REASON_POLICY);
318         when(mPackageManager.getInstallReason(APP_6, managedProfileUser))
319                 .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN);
320     }
321 
322     private class InstalledAppCounterTestable extends InstalledAppCounter {
InstalledAppCounterTestable(int installReason)323         private InstalledAppCounterTestable(int installReason) {
324             super(mContext, installReason, mPackageManager);
325         }
326 
InstalledAppCounterTestable(int installReason, FeatureFlags featureFlags)327         private InstalledAppCounterTestable(int installReason, FeatureFlags featureFlags) {
328             super(mContext, installReason, mPackageManager, featureFlags);
329         }
330 
331         @Override
onCountComplete(int num)332         protected void onCountComplete(int num) {
333             mInstalledAppCount = num;
334         }
335     }
336 
isLaunchIntentFor(String packageName)337     private ArgumentMatcher<Intent> isLaunchIntentFor(String packageName) {
338         return intent -> {
339             if (intent == null) {
340                 return false;
341             }
342             if (!Intent.ACTION_MAIN.equals(intent.getAction())) {
343                 return false;
344             }
345             final Set<String> categories = intent.getCategories();
346             if (categories == null || categories.size() != 1 ||
347                     !categories.contains(Intent.CATEGORY_LAUNCHER)) {
348                 return false;
349             }
350             if (!packageName.equals(intent.getPackage())) {
351                 return false;
352             }
353             return true;
354         };
355     }
356 
357     private ArgumentMatcher<ApplicationInfoFlags> isApplicationInfoFlagsEqualTo(
358             ApplicationInfoFlags infoFlags) {
359         return flags -> {
360             if (flags == null) {
361                 return false;
362             }
363             return flags.getValue() == infoFlags.getValue();
364         };
365     }
366 }
367