Loading packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt +192 −84 Original line number Diff line number Diff line Loading @@ -28,6 +28,8 @@ import android.content.pm.ResolveInfo import android.util.Log import com.android.internal.R import com.android.settingslib.spaprivileged.framework.common.userManager import java.util.concurrent.ConcurrentHashMap import kotlinx.coroutines.Deferred import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope Loading @@ -35,9 +37,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.runBlocking /** * The repository to load the App List data. */ /** The repository to load the App List data. */ interface AppListRepository { /** Loads the list of [ApplicationInfo]. */ suspend fun loadApps( Loading Loading @@ -65,9 +65,7 @@ interface AppListRepository { ): List<ApplicationInfo> = listOf() } /** * Util for app list repository. */ /** Util for app list repository. */ object AppListRepositoryUtil { /** Gets the system app package names. */ @JvmStatic Loading @@ -75,23 +73,21 @@ object AppListRepositoryUtil { AppListRepositoryImpl(context).getSystemPackageNamesBlocking(userId) } /** * This constructor is visible for tests only in order to override `featureFlags`. */ class AppListRepositoryImpl( internal class AppListRepositoryImplHelper( private val context: Context, private val featureFlags: FeatureFlags ) : AppListRepository { private val featureFlags: FeatureFlags, ) { private val packageManager = context.packageManager private val userManager = context.userManager constructor(context: Context) : this(context, FeatureFlagsImpl()) override suspend fun loadApps( suspend fun loadApps( userId: Int, loadInstantApps: Boolean, matchAnyUserForAdmin: Boolean, ): List<ApplicationInfo> = try { ): List<ApplicationInfo> = try { coroutineScope { // TODO(b/382016780): to be removed after flag cleanup. val hiddenSystemModulesDeferred = async { packageManager.getHiddenSystemModules() } Loading @@ -117,36 +113,36 @@ class AppListRepositoryImpl( userId: Int, matchAnyUserForAdmin: Boolean, ): List<ApplicationInfo> { val disabledComponentsFlag = (PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() val archivedPackagesFlag: Long = if (isArchivingEnabled(featureFlags)) PackageManager.MATCH_ARCHIVED_PACKAGES else 0L val regularFlags = ApplicationInfoFlags.of( disabledComponentsFlag or archivedPackagesFlag ) val disabledComponentsFlag = (PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS) .toLong() val archivedPackagesFlag: Long = if (isArchivingEnabled(featureFlags)) PackageManager.MATCH_ARCHIVED_PACKAGES else 0L val regularFlags = ApplicationInfoFlags.of(disabledComponentsFlag or archivedPackagesFlag) return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) { packageManager.getInstalledApplicationsAsUser(regularFlags, userId) } else { coroutineScope { val deferredPackageNamesInChildProfiles = userManager.getProfileIdsWithDisabled(userId) userManager .getProfileIdsWithDisabled(userId) .filter { it != userId } .map { async { packageManager.getInstalledApplicationsAsUser(regularFlags, it) packageManager .getInstalledApplicationsAsUser(regularFlags, it) .map { it.packageName } } } val adminFlags = ApplicationInfoFlags.of( val adminFlags = ApplicationInfoFlags.of( PackageManager.MATCH_ANY_USER.toLong() or regularFlags.value ) val allInstalledApplications = packageManager.getInstalledApplicationsAsUser(adminFlags, userId) val packageNamesInChildProfiles = deferredPackageNamesInChildProfiles .awaitAll() .flatten() .toSet() 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. Loading @@ -157,30 +153,23 @@ class AppListRepositoryImpl( } } private fun isArchivingEnabled(featureFlags: FeatureFlags) = featureFlags.archiving() override fun showSystemPredicate( userIdFlow: Flow<Int>, showSystemFlow: Flow<Boolean>, ): Flow<(app: ApplicationInfo) -> Boolean> = userIdFlow.combine(showSystemFlow, ::showSystemPredicate) private fun isArchivingEnabled(featureFlags: FeatureFlags) = featureFlags.archiving() override fun getSystemPackageNamesBlocking(userId: Int) = runBlocking { fun getSystemPackageNamesBlocking(userId: Int) = runBlocking { loadAndFilterApps(userId = userId, isSystemApp = true).map { it.packageName }.toSet() } override suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean) = coroutineScope { val loadAppsDeferred = async { loadApps(userId) } suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean) = coroutineScope { val loadAppsDeferred = async { loadApps(userId, false, false) } val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) loadAppsDeferred.await().filter { app -> isSystemApp(app, homeOrLauncherPackages) == isSystemApp } } override suspend fun loadAndMaybeExcludeSystemApps(userId: Int, excludeSystemApp: Boolean) = suspend fun loadAndMaybeExcludeSystemApps(userId: Int, excludeSystemApp: Boolean) = coroutineScope { val loadAppsDeferred = async { loadApps(userId) } val loadAppsDeferred = async { loadApps(userId, false, false) } if (excludeSystemApp) { val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) loadAppsDeferred.await().filter { app -> !isSystemApp(app, homeOrLauncherPackages) } Loading @@ -189,25 +178,18 @@ class AppListRepositoryImpl( } } private suspend fun showSystemPredicate( userId: Int, showSystem: Boolean, ): (app: ApplicationInfo) -> Boolean { if (showSystem) return { true } val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) return { app -> !isSystemApp(app, homeOrLauncherPackages) } } private suspend fun loadHomeOrLauncherPackages(userId: Int): Set<String> { internal suspend fun loadHomeOrLauncherPackages(userId: Int): Set<String> { val launchIntent = Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER) // If we do not specify MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE, system will // derive and update the flags according to the user's lock state. When the user is locked, // components with ComponentInfo#directBootAware == false will be filtered. We should // explicitly include both direct boot aware and unaware component here. val flags = PackageManager.ResolveInfoFlags.of( val flags = PackageManager.ResolveInfoFlags.of( (PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_DIRECT_BOOT_AWARE or PackageManager.MATCH_DIRECT_BOOT_UNAWARE).toLong() PackageManager.MATCH_DIRECT_BOOT_UNAWARE) .toLong() ) return coroutineScope { val launcherActivities = async { Loading Loading @@ -235,14 +217,15 @@ class AppListRepositoryImpl( } companion object { private const val TAG = "AppListRepository" private const val TAG = "AppListRepositoryImplHelper" // TODO(b/382016780): to be removed after flag cleanup. private fun ApplicationInfo.isInAppList( showInstantApps: Boolean, hiddenSystemModules: Set<String>, hideWhenDisabledPackages: Array<String>, ) = when { ) = when { !showInstantApps && isInstantApp -> false !Flags.removeHiddenModuleUsage() && (packageName in hiddenSystemModules) -> false packageName in hideWhenDisabledPackages -> enabled && !isDisabledUntilUsed Loading @@ -251,3 +234,128 @@ class AppListRepositoryImpl( } } } /** This constructor is visible for tests only in order to override `featureFlags`. */ class AppListRepositoryImpl(context: Context, private val featureFlags: FeatureFlags) : AppListRepository { private val helper = AppListRepositoryImplHelper(context, featureFlags) constructor(context: Context) : this(context, FeatureFlagsImpl()) companion object { @JvmStatic @Volatile var useCaching = false set(value) { if (!value) { appsCache.clear() homeOrLauncherPackagesCache.clear() } field = value } private val appsCache = ConcurrentHashMap<AppsCacheKey, Deferred<List<ApplicationInfo>>>() private data class AppsCacheKey( val userId: Int, val loadInstantApps: Boolean, val matchAnyUserForAdmin: Boolean, ) private val homeOrLauncherPackagesCache = ConcurrentHashMap<Int, Deferred<Set<String>>>() } private suspend fun <K, V> getOrAsync( cache: ConcurrentHashMap<K, Deferred<V>>, key: K, block: suspend () -> V, ): Deferred<V> = coroutineScope { cache.computeIfAbsent(key) { this@coroutineScope.async { block() } } } override suspend fun loadApps( userId: Int, loadInstantApps: Boolean, matchAnyUserForAdmin: Boolean, ): List<ApplicationInfo> { if (!useCaching) { return helper.loadApps(userId, loadInstantApps, matchAnyUserForAdmin) } val key = AppsCacheKey(userId, loadInstantApps, matchAnyUserForAdmin) return getOrAsync(appsCache, key) { helper.loadApps(userId, loadInstantApps, matchAnyUserForAdmin) } .await() } override fun showSystemPredicate( userIdFlow: Flow<Int>, showSystemFlow: Flow<Boolean>, ): Flow<(app: ApplicationInfo) -> Boolean> = userIdFlow.combine(showSystemFlow, ::showSystemPredicate) override fun getSystemPackageNamesBlocking(userId: Int): Set<String> { if (!useCaching) { return helper.getSystemPackageNamesBlocking(userId) } return runBlocking { loadAndFilterApps(userId = userId, isSystemApp = true).map { it.packageName }.toSet() } } override suspend fun loadAndFilterApps( userId: Int, isSystemApp: Boolean, ): List<ApplicationInfo> { if (!useCaching) { return helper.loadAndFilterApps(userId, isSystemApp) } return coroutineScope { val loadAppsDeferred = async { loadApps(userId) } val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) loadAppsDeferred.await().filter { app -> isSystemApp(app, homeOrLauncherPackages) == isSystemApp } } } override suspend fun loadAndMaybeExcludeSystemApps( userId: Int, excludeSystemApp: Boolean, ): List<ApplicationInfo> { if (!useCaching) { return helper.loadAndMaybeExcludeSystemApps(userId, excludeSystemApp) } return coroutineScope { val loadAppsDeferred = async { loadApps(userId) } if (excludeSystemApp) { val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) loadAppsDeferred.await().filter { app -> !isSystemApp(app, homeOrLauncherPackages) } } else { loadAppsDeferred.await() } } } private suspend fun showSystemPredicate( userId: Int, showSystem: Boolean, ): (app: ApplicationInfo) -> Boolean { if (showSystem) return { true } val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) return { app -> !isSystemApp(app, homeOrLauncherPackages) } } private suspend fun loadHomeOrLauncherPackages(userId: Int): Set<String> { if (!useCaching) { return helper.loadHomeOrLauncherPackages(userId) } return getOrAsync(homeOrLauncherPackagesCache, userId) { helper.loadHomeOrLauncherPackages(userId) } .await() } private fun isSystemApp(app: ApplicationInfo, homeOrLauncherPackages: Set<String>): Boolean = app.isSystemApp && !app.isUpdatedSystemApp && app.packageName !in homeOrLauncherPackages } packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt +253 −176 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt +192 −84 Original line number Diff line number Diff line Loading @@ -28,6 +28,8 @@ import android.content.pm.ResolveInfo import android.util.Log import com.android.internal.R import com.android.settingslib.spaprivileged.framework.common.userManager import java.util.concurrent.ConcurrentHashMap import kotlinx.coroutines.Deferred import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope Loading @@ -35,9 +37,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.runBlocking /** * The repository to load the App List data. */ /** The repository to load the App List data. */ interface AppListRepository { /** Loads the list of [ApplicationInfo]. */ suspend fun loadApps( Loading Loading @@ -65,9 +65,7 @@ interface AppListRepository { ): List<ApplicationInfo> = listOf() } /** * Util for app list repository. */ /** Util for app list repository. */ object AppListRepositoryUtil { /** Gets the system app package names. */ @JvmStatic Loading @@ -75,23 +73,21 @@ object AppListRepositoryUtil { AppListRepositoryImpl(context).getSystemPackageNamesBlocking(userId) } /** * This constructor is visible for tests only in order to override `featureFlags`. */ class AppListRepositoryImpl( internal class AppListRepositoryImplHelper( private val context: Context, private val featureFlags: FeatureFlags ) : AppListRepository { private val featureFlags: FeatureFlags, ) { private val packageManager = context.packageManager private val userManager = context.userManager constructor(context: Context) : this(context, FeatureFlagsImpl()) override suspend fun loadApps( suspend fun loadApps( userId: Int, loadInstantApps: Boolean, matchAnyUserForAdmin: Boolean, ): List<ApplicationInfo> = try { ): List<ApplicationInfo> = try { coroutineScope { // TODO(b/382016780): to be removed after flag cleanup. val hiddenSystemModulesDeferred = async { packageManager.getHiddenSystemModules() } Loading @@ -117,36 +113,36 @@ class AppListRepositoryImpl( userId: Int, matchAnyUserForAdmin: Boolean, ): List<ApplicationInfo> { val disabledComponentsFlag = (PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() val archivedPackagesFlag: Long = if (isArchivingEnabled(featureFlags)) PackageManager.MATCH_ARCHIVED_PACKAGES else 0L val regularFlags = ApplicationInfoFlags.of( disabledComponentsFlag or archivedPackagesFlag ) val disabledComponentsFlag = (PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS) .toLong() val archivedPackagesFlag: Long = if (isArchivingEnabled(featureFlags)) PackageManager.MATCH_ARCHIVED_PACKAGES else 0L val regularFlags = ApplicationInfoFlags.of(disabledComponentsFlag or archivedPackagesFlag) return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) { packageManager.getInstalledApplicationsAsUser(regularFlags, userId) } else { coroutineScope { val deferredPackageNamesInChildProfiles = userManager.getProfileIdsWithDisabled(userId) userManager .getProfileIdsWithDisabled(userId) .filter { it != userId } .map { async { packageManager.getInstalledApplicationsAsUser(regularFlags, it) packageManager .getInstalledApplicationsAsUser(regularFlags, it) .map { it.packageName } } } val adminFlags = ApplicationInfoFlags.of( val adminFlags = ApplicationInfoFlags.of( PackageManager.MATCH_ANY_USER.toLong() or regularFlags.value ) val allInstalledApplications = packageManager.getInstalledApplicationsAsUser(adminFlags, userId) val packageNamesInChildProfiles = deferredPackageNamesInChildProfiles .awaitAll() .flatten() .toSet() 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. Loading @@ -157,30 +153,23 @@ class AppListRepositoryImpl( } } private fun isArchivingEnabled(featureFlags: FeatureFlags) = featureFlags.archiving() override fun showSystemPredicate( userIdFlow: Flow<Int>, showSystemFlow: Flow<Boolean>, ): Flow<(app: ApplicationInfo) -> Boolean> = userIdFlow.combine(showSystemFlow, ::showSystemPredicate) private fun isArchivingEnabled(featureFlags: FeatureFlags) = featureFlags.archiving() override fun getSystemPackageNamesBlocking(userId: Int) = runBlocking { fun getSystemPackageNamesBlocking(userId: Int) = runBlocking { loadAndFilterApps(userId = userId, isSystemApp = true).map { it.packageName }.toSet() } override suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean) = coroutineScope { val loadAppsDeferred = async { loadApps(userId) } suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean) = coroutineScope { val loadAppsDeferred = async { loadApps(userId, false, false) } val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) loadAppsDeferred.await().filter { app -> isSystemApp(app, homeOrLauncherPackages) == isSystemApp } } override suspend fun loadAndMaybeExcludeSystemApps(userId: Int, excludeSystemApp: Boolean) = suspend fun loadAndMaybeExcludeSystemApps(userId: Int, excludeSystemApp: Boolean) = coroutineScope { val loadAppsDeferred = async { loadApps(userId) } val loadAppsDeferred = async { loadApps(userId, false, false) } if (excludeSystemApp) { val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) loadAppsDeferred.await().filter { app -> !isSystemApp(app, homeOrLauncherPackages) } Loading @@ -189,25 +178,18 @@ class AppListRepositoryImpl( } } private suspend fun showSystemPredicate( userId: Int, showSystem: Boolean, ): (app: ApplicationInfo) -> Boolean { if (showSystem) return { true } val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) return { app -> !isSystemApp(app, homeOrLauncherPackages) } } private suspend fun loadHomeOrLauncherPackages(userId: Int): Set<String> { internal suspend fun loadHomeOrLauncherPackages(userId: Int): Set<String> { val launchIntent = Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER) // If we do not specify MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE, system will // derive and update the flags according to the user's lock state. When the user is locked, // components with ComponentInfo#directBootAware == false will be filtered. We should // explicitly include both direct boot aware and unaware component here. val flags = PackageManager.ResolveInfoFlags.of( val flags = PackageManager.ResolveInfoFlags.of( (PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_DIRECT_BOOT_AWARE or PackageManager.MATCH_DIRECT_BOOT_UNAWARE).toLong() PackageManager.MATCH_DIRECT_BOOT_UNAWARE) .toLong() ) return coroutineScope { val launcherActivities = async { Loading Loading @@ -235,14 +217,15 @@ class AppListRepositoryImpl( } companion object { private const val TAG = "AppListRepository" private const val TAG = "AppListRepositoryImplHelper" // TODO(b/382016780): to be removed after flag cleanup. private fun ApplicationInfo.isInAppList( showInstantApps: Boolean, hiddenSystemModules: Set<String>, hideWhenDisabledPackages: Array<String>, ) = when { ) = when { !showInstantApps && isInstantApp -> false !Flags.removeHiddenModuleUsage() && (packageName in hiddenSystemModules) -> false packageName in hideWhenDisabledPackages -> enabled && !isDisabledUntilUsed Loading @@ -251,3 +234,128 @@ class AppListRepositoryImpl( } } } /** This constructor is visible for tests only in order to override `featureFlags`. */ class AppListRepositoryImpl(context: Context, private val featureFlags: FeatureFlags) : AppListRepository { private val helper = AppListRepositoryImplHelper(context, featureFlags) constructor(context: Context) : this(context, FeatureFlagsImpl()) companion object { @JvmStatic @Volatile var useCaching = false set(value) { if (!value) { appsCache.clear() homeOrLauncherPackagesCache.clear() } field = value } private val appsCache = ConcurrentHashMap<AppsCacheKey, Deferred<List<ApplicationInfo>>>() private data class AppsCacheKey( val userId: Int, val loadInstantApps: Boolean, val matchAnyUserForAdmin: Boolean, ) private val homeOrLauncherPackagesCache = ConcurrentHashMap<Int, Deferred<Set<String>>>() } private suspend fun <K, V> getOrAsync( cache: ConcurrentHashMap<K, Deferred<V>>, key: K, block: suspend () -> V, ): Deferred<V> = coroutineScope { cache.computeIfAbsent(key) { this@coroutineScope.async { block() } } } override suspend fun loadApps( userId: Int, loadInstantApps: Boolean, matchAnyUserForAdmin: Boolean, ): List<ApplicationInfo> { if (!useCaching) { return helper.loadApps(userId, loadInstantApps, matchAnyUserForAdmin) } val key = AppsCacheKey(userId, loadInstantApps, matchAnyUserForAdmin) return getOrAsync(appsCache, key) { helper.loadApps(userId, loadInstantApps, matchAnyUserForAdmin) } .await() } override fun showSystemPredicate( userIdFlow: Flow<Int>, showSystemFlow: Flow<Boolean>, ): Flow<(app: ApplicationInfo) -> Boolean> = userIdFlow.combine(showSystemFlow, ::showSystemPredicate) override fun getSystemPackageNamesBlocking(userId: Int): Set<String> { if (!useCaching) { return helper.getSystemPackageNamesBlocking(userId) } return runBlocking { loadAndFilterApps(userId = userId, isSystemApp = true).map { it.packageName }.toSet() } } override suspend fun loadAndFilterApps( userId: Int, isSystemApp: Boolean, ): List<ApplicationInfo> { if (!useCaching) { return helper.loadAndFilterApps(userId, isSystemApp) } return coroutineScope { val loadAppsDeferred = async { loadApps(userId) } val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) loadAppsDeferred.await().filter { app -> isSystemApp(app, homeOrLauncherPackages) == isSystemApp } } } override suspend fun loadAndMaybeExcludeSystemApps( userId: Int, excludeSystemApp: Boolean, ): List<ApplicationInfo> { if (!useCaching) { return helper.loadAndMaybeExcludeSystemApps(userId, excludeSystemApp) } return coroutineScope { val loadAppsDeferred = async { loadApps(userId) } if (excludeSystemApp) { val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) loadAppsDeferred.await().filter { app -> !isSystemApp(app, homeOrLauncherPackages) } } else { loadAppsDeferred.await() } } } private suspend fun showSystemPredicate( userId: Int, showSystem: Boolean, ): (app: ApplicationInfo) -> Boolean { if (showSystem) return { true } val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) return { app -> !isSystemApp(app, homeOrLauncherPackages) } } private suspend fun loadHomeOrLauncherPackages(userId: Int): Set<String> { if (!useCaching) { return helper.loadHomeOrLauncherPackages(userId) } return getOrAsync(homeOrLauncherPackagesCache, userId) { helper.loadHomeOrLauncherPackages(userId) } .await() } private fun isSystemApp(app: ApplicationInfo, homeOrLauncherPackages: Set<String>): Boolean = app.isSystemApp && !app.isUpdatedSystemApp && app.packageName !in homeOrLauncherPackages }
packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt +253 −176 File changed.Preview size limit exceeded, changes collapsed. Show changes