Loading packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt +51 −6 Original line number Diff line number Diff line Loading @@ -20,9 +20,12 @@ import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.PackageManager.ApplicationInfoFlags import android.content.pm.ResolveInfo import com.android.internal.R import com.android.settingslib.spaprivileged.framework.common.userManager import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine Loading @@ -33,7 +36,11 @@ import kotlinx.coroutines.runBlocking */ internal interface AppListRepository { /** Loads the list of [ApplicationInfo]. */ suspend fun loadApps(userId: Int, showInstantApps: Boolean): List<ApplicationInfo> suspend fun loadApps( userId: Int, showInstantApps: Boolean = false, matchAnyUserForAdmin: Boolean = false, ): List<ApplicationInfo> /** Gets the flow of predicate that could used to filter system app. */ fun showSystemPredicate( Loading Loading @@ -61,10 +68,12 @@ object AppListRepositoryUtil { internal class AppListRepositoryImpl(private val context: Context) : AppListRepository { private val packageManager = context.packageManager private val userManager = context.userManager override suspend fun loadApps( userId: Int, showInstantApps: Boolean, matchAnyUserForAdmin: Boolean, ): List<ApplicationInfo> = coroutineScope { val hiddenSystemModulesDeferred = async { packageManager.getInstalledModules(0) Loading @@ -75,12 +84,8 @@ internal class AppListRepositoryImpl(private val context: Context) : AppListRepo val hideWhenDisabledPackagesDeferred = async { context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames) } val flags = PackageManager.ApplicationInfoFlags.of( (PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() ) val installedApplicationsAsUser = packageManager.getInstalledApplicationsAsUser(flags, userId) getInstalledApplications(userId, matchAnyUserForAdmin) val hiddenSystemModules = hiddenSystemModulesDeferred.await() val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await() Loading @@ -89,6 +94,46 @@ internal class AppListRepositoryImpl(private val context: Context) : AppListRepo } } private suspend fun getInstalledApplications( userId: Int, matchAnyUserForAdmin: Boolean, ): List<ApplicationInfo> { val regularFlags = ApplicationInfoFlags.of( (PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() ) return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) { packageManager.getInstalledApplicationsAsUser(regularFlags, userId) } else { coroutineScope { val deferredPackageNamesInChildProfiles = userManager.getProfileIdsWithDisabled(userId) .filter { it != userId } .map { async { packageManager.getInstalledApplicationsAsUser(regularFlags, it) .map { it.packageName } } } val adminFlags = ApplicationInfoFlags.of( PackageManager.MATCH_ANY_USER.toLong() or regularFlags.value ) val allInstalledApplications = packageManager.getInstalledApplicationsAsUser(adminFlags, userId) val packageNamesInChildProfiles = deferredPackageNamesInChildProfiles .awaitAll() .flatten() .toSet() // If an app is for a child profile and not installed on the owner, not display as // 'not installed for this user' in the owner. This will prevent duplicates of work // only apps showing up in the personal profile. allInstalledApplications.filter { it.installed || it.packageName !in packageNamesInChildProfiles } } } } override fun showSystemPredicate( userIdFlow: Flow<Int>, showSystemFlow: Flow<Boolean>, Loading packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt +6 −2 Original line number Diff line number Diff line Loading @@ -80,12 +80,15 @@ internal open class AppListViewModelImpl<T : AppRecord>( private val scope = viewModelScope + Dispatchers.IO private val userSubGraphsFlow = appListConfig.flow.map { config -> config.userIds.map { userId -> UserSubGraph(userId, config.showInstantApps) } config.userIds.map { userId -> UserSubGraph(userId, config.showInstantApps, config.matchAnyUserForAdmin) } }.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1) private inner class UserSubGraph( private val userId: Int, private val showInstantApps: Boolean, private val matchAnyUserForAdmin: Boolean, ) { private val userIdFlow = flowOf(userId) Loading @@ -110,7 +113,8 @@ internal open class AppListViewModelImpl<T : AppRecord>( fun reloadApps() { scope.launch { appsStateFlow.value = appListRepository.loadApps(userId, showInstantApps) appsStateFlow.value = appListRepository.loadApps(userId, showInstantApps, matchAnyUserForAdmin) } } } Loading packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt +1 −0 Original line number Diff line number Diff line Loading @@ -62,6 +62,7 @@ private const val CONTENT_TYPE_HEADER = "header" data class AppListConfig( val userIds: List<Int>, val showInstantApps: Boolean, val matchAnyUserForAdmin: Boolean, ) data class AppListState( Loading packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt +2 −0 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ fun <T : AppRecord> AppListPage( title: String, listModel: AppListModel<T>, showInstantApps: Boolean = false, matchAnyUserForAdmin: Boolean = false, primaryUserOnly: Boolean = false, noItemMessage: String? = null, moreOptions: @Composable MoreOptionsScope.() -> Unit = {}, Loading @@ -59,6 +60,7 @@ fun <T : AppRecord> AppListPage( config = AppListConfig( userIds = userGroup.userInfos.map { it.id }, showInstantApps = showInstantApps, matchAnyUserForAdmin = matchAnyUserForAdmin, ), listModel = listModel, state = AppListState( Loading packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt +131 −32 Original line number Diff line number Diff line Loading @@ -23,9 +23,13 @@ import android.content.pm.PackageManager import android.content.pm.PackageManager.ApplicationInfoFlags import android.content.pm.PackageManager.ResolveInfoFlags import android.content.pm.ResolveInfo import android.content.pm.UserInfo import android.content.res.Resources import android.os.UserManager import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.internal.R import com.android.settingslib.spaprivileged.framework.common.userManager import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first Loading @@ -35,10 +39,13 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.eq import org.mockito.Mockito.verify import org.mockito.Spy import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule import org.mockito.Mockito.`when` as whenever Loading @@ -49,8 +56,8 @@ class AppListRepositoryTest { @get:Rule val mockito: MockitoRule = MockitoJUnit.rule() @Mock private lateinit var context: Context @Spy private val context: Context = ApplicationProvider.getApplicationContext() @Mock private lateinit var resources: Resources Loading @@ -58,6 +65,9 @@ class AppListRepositoryTest { @Mock private lateinit var packageManager: PackageManager @Mock private lateinit var userManager: UserManager private lateinit var repository: AppListRepository @Before Loading @@ -66,36 +76,116 @@ class AppListRepositoryTest { whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)) .thenReturn(emptyArray()) whenever(context.packageManager).thenReturn(packageManager) whenever(context.userManager).thenReturn(userManager) whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList()) whenever( packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID)) packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), anyInt()) ).thenReturn(emptyList()) whenever(userManager.getUserInfo(ADMIN_USER_ID)).thenReturn(UserInfo().apply { flags = UserInfo.FLAG_ADMIN }) whenever(userManager.getProfileIdsWithDisabled(ADMIN_USER_ID)) .thenReturn(intArrayOf(ADMIN_USER_ID, MANAGED_PROFILE_USER_ID)) repository = AppListRepositoryImpl(context) } private fun mockInstalledApplications(apps: List<ApplicationInfo>) { private fun mockInstalledApplications(apps: List<ApplicationInfo>, userId: Int) { whenever( packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID)) packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(userId)) ).thenReturn(apps) } @Test fun loadApps_notShowInstantApps() = runTest { mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP)) mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID) val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) val appList = repository.loadApps( userId = ADMIN_USER_ID, showInstantApps = false, ) assertThat(appListFlow).containsExactly(NORMAL_APP) assertThat(appList).containsExactly(NORMAL_APP) } @Test fun loadApps_showInstantApps() = runTest { mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP)) mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID) val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = true) val appList = repository.loadApps( userId = ADMIN_USER_ID, showInstantApps = true, ) assertThat(appListFlow).containsExactly(NORMAL_APP, INSTANT_APP) assertThat(appList).containsExactly(NORMAL_APP, INSTANT_APP) } @Test fun loadApps_notMatchAnyUserForAdmin_withRegularFlags() = runTest { mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID) val appList = repository.loadApps( userId = ADMIN_USER_ID, matchAnyUserForAdmin = false, ) assertThat(appList).containsExactly(NORMAL_APP) val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java) verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID)) assertThat(flags.value.value).isEqualTo( PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS ) } @Test fun loadApps_matchAnyUserForAdmin_withMatchAnyUserFlag() = runTest { mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID) val appList = repository.loadApps( userId = ADMIN_USER_ID, matchAnyUserForAdmin = true, ) assertThat(appList).containsExactly(NORMAL_APP) val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java) verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID)) assertThat(flags.value.value and PackageManager.MATCH_ANY_USER.toLong()).isGreaterThan(0L) } @Test fun loadApps_matchAnyUserForAdminAndInstalledOnManagedProfileOnly_notDisplayed() = runTest { val managedProfileOnlyPackageName = "installed.on.managed.profile.only" mockInstalledApplications(listOf(ApplicationInfo().apply { packageName = managedProfileOnlyPackageName }), ADMIN_USER_ID) mockInstalledApplications(listOf(ApplicationInfo().apply { packageName = managedProfileOnlyPackageName flags = ApplicationInfo.FLAG_INSTALLED }), MANAGED_PROFILE_USER_ID) val appList = repository.loadApps( userId = ADMIN_USER_ID, matchAnyUserForAdmin = true, ) assertThat(appList).isEmpty() } @Test fun loadApps_matchAnyUserForAdminAndInstalledOnSecondaryUserOnly_displayed() = runTest { val secondaryUserOnlyApp = ApplicationInfo().apply { packageName = "installed.on.secondary.user.only" } mockInstalledApplications(listOf(secondaryUserOnlyApp), ADMIN_USER_ID) mockInstalledApplications(emptyList(), MANAGED_PROFILE_USER_ID) val appList = repository.loadApps( userId = ADMIN_USER_ID, matchAnyUserForAdmin = true, ) assertThat(appList).containsExactly(secondaryUserOnlyApp) } @Test Loading @@ -106,11 +196,11 @@ class AppListRepositoryTest { } whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)) .thenReturn(arrayOf(app.packageName)) mockInstalledApplications(listOf(app)) mockInstalledApplications(listOf(app), ADMIN_USER_ID) val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) val appList = repository.loadApps(userId = ADMIN_USER_ID) assertThat(appListFlow).isEmpty() assertThat(appList).isEmpty() } @Test Loading @@ -122,11 +212,11 @@ class AppListRepositoryTest { } whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)) .thenReturn(arrayOf(app.packageName)) mockInstalledApplications(listOf(app)) mockInstalledApplications(listOf(app), ADMIN_USER_ID) val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) val appList = repository.loadApps(userId = ADMIN_USER_ID) assertThat(appListFlow).isEmpty() assertThat(appList).isEmpty() } @Test Loading @@ -137,11 +227,11 @@ class AppListRepositoryTest { } whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)) .thenReturn(arrayOf(app.packageName)) mockInstalledApplications(listOf(app)) mockInstalledApplications(listOf(app), ADMIN_USER_ID) val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) val appList = repository.loadApps(userId = ADMIN_USER_ID) assertThat(appListFlow).containsExactly(app) assertThat(appList).containsExactly(app) } @Test Loading @@ -151,11 +241,11 @@ class AppListRepositoryTest { enabled = false enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER } mockInstalledApplications(listOf(app)) mockInstalledApplications(listOf(app), ADMIN_USER_ID) val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) val appList = repository.loadApps(userId = ADMIN_USER_ID) assertThat(appListFlow).containsExactly(app) assertThat(appList).containsExactly(app) } @Test Loading @@ -164,11 +254,11 @@ class AppListRepositoryTest { packageName = "disabled" enabled = false } mockInstalledApplications(listOf(app)) mockInstalledApplications(listOf(app), ADMIN_USER_ID) val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) val appList = repository.loadApps(userId = ADMIN_USER_ID) assertThat(appListFlow).isEmpty() assertThat(appList).isEmpty() } @Test Loading Loading @@ -219,7 +309,11 @@ class AppListRepositoryTest { val app = IN_LAUNCHER_APP whenever( packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID)) packageManager.queryIntentActivitiesAsUser( any(), any<ResolveInfoFlags>(), eq(ADMIN_USER_ID) ) ).thenReturn(listOf(resolveInfoOf(packageName = app.packageName))) val showSystemPredicate = getShowSystemPredicate(showSystem = false) Loading @@ -229,12 +323,16 @@ class AppListRepositoryTest { @Test fun getSystemPackageNames_returnExpectedValues() = runTest { mockInstalledApplications(listOf( NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP)) mockInstalledApplications( apps = listOf( NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP ), userId = ADMIN_USER_ID, ) val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames( context = context, userId = USER_ID, userId = ADMIN_USER_ID, showInstantApps = false, ) Loading @@ -243,12 +341,13 @@ class AppListRepositoryTest { private suspend fun getShowSystemPredicate(showSystem: Boolean) = repository.showSystemPredicate( userIdFlow = flowOf(USER_ID), userIdFlow = flowOf(ADMIN_USER_ID), showSystemFlow = flowOf(showSystem), ).first() private companion object { const val USER_ID = 0 const val ADMIN_USER_ID = 0 const val MANAGED_PROFILE_USER_ID = 11 val NORMAL_APP = ApplicationInfo().apply { packageName = "normal" Loading Loading
packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt +51 −6 Original line number Diff line number Diff line Loading @@ -20,9 +20,12 @@ import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.PackageManager.ApplicationInfoFlags import android.content.pm.ResolveInfo import com.android.internal.R import com.android.settingslib.spaprivileged.framework.common.userManager import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine Loading @@ -33,7 +36,11 @@ import kotlinx.coroutines.runBlocking */ internal interface AppListRepository { /** Loads the list of [ApplicationInfo]. */ suspend fun loadApps(userId: Int, showInstantApps: Boolean): List<ApplicationInfo> suspend fun loadApps( userId: Int, showInstantApps: Boolean = false, matchAnyUserForAdmin: Boolean = false, ): List<ApplicationInfo> /** Gets the flow of predicate that could used to filter system app. */ fun showSystemPredicate( Loading Loading @@ -61,10 +68,12 @@ object AppListRepositoryUtil { internal class AppListRepositoryImpl(private val context: Context) : AppListRepository { private val packageManager = context.packageManager private val userManager = context.userManager override suspend fun loadApps( userId: Int, showInstantApps: Boolean, matchAnyUserForAdmin: Boolean, ): List<ApplicationInfo> = coroutineScope { val hiddenSystemModulesDeferred = async { packageManager.getInstalledModules(0) Loading @@ -75,12 +84,8 @@ internal class AppListRepositoryImpl(private val context: Context) : AppListRepo val hideWhenDisabledPackagesDeferred = async { context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames) } val flags = PackageManager.ApplicationInfoFlags.of( (PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() ) val installedApplicationsAsUser = packageManager.getInstalledApplicationsAsUser(flags, userId) getInstalledApplications(userId, matchAnyUserForAdmin) val hiddenSystemModules = hiddenSystemModulesDeferred.await() val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await() Loading @@ -89,6 +94,46 @@ internal class AppListRepositoryImpl(private val context: Context) : AppListRepo } } private suspend fun getInstalledApplications( userId: Int, matchAnyUserForAdmin: Boolean, ): List<ApplicationInfo> { val regularFlags = ApplicationInfoFlags.of( (PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() ) return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) { packageManager.getInstalledApplicationsAsUser(regularFlags, userId) } else { coroutineScope { val deferredPackageNamesInChildProfiles = userManager.getProfileIdsWithDisabled(userId) .filter { it != userId } .map { async { packageManager.getInstalledApplicationsAsUser(regularFlags, it) .map { it.packageName } } } val adminFlags = ApplicationInfoFlags.of( PackageManager.MATCH_ANY_USER.toLong() or regularFlags.value ) val allInstalledApplications = packageManager.getInstalledApplicationsAsUser(adminFlags, userId) val packageNamesInChildProfiles = deferredPackageNamesInChildProfiles .awaitAll() .flatten() .toSet() // If an app is for a child profile and not installed on the owner, not display as // 'not installed for this user' in the owner. This will prevent duplicates of work // only apps showing up in the personal profile. allInstalledApplications.filter { it.installed || it.packageName !in packageNamesInChildProfiles } } } } override fun showSystemPredicate( userIdFlow: Flow<Int>, showSystemFlow: Flow<Boolean>, Loading
packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt +6 −2 Original line number Diff line number Diff line Loading @@ -80,12 +80,15 @@ internal open class AppListViewModelImpl<T : AppRecord>( private val scope = viewModelScope + Dispatchers.IO private val userSubGraphsFlow = appListConfig.flow.map { config -> config.userIds.map { userId -> UserSubGraph(userId, config.showInstantApps) } config.userIds.map { userId -> UserSubGraph(userId, config.showInstantApps, config.matchAnyUserForAdmin) } }.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1) private inner class UserSubGraph( private val userId: Int, private val showInstantApps: Boolean, private val matchAnyUserForAdmin: Boolean, ) { private val userIdFlow = flowOf(userId) Loading @@ -110,7 +113,8 @@ internal open class AppListViewModelImpl<T : AppRecord>( fun reloadApps() { scope.launch { appsStateFlow.value = appListRepository.loadApps(userId, showInstantApps) appsStateFlow.value = appListRepository.loadApps(userId, showInstantApps, matchAnyUserForAdmin) } } } Loading
packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt +1 −0 Original line number Diff line number Diff line Loading @@ -62,6 +62,7 @@ private const val CONTENT_TYPE_HEADER = "header" data class AppListConfig( val userIds: List<Int>, val showInstantApps: Boolean, val matchAnyUserForAdmin: Boolean, ) data class AppListState( Loading
packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt +2 −0 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ fun <T : AppRecord> AppListPage( title: String, listModel: AppListModel<T>, showInstantApps: Boolean = false, matchAnyUserForAdmin: Boolean = false, primaryUserOnly: Boolean = false, noItemMessage: String? = null, moreOptions: @Composable MoreOptionsScope.() -> Unit = {}, Loading @@ -59,6 +60,7 @@ fun <T : AppRecord> AppListPage( config = AppListConfig( userIds = userGroup.userInfos.map { it.id }, showInstantApps = showInstantApps, matchAnyUserForAdmin = matchAnyUserForAdmin, ), listModel = listModel, state = AppListState( Loading
packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt +131 −32 Original line number Diff line number Diff line Loading @@ -23,9 +23,13 @@ import android.content.pm.PackageManager import android.content.pm.PackageManager.ApplicationInfoFlags import android.content.pm.PackageManager.ResolveInfoFlags import android.content.pm.ResolveInfo import android.content.pm.UserInfo import android.content.res.Resources import android.os.UserManager import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.internal.R import com.android.settingslib.spaprivileged.framework.common.userManager import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first Loading @@ -35,10 +39,13 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.eq import org.mockito.Mockito.verify import org.mockito.Spy import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule import org.mockito.Mockito.`when` as whenever Loading @@ -49,8 +56,8 @@ class AppListRepositoryTest { @get:Rule val mockito: MockitoRule = MockitoJUnit.rule() @Mock private lateinit var context: Context @Spy private val context: Context = ApplicationProvider.getApplicationContext() @Mock private lateinit var resources: Resources Loading @@ -58,6 +65,9 @@ class AppListRepositoryTest { @Mock private lateinit var packageManager: PackageManager @Mock private lateinit var userManager: UserManager private lateinit var repository: AppListRepository @Before Loading @@ -66,36 +76,116 @@ class AppListRepositoryTest { whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)) .thenReturn(emptyArray()) whenever(context.packageManager).thenReturn(packageManager) whenever(context.userManager).thenReturn(userManager) whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList()) whenever( packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID)) packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), anyInt()) ).thenReturn(emptyList()) whenever(userManager.getUserInfo(ADMIN_USER_ID)).thenReturn(UserInfo().apply { flags = UserInfo.FLAG_ADMIN }) whenever(userManager.getProfileIdsWithDisabled(ADMIN_USER_ID)) .thenReturn(intArrayOf(ADMIN_USER_ID, MANAGED_PROFILE_USER_ID)) repository = AppListRepositoryImpl(context) } private fun mockInstalledApplications(apps: List<ApplicationInfo>) { private fun mockInstalledApplications(apps: List<ApplicationInfo>, userId: Int) { whenever( packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID)) packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(userId)) ).thenReturn(apps) } @Test fun loadApps_notShowInstantApps() = runTest { mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP)) mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID) val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) val appList = repository.loadApps( userId = ADMIN_USER_ID, showInstantApps = false, ) assertThat(appListFlow).containsExactly(NORMAL_APP) assertThat(appList).containsExactly(NORMAL_APP) } @Test fun loadApps_showInstantApps() = runTest { mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP)) mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID) val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = true) val appList = repository.loadApps( userId = ADMIN_USER_ID, showInstantApps = true, ) assertThat(appListFlow).containsExactly(NORMAL_APP, INSTANT_APP) assertThat(appList).containsExactly(NORMAL_APP, INSTANT_APP) } @Test fun loadApps_notMatchAnyUserForAdmin_withRegularFlags() = runTest { mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID) val appList = repository.loadApps( userId = ADMIN_USER_ID, matchAnyUserForAdmin = false, ) assertThat(appList).containsExactly(NORMAL_APP) val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java) verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID)) assertThat(flags.value.value).isEqualTo( PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS ) } @Test fun loadApps_matchAnyUserForAdmin_withMatchAnyUserFlag() = runTest { mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID) val appList = repository.loadApps( userId = ADMIN_USER_ID, matchAnyUserForAdmin = true, ) assertThat(appList).containsExactly(NORMAL_APP) val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java) verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID)) assertThat(flags.value.value and PackageManager.MATCH_ANY_USER.toLong()).isGreaterThan(0L) } @Test fun loadApps_matchAnyUserForAdminAndInstalledOnManagedProfileOnly_notDisplayed() = runTest { val managedProfileOnlyPackageName = "installed.on.managed.profile.only" mockInstalledApplications(listOf(ApplicationInfo().apply { packageName = managedProfileOnlyPackageName }), ADMIN_USER_ID) mockInstalledApplications(listOf(ApplicationInfo().apply { packageName = managedProfileOnlyPackageName flags = ApplicationInfo.FLAG_INSTALLED }), MANAGED_PROFILE_USER_ID) val appList = repository.loadApps( userId = ADMIN_USER_ID, matchAnyUserForAdmin = true, ) assertThat(appList).isEmpty() } @Test fun loadApps_matchAnyUserForAdminAndInstalledOnSecondaryUserOnly_displayed() = runTest { val secondaryUserOnlyApp = ApplicationInfo().apply { packageName = "installed.on.secondary.user.only" } mockInstalledApplications(listOf(secondaryUserOnlyApp), ADMIN_USER_ID) mockInstalledApplications(emptyList(), MANAGED_PROFILE_USER_ID) val appList = repository.loadApps( userId = ADMIN_USER_ID, matchAnyUserForAdmin = true, ) assertThat(appList).containsExactly(secondaryUserOnlyApp) } @Test Loading @@ -106,11 +196,11 @@ class AppListRepositoryTest { } whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)) .thenReturn(arrayOf(app.packageName)) mockInstalledApplications(listOf(app)) mockInstalledApplications(listOf(app), ADMIN_USER_ID) val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) val appList = repository.loadApps(userId = ADMIN_USER_ID) assertThat(appListFlow).isEmpty() assertThat(appList).isEmpty() } @Test Loading @@ -122,11 +212,11 @@ class AppListRepositoryTest { } whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)) .thenReturn(arrayOf(app.packageName)) mockInstalledApplications(listOf(app)) mockInstalledApplications(listOf(app), ADMIN_USER_ID) val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) val appList = repository.loadApps(userId = ADMIN_USER_ID) assertThat(appListFlow).isEmpty() assertThat(appList).isEmpty() } @Test Loading @@ -137,11 +227,11 @@ class AppListRepositoryTest { } whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)) .thenReturn(arrayOf(app.packageName)) mockInstalledApplications(listOf(app)) mockInstalledApplications(listOf(app), ADMIN_USER_ID) val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) val appList = repository.loadApps(userId = ADMIN_USER_ID) assertThat(appListFlow).containsExactly(app) assertThat(appList).containsExactly(app) } @Test Loading @@ -151,11 +241,11 @@ class AppListRepositoryTest { enabled = false enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER } mockInstalledApplications(listOf(app)) mockInstalledApplications(listOf(app), ADMIN_USER_ID) val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) val appList = repository.loadApps(userId = ADMIN_USER_ID) assertThat(appListFlow).containsExactly(app) assertThat(appList).containsExactly(app) } @Test Loading @@ -164,11 +254,11 @@ class AppListRepositoryTest { packageName = "disabled" enabled = false } mockInstalledApplications(listOf(app)) mockInstalledApplications(listOf(app), ADMIN_USER_ID) val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) val appList = repository.loadApps(userId = ADMIN_USER_ID) assertThat(appListFlow).isEmpty() assertThat(appList).isEmpty() } @Test Loading Loading @@ -219,7 +309,11 @@ class AppListRepositoryTest { val app = IN_LAUNCHER_APP whenever( packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID)) packageManager.queryIntentActivitiesAsUser( any(), any<ResolveInfoFlags>(), eq(ADMIN_USER_ID) ) ).thenReturn(listOf(resolveInfoOf(packageName = app.packageName))) val showSystemPredicate = getShowSystemPredicate(showSystem = false) Loading @@ -229,12 +323,16 @@ class AppListRepositoryTest { @Test fun getSystemPackageNames_returnExpectedValues() = runTest { mockInstalledApplications(listOf( NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP)) mockInstalledApplications( apps = listOf( NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP ), userId = ADMIN_USER_ID, ) val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames( context = context, userId = USER_ID, userId = ADMIN_USER_ID, showInstantApps = false, ) Loading @@ -243,12 +341,13 @@ class AppListRepositoryTest { private suspend fun getShowSystemPredicate(showSystem: Boolean) = repository.showSystemPredicate( userIdFlow = flowOf(USER_ID), userIdFlow = flowOf(ADMIN_USER_ID), showSystemFlow = flowOf(showSystem), ).first() private companion object { const val USER_ID = 0 const val ADMIN_USER_ID = 0 const val MANAGED_PROFILE_USER_ID = 11 val NORMAL_APP = ApplicationInfo().apply { packageName = "normal" Loading