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.PackageManager
23 import android.content.pm.PackageManager.ApplicationInfoFlags
24 import android.content.pm.PackageManager.ResolveInfoFlags
25 import android.content.pm.ResolveInfo
26 import android.content.pm.UserInfo
27 import android.content.res.Resources
28 import android.os.UserManager
29 import androidx.test.core.app.ApplicationProvider
30 import androidx.test.ext.junit.runners.AndroidJUnit4
31 import com.android.internal.R
32 import com.android.settingslib.spaprivileged.framework.common.userManager
33 import com.google.common.truth.Truth.assertThat
34 import kotlinx.coroutines.ExperimentalCoroutinesApi
35 import kotlinx.coroutines.flow.first
36 import kotlinx.coroutines.flow.flowOf
37 import kotlinx.coroutines.test.runTest
38 import org.junit.Before
39 import org.junit.Rule
40 import org.junit.Test
41 import org.junit.runner.RunWith
42 import org.mockito.ArgumentCaptor
43 import org.mockito.Mock
44 import org.mockito.Mockito.any
45 import org.mockito.Mockito.anyInt
46 import org.mockito.Mockito.eq
47 import org.mockito.Mockito.verify
48 import org.mockito.Spy
49 import org.mockito.junit.MockitoJUnit
50 import org.mockito.junit.MockitoRule
51 import org.mockito.Mockito.`when` as whenever
52 
53 @OptIn(ExperimentalCoroutinesApi::class)
54 @RunWith(AndroidJUnit4::class)
55 class AppListRepositoryTest {
56     @get:Rule
57     val mockito: MockitoRule = MockitoJUnit.rule()
58 
59     @Spy
60     private val context: Context = ApplicationProvider.getApplicationContext()
61 
62     @Mock
63     private lateinit var resources: Resources
64 
65     @Mock
66     private lateinit var packageManager: PackageManager
67 
68     @Mock
69     private lateinit var userManager: UserManager
70 
71     private lateinit var repository: AppListRepository
72 
73     @Before
74     fun setUp() {
75         whenever(context.resources).thenReturn(resources)
76         whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
77             .thenReturn(emptyArray())
78         whenever(context.packageManager).thenReturn(packageManager)
79         whenever(context.userManager).thenReturn(userManager)
80         whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList())
81         whenever(packageManager.getHomeActivities(any())).thenAnswer {
82             @Suppress("UNCHECKED_CAST")
83             val resolveInfos = it.arguments[0] as MutableList<ResolveInfo>
84             resolveInfos += resolveInfoOf(packageName = HOME_APP.packageName)
85             null
86         }
87         whenever(
88             packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), anyInt())
89         ).thenReturn(listOf(resolveInfoOf(packageName = IN_LAUNCHER_APP.packageName)))
90         whenever(userManager.getUserInfo(ADMIN_USER_ID)).thenReturn(UserInfo().apply {
91             flags = UserInfo.FLAG_ADMIN
92         })
93         whenever(userManager.getProfileIdsWithDisabled(ADMIN_USER_ID))
94             .thenReturn(intArrayOf(ADMIN_USER_ID, MANAGED_PROFILE_USER_ID))
95 
96         repository = AppListRepositoryImpl(context)
97     }
98 
99     private fun mockInstalledApplications(apps: List<ApplicationInfo>, userId: Int) {
100         whenever(
101             packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(userId))
102         ).thenReturn(apps)
103     }
104 
105     @Test
106     fun loadApps_notShowInstantApps() = runTest {
107         mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID)
108 
109         val appList = repository.loadApps(
110             userId = ADMIN_USER_ID,
111             loadInstantApps = false,
112         )
113 
114         assertThat(appList).containsExactly(NORMAL_APP)
115     }
116 
117     @Test
118     fun loadApps_showInstantApps() = runTest {
119         mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID)
120 
121         val appList = repository.loadApps(
122             userId = ADMIN_USER_ID,
123             loadInstantApps = true,
124         )
125 
126         assertThat(appList).containsExactly(NORMAL_APP, INSTANT_APP)
127     }
128 
129     @Test
130     fun loadApps_notMatchAnyUserForAdmin_withRegularFlags() = runTest {
131         mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
132 
133         val appList = repository.loadApps(
134             userId = ADMIN_USER_ID,
135             matchAnyUserForAdmin = false,
136         )
137 
138         assertThat(appList).containsExactly(NORMAL_APP)
139         val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java)
140         verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID))
141         assertThat(flags.value.value).isEqualTo(
142             PackageManager.MATCH_DISABLED_COMPONENTS or
143                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
144         )
145     }
146 
147     @Test
148     fun loadApps_matchAnyUserForAdmin_withMatchAnyUserFlag() = runTest {
149         mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
150 
151         val appList = repository.loadApps(
152             userId = ADMIN_USER_ID,
153             matchAnyUserForAdmin = true,
154         )
155 
156         assertThat(appList).containsExactly(NORMAL_APP)
157         val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java)
158         verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID))
159         assertThat(flags.value.value and PackageManager.MATCH_ANY_USER.toLong()).isGreaterThan(0L)
160     }
161 
162     @Test
163     fun loadApps_matchAnyUserForAdminAndInstalledOnManagedProfileOnly_notDisplayed() = runTest {
164         val managedProfileOnlyPackageName = "installed.on.managed.profile.only"
165         mockInstalledApplications(listOf(ApplicationInfo().apply {
166             packageName = managedProfileOnlyPackageName
167         }), ADMIN_USER_ID)
168         mockInstalledApplications(listOf(ApplicationInfo().apply {
169             packageName = managedProfileOnlyPackageName
170             flags = ApplicationInfo.FLAG_INSTALLED
171         }), MANAGED_PROFILE_USER_ID)
172 
173         val appList = repository.loadApps(
174             userId = ADMIN_USER_ID,
175             matchAnyUserForAdmin = true,
176         )
177 
178         assertThat(appList).isEmpty()
179     }
180 
181     @Test
182     fun loadApps_matchAnyUserForAdminAndInstalledOnSecondaryUserOnly_displayed() = runTest {
183         val secondaryUserOnlyApp = ApplicationInfo().apply {
184             packageName = "installed.on.secondary.user.only"
185         }
186         mockInstalledApplications(listOf(secondaryUserOnlyApp), ADMIN_USER_ID)
187         mockInstalledApplications(emptyList(), MANAGED_PROFILE_USER_ID)
188 
189         val appList = repository.loadApps(
190             userId = ADMIN_USER_ID,
191             matchAnyUserForAdmin = true,
192         )
193 
194         assertThat(appList).containsExactly(secondaryUserOnlyApp)
195     }
196 
197     @Test
198     fun loadApps_isHideWhenDisabledPackageAndDisabled() = runTest {
199         val app = ApplicationInfo().apply {
200             packageName = "is.hide.when.disabled"
201             enabled = false
202         }
203         whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
204             .thenReturn(arrayOf(app.packageName))
205         mockInstalledApplications(listOf(app), ADMIN_USER_ID)
206 
207         val appList = repository.loadApps(userId = ADMIN_USER_ID)
208 
209         assertThat(appList).isEmpty()
210     }
211 
212     @Test
213     fun loadApps_isHideWhenDisabledPackageAndDisabledUntilUsed() = runTest {
214         val app = ApplicationInfo().apply {
215             packageName = "is.hide.when.disabled"
216             enabled = true
217             enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
218         }
219         whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
220             .thenReturn(arrayOf(app.packageName))
221         mockInstalledApplications(listOf(app), ADMIN_USER_ID)
222 
223         val appList = repository.loadApps(userId = ADMIN_USER_ID)
224 
225         assertThat(appList).isEmpty()
226     }
227 
228     @Test
229     fun loadApps_isHideWhenDisabledPackageAndEnabled() = runTest {
230         val app = ApplicationInfo().apply {
231             packageName = "is.hide.when.disabled"
232             enabled = true
233         }
234         whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
235             .thenReturn(arrayOf(app.packageName))
236         mockInstalledApplications(listOf(app), ADMIN_USER_ID)
237 
238         val appList = repository.loadApps(userId = ADMIN_USER_ID)
239 
240         assertThat(appList).containsExactly(app)
241     }
242 
243     @Test
244     fun loadApps_disabledByUser() = runTest {
245         val app = ApplicationInfo().apply {
246             packageName = "disabled.by.user"
247             enabled = false
248             enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
249         }
250         mockInstalledApplications(listOf(app), ADMIN_USER_ID)
251 
252         val appList = repository.loadApps(userId = ADMIN_USER_ID)
253 
254         assertThat(appList).containsExactly(app)
255     }
256 
257     @Test
258     fun loadApps_disabledButNotByUser() = runTest {
259         val app = ApplicationInfo().apply {
260             packageName = "disabled"
261             enabled = false
262         }
263         mockInstalledApplications(listOf(app), ADMIN_USER_ID)
264 
265         val appList = repository.loadApps(userId = ADMIN_USER_ID)
266 
267         assertThat(appList).isEmpty()
268     }
269 
270     @Test
271     fun showSystemPredicate_showSystem() = runTest {
272         val app = SYSTEM_APP
273 
274         val showSystemPredicate = getShowSystemPredicate(showSystem = true)
275 
276         assertThat(showSystemPredicate(app)).isTrue()
277     }
278 
279     @Test
280     fun showSystemPredicate_notShowSystemAndIsSystemApp() = runTest {
281         val app = SYSTEM_APP
282 
283         val showSystemPredicate = getShowSystemPredicate(showSystem = false)
284 
285         assertThat(showSystemPredicate(app)).isFalse()
286     }
287 
288     @Test
289     fun showSystemPredicate_isUpdatedSystemApp() = runTest {
290         val app = UPDATED_SYSTEM_APP
291 
292         val showSystemPredicate = getShowSystemPredicate(showSystem = false)
293 
294         assertThat(showSystemPredicate(app)).isTrue()
295     }
296 
297     @Test
298     fun showSystemPredicate_isHome() = runTest {
299         val showSystemPredicate = getShowSystemPredicate(showSystem = false)
300 
301         assertThat(showSystemPredicate(HOME_APP)).isTrue()
302     }
303 
304     @Test
305     fun showSystemPredicate_appInLauncher() = runTest {
306         val showSystemPredicate = getShowSystemPredicate(showSystem = false)
307 
308         assertThat(showSystemPredicate(IN_LAUNCHER_APP)).isTrue()
309     }
310 
311     @Test
312     fun getSystemPackageNames_returnExpectedValues() = runTest {
313         mockInstalledApplications(
314             apps = listOf(
315                 NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP
316             ),
317             userId = ADMIN_USER_ID,
318         )
319 
320         val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames(
321             context = context,
322             userId = ADMIN_USER_ID,
323         )
324 
325         assertThat(systemPackageNames).containsExactly(SYSTEM_APP.packageName)
326     }
327 
328     @Test
329     fun loadAndFilterApps_loadNonSystemApp_returnExpectedValues() = runTest {
330         mockInstalledApplications(
331             apps = listOf(
332                 NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP
333             ),
334             userId = ADMIN_USER_ID,
335         )
336 
337         val appList = repository.loadAndFilterApps(userId = ADMIN_USER_ID, isSystemApp = false)
338 
339         assertThat(appList)
340             .containsExactly(NORMAL_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP)
341     }
342 
343     private suspend fun getShowSystemPredicate(showSystem: Boolean) =
344         repository.showSystemPredicate(
345             userIdFlow = flowOf(ADMIN_USER_ID),
346             showSystemFlow = flowOf(showSystem),
347         ).first()
348 
349     private companion object {
350         const val ADMIN_USER_ID = 0
351         const val MANAGED_PROFILE_USER_ID = 11
352 
353         val NORMAL_APP = ApplicationInfo().apply {
354             packageName = "normal"
355             enabled = true
356         }
357 
358         val INSTANT_APP = ApplicationInfo().apply {
359             packageName = "instant"
360             enabled = true
361             privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
362         }
363 
364         val SYSTEM_APP = ApplicationInfo().apply {
365             packageName = "system.app"
366             flags = ApplicationInfo.FLAG_SYSTEM
367         }
368 
369         val UPDATED_SYSTEM_APP = ApplicationInfo().apply {
370             packageName = "updated.system.app"
371             flags = ApplicationInfo.FLAG_SYSTEM or ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
372         }
373 
374         val HOME_APP = ApplicationInfo().apply {
375             packageName = "home.app"
376             flags = ApplicationInfo.FLAG_SYSTEM
377         }
378 
379         val IN_LAUNCHER_APP = ApplicationInfo().apply {
380             packageName = "app.in.launcher"
381             flags = ApplicationInfo.FLAG_SYSTEM
382         }
383 
384         fun resolveInfoOf(packageName: String) = ResolveInfo().apply {
385             activityInfo = ActivityInfo().apply {
386                 this.packageName = packageName
387             }
388         }
389     }
390 }
391