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