Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e5b2e4e2 authored by Anton Vayvod's avatar Anton Vayvod Committed by Android (Google) Code Review
Browse files

Merge "Refactor AppListRepositoryImpl to add caching." into main

parents c502640f 3aed8e72
Loading
Loading
Loading
Loading
+192 −84
Original line number Diff line number Diff line
@@ -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
@@ -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(
@@ -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
@@ -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() }
@@ -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.
@@ -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) }
@@ -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 {
@@ -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
@@ -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
}
+253 −176

File changed.

Preview size limit exceeded, changes collapsed.