• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.settingslib.spaprivileged.model.app
18 
19 import android.content.Context
20 import android.content.pm.ActivityInfo
21 import android.content.pm.ApplicationInfo
22 import android.content.pm.FakeFeatureFlagsImpl
23 import android.content.pm.Flags
24 import android.content.pm.ModuleInfo
25 import android.content.pm.PackageManager
26 import android.content.pm.PackageManager.ApplicationInfoFlags
27 import android.content.pm.PackageManager.ResolveInfoFlags
28 import android.content.pm.ResolveInfo
29 import android.content.pm.UserInfo
30 import android.content.res.Resources
31 import android.os.BadParcelableException
32 import android.os.DeadObjectException
33 import android.os.UserManager
34 import android.platform.test.flag.junit.SetFlagsRule
35 import androidx.test.core.app.ApplicationProvider
36 import androidx.test.ext.junit.runners.AndroidJUnit4
37 import com.android.internal.R
38 import com.google.common.truth.Truth.assertThat
39 import kotlinx.coroutines.flow.first
40 import kotlinx.coroutines.flow.flowOf
41 import kotlinx.coroutines.test.runTest
42 import org.junit.Rule
43 import org.junit.Test
44 import org.junit.runner.RunWith
45 import org.mockito.kotlin.any
46 import org.mockito.kotlin.argumentCaptor
47 import org.mockito.kotlin.doAnswer
48 import org.mockito.kotlin.doReturn
49 import org.mockito.kotlin.doThrow
50 import org.mockito.kotlin.eq
51 import org.mockito.kotlin.mock
52 import org.mockito.kotlin.spy
53 import org.mockito.kotlin.stub
54 import org.mockito.kotlin.verify
55 import org.mockito.kotlin.whenever
56 
57 @RunWith(AndroidJUnit4::class)
58 class AppListRepositoryTest {
59     @get:Rule
60     val mSetFlagsRule = SetFlagsRule()
61 
<lambda>null62     private val resources = mock<Resources> {
63         on { getStringArray(R.array.config_hideWhenDisabled_packageNames) } doReturn emptyArray()
64     }
65 
<lambda>null66     private val packageManager = mock<PackageManager> {
67         on { getInstalledModules(any()) } doReturn emptyList()
68         on { getHomeActivities(any()) } doAnswer {
69             @Suppress("UNCHECKED_CAST")
70             val resolveInfos = it.arguments[0] as MutableList<ResolveInfo>
71             resolveInfos += resolveInfoOf(packageName = HOME_APP.packageName)
72             null
73         }
74         on { queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), any<Int>()) } doReturn
75             listOf(resolveInfoOf(packageName = IN_LAUNCHER_APP.packageName))
76     }
77 
<lambda>null78     private val mockUserManager = mock<UserManager> {
79         on { getUserInfo(ADMIN_USER_ID) } doReturn UserInfo().apply {
80             flags = UserInfo.FLAG_ADMIN
81         }
82         on { getProfileIdsWithDisabled(ADMIN_USER_ID) } doReturn
83             intArrayOf(ADMIN_USER_ID, MANAGED_PROFILE_USER_ID)
84     }
85 
<lambda>null86     private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
87         on { resources } doReturn resources
88         on { packageManager } doReturn packageManager
89         on { getSystemService(UserManager::class.java) } doReturn mockUserManager
90     }
91 
92     private val repository = AppListRepositoryImpl(context)
93 
mockInstalledApplicationsnull94     private fun mockInstalledApplications(apps: List<ApplicationInfo>, userId: Int) {
95         packageManager.stub {
96             on { getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(userId)) } doReturn
97                 apps
98         }
99     }
100 
101     @Test
<lambda>null102     fun loadApps_notShowInstantApps() = runTest {
103         mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID)
104 
105         val appList = repository.loadApps(
106             userId = ADMIN_USER_ID,
107             loadInstantApps = false,
108         )
109 
110         assertThat(appList).containsExactly(NORMAL_APP)
111     }
112 
113     @Test
<lambda>null114     fun loadApps_showInstantApps() = runTest {
115         mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID)
116 
117         val appList = repository.loadApps(
118             userId = ADMIN_USER_ID,
119             loadInstantApps = true,
120         )
121 
122         assertThat(appList).containsExactly(NORMAL_APP, INSTANT_APP)
123     }
124 
125     @Test
<lambda>null126     fun loadApps_notMatchAnyUserForAdmin_withRegularFlags() = runTest {
127         mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
128 
129         val appList = repository.loadApps(
130             userId = ADMIN_USER_ID,
131             matchAnyUserForAdmin = false,
132         )
133 
134         assertThat(appList).containsExactly(NORMAL_APP)
135         val flags = argumentCaptor<ApplicationInfoFlags> {
136             verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID))
137         }.firstValue
138         assertThat(flags.value and PackageManager.MATCH_ANY_USER.toLong()).isEqualTo(0L)
139     }
140 
141     @Test
<lambda>null142     fun loadApps_matchAnyUserForAdmin_withMatchAnyUserFlag() = runTest {
143         mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
144 
145         val appList = repository.loadApps(
146             userId = ADMIN_USER_ID,
147             matchAnyUserForAdmin = true,
148         )
149 
150         assertThat(appList).containsExactly(NORMAL_APP)
151         val flags = argumentCaptor<ApplicationInfoFlags> {
152             verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID))
153         }.firstValue
154         assertThat(flags.value and PackageManager.MATCH_ANY_USER.toLong()).isGreaterThan(0L)
155     }
156 
157     @Test
<lambda>null158     fun loadApps_matchAnyUserForAdminAndInstalledOnManagedProfileOnly_notDisplayed() = runTest {
159         val managedProfileOnlyPackageName = "installed.on.managed.profile.only"
160         mockInstalledApplications(listOf(ApplicationInfo().apply {
161             packageName = managedProfileOnlyPackageName
162         }), ADMIN_USER_ID)
163         mockInstalledApplications(listOf(ApplicationInfo().apply {
164             packageName = managedProfileOnlyPackageName
165             flags = ApplicationInfo.FLAG_INSTALLED
166         }), MANAGED_PROFILE_USER_ID)
167 
168         val appList = repository.loadApps(
169             userId = ADMIN_USER_ID,
170             matchAnyUserForAdmin = true,
171         )
172 
173         assertThat(appList).isEmpty()
174     }
175 
176     @Test
<lambda>null177     fun loadApps_matchAnyUserForAdminAndInstalledOnSecondaryUserOnly_displayed() = runTest {
178         val secondaryUserOnlyApp = ApplicationInfo().apply {
179             packageName = "installed.on.secondary.user.only"
180         }
181         mockInstalledApplications(listOf(secondaryUserOnlyApp), ADMIN_USER_ID)
182         mockInstalledApplications(emptyList(), MANAGED_PROFILE_USER_ID)
183 
184         val appList = repository.loadApps(
185             userId = ADMIN_USER_ID,
186             matchAnyUserForAdmin = true,
187         )
188 
189         assertThat(appList).containsExactly(secondaryUserOnlyApp)
190     }
191 
192     @Test
<lambda>null193     fun loadApps_isHideWhenDisabledPackageAndDisabled() = runTest {
194         val app = ApplicationInfo().apply {
195             packageName = "is.hide.when.disabled"
196             enabled = false
197         }
198         whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
199             .thenReturn(arrayOf(app.packageName))
200         mockInstalledApplications(listOf(app), ADMIN_USER_ID)
201 
202         val appList = repository.loadApps(userId = ADMIN_USER_ID)
203 
204         assertThat(appList).isEmpty()
205     }
206 
207     @Test
<lambda>null208     fun loadApps_isHideWhenDisabledPackageAndDisabledUntilUsed() = runTest {
209         val app = ApplicationInfo().apply {
210             packageName = "is.hide.when.disabled"
211             enabled = true
212             enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
213         }
214         whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
215             .thenReturn(arrayOf(app.packageName))
216         mockInstalledApplications(listOf(app), ADMIN_USER_ID)
217 
218         val appList = repository.loadApps(userId = ADMIN_USER_ID)
219 
220         assertThat(appList).isEmpty()
221     }
222 
223     @Test
<lambda>null224     fun loadApps_isHideWhenDisabledPackageAndEnabled() = runTest {
225         val app = ApplicationInfo().apply {
226             packageName = "is.hide.when.disabled"
227             enabled = true
228         }
229         whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
230             .thenReturn(arrayOf(app.packageName))
231         mockInstalledApplications(listOf(app), ADMIN_USER_ID)
232 
233         val appList = repository.loadApps(userId = ADMIN_USER_ID)
234 
235         assertThat(appList).containsExactly(app)
236     }
237 
238     @Test
<lambda>null239     fun loadApps_disabledByUser() = runTest {
240         val app = ApplicationInfo().apply {
241             packageName = "disabled.by.user"
242             enabled = false
243             enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
244         }
245         mockInstalledApplications(listOf(app), ADMIN_USER_ID)
246 
247         val appList = repository.loadApps(userId = ADMIN_USER_ID)
248 
249         assertThat(appList).containsExactly(app)
250     }
251 
252     @Test
<lambda>null253     fun loadApps_disabledButNotByUser() = runTest {
254         val app = ApplicationInfo().apply {
255             packageName = "disabled"
256             enabled = false
257         }
258         mockInstalledApplications(listOf(app), ADMIN_USER_ID)
259 
260         val appList = repository.loadApps(userId = ADMIN_USER_ID)
261 
262         assertThat(appList).isEmpty()
263     }
264 
265     @Test
<lambda>null266     fun loadApps_archivedAppsEnabled() = runTest {
267         val fakeFlags = FakeFeatureFlagsImpl()
268         fakeFlags.setFlag(Flags.FLAG_ARCHIVING, true)
269         mockInstalledApplications(listOf(NORMAL_APP, ARCHIVED_APP), ADMIN_USER_ID)
270         val repository = AppListRepositoryImpl(context, fakeFlags)
271         val appList = repository.loadApps(userId = ADMIN_USER_ID)
272 
273         assertThat(appList).containsExactly(NORMAL_APP, ARCHIVED_APP)
274         val flags = argumentCaptor<ApplicationInfoFlags> {
275             verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID))
276         }.firstValue
277         assertThat(flags.value).isEqualTo(
278             (PackageManager.MATCH_DISABLED_COMPONENTS or
279                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() or
280                 PackageManager.MATCH_ARCHIVED_PACKAGES
281         )
282     }
283 
284     @Test
<lambda>null285     fun loadApps_hasApkInApexInfo_shouldNotIncludeAllHiddenApps() = runTest {
286         mSetFlagsRule.enableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
287         packageManager.stub {
288             on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE)
289         }
290         mockInstalledApplications(
291             listOf(NORMAL_APP, HIDDEN_APEX_APP, HIDDEN_MODULE_APP),
292             ADMIN_USER_ID
293         )
294 
295         val appList = repository.loadApps(userId = ADMIN_USER_ID)
296 
297         assertThat(appList).containsExactly(NORMAL_APP)
298     }
299 
300     @Test
<lambda>null301     fun loadApps_noApkInApexInfo_shouldNotIncludeHiddenSystemModule() = runTest {
302         mSetFlagsRule.disableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
303         packageManager.stub {
304             on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE)
305         }
306         mockInstalledApplications(
307             listOf(NORMAL_APP, HIDDEN_APEX_APP, HIDDEN_MODULE_APP),
308             ADMIN_USER_ID
309         )
310 
311         val appList = repository.loadApps(userId = ADMIN_USER_ID)
312 
313         assertThat(appList).containsExactly(NORMAL_APP, HIDDEN_APEX_APP)
314     }
315 
316     @Test
<lambda>null317     fun loadApps_hasException_returnEmptyList() = runTest {
318         packageManager.stub {
319             on {
320                 getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(ADMIN_USER_ID))
321             } doThrow BadParcelableException(DeadObjectException())
322         }
323 
324         val appList = repository.loadApps(userId = ADMIN_USER_ID)
325 
326         assertThat(appList).isEmpty()
327     }
328 
329     @Test
<lambda>null330     fun showSystemPredicate_showSystem() = runTest {
331         val app = SYSTEM_APP
332 
333         val showSystemPredicate = getShowSystemPredicate(showSystem = true)
334 
335         assertThat(showSystemPredicate(app)).isTrue()
336     }
337 
338     @Test
<lambda>null339     fun showSystemPredicate_notShowSystemAndIsSystemApp() = runTest {
340         val app = SYSTEM_APP
341 
342         val showSystemPredicate = getShowSystemPredicate(showSystem = false)
343 
344         assertThat(showSystemPredicate(app)).isFalse()
345     }
346 
347     @Test
<lambda>null348     fun showSystemPredicate_isUpdatedSystemApp() = runTest {
349         val app = UPDATED_SYSTEM_APP
350 
351         val showSystemPredicate = getShowSystemPredicate(showSystem = false)
352 
353         assertThat(showSystemPredicate(app)).isTrue()
354     }
355 
356     @Test
<lambda>null357     fun showSystemPredicate_isHome() = runTest {
358         val showSystemPredicate = getShowSystemPredicate(showSystem = false)
359 
360         assertThat(showSystemPredicate(HOME_APP)).isTrue()
361     }
362 
363     @Test
showSystemPredicate_appInLaunchernull364     fun showSystemPredicate_appInLauncher() = runTest {
365         val showSystemPredicate = getShowSystemPredicate(showSystem = false)
366 
367         assertThat(showSystemPredicate(IN_LAUNCHER_APP)).isTrue()
368     }
369 
370     @Test
<lambda>null371     fun getSystemPackageNames_returnExpectedValues() = runTest {
372         mockInstalledApplications(
373             apps = listOf(
374                 NORMAL_APP,
375                 INSTANT_APP,
376                 SYSTEM_APP,
377                 UPDATED_SYSTEM_APP,
378                 HOME_APP,
379                 IN_LAUNCHER_APP,
380             ),
381             userId = ADMIN_USER_ID,
382         )
383 
384         val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames(
385             context = context,
386             userId = ADMIN_USER_ID,
387         )
388 
389         assertThat(systemPackageNames).containsExactly(SYSTEM_APP.packageName)
390     }
391 
392     @Test
<lambda>null393     fun loadAndFilterApps_loadNonSystemApp_returnExpectedValues() = runTest {
394         mockInstalledApplications(
395             apps = listOf(
396                 NORMAL_APP,
397                 INSTANT_APP,
398                 SYSTEM_APP,
399                 UPDATED_SYSTEM_APP,
400                 HOME_APP,
401                 IN_LAUNCHER_APP,
402             ),
403             userId = ADMIN_USER_ID,
404         )
405 
406         val appList = repository.loadAndFilterApps(userId = ADMIN_USER_ID, isSystemApp = false)
407 
408         assertThat(appList)
409             .containsExactly(NORMAL_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP)
410     }
411 
getShowSystemPredicatenull412     private suspend fun getShowSystemPredicate(showSystem: Boolean) =
413         repository.showSystemPredicate(
414             userIdFlow = flowOf(ADMIN_USER_ID),
415             showSystemFlow = flowOf(showSystem),
416         ).first()
417 
418     private companion object {
419         const val ADMIN_USER_ID = 0
420         const val MANAGED_PROFILE_USER_ID = 11
421 
422         val NORMAL_APP = ApplicationInfo().apply {
423             packageName = "normal"
424             enabled = true
425         }
426 
427         val INSTANT_APP = ApplicationInfo().apply {
428             packageName = "instant"
429             enabled = true
430             privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
431         }
432 
433         val SYSTEM_APP = ApplicationInfo().apply {
434             packageName = "system.app"
435             flags = ApplicationInfo.FLAG_SYSTEM
436         }
437 
438         val UPDATED_SYSTEM_APP = ApplicationInfo().apply {
439             packageName = "updated.system.app"
440             flags = ApplicationInfo.FLAG_SYSTEM or ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
441         }
442 
443         val HOME_APP = ApplicationInfo().apply {
444             packageName = "home.app"
445             flags = ApplicationInfo.FLAG_SYSTEM
446         }
447 
448         val IN_LAUNCHER_APP = ApplicationInfo().apply {
449             packageName = "app.in.launcher"
450             flags = ApplicationInfo.FLAG_SYSTEM
451         }
452 
453         val ARCHIVED_APP = ApplicationInfo().apply {
454             packageName = "archived.app"
455             flags = ApplicationInfo.FLAG_SYSTEM
456             isArchived = true
457         }
458 
459         val HIDDEN_APEX_APP = ApplicationInfo().apply {
460             packageName = "hidden.apex.package"
461         }
462 
463         val HIDDEN_MODULE_APP = ApplicationInfo().apply {
464             packageName = "hidden.module.package"
465         }
466 
467         val HIDDEN_MODULE = ModuleInfo().apply {
468             packageName = "hidden.module.package"
469             apkInApexPackageNames = listOf("hidden.apex.package")
470             isHidden = true
471         }
472 
473         fun resolveInfoOf(packageName: String) = ResolveInfo().apply {
474             activityInfo = ActivityInfo().apply {
475                 this.packageName = packageName
476             }
477         }
478     }
479 }
480