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