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

Commit e6fa174f authored by Android Build Coastguard Worker's avatar Android Build Coastguard Worker
Browse files

Snap for 12623742 from f91600ab to 25Q1-release

Change-Id: Ia684b17f288b7e39583835a7ad9bd83ed5a9f37d
parents 5e4e4433 f91600ab
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -294,7 +294,7 @@ public abstract class BaseIconCache {
        // Remove all active icon update tasks.
        workerHandler.removeCallbacksAndMessages(iconUpdateToken);

        return new IconCacheUpdateHandler(this);
        return new IconCacheUpdateHandler(this, mIconDb, workerHandler);
    }

    /**
@@ -546,7 +546,7 @@ public abstract class BaseIconCache {
    }

    @NonNull
    private static ComponentKey getPackageKey(@NonNull final String packageName,
    public static ComponentKey getPackageKey(@NonNull final String packageName,
            @NonNull final UserHandle user) {
        ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
        return new ComponentKey(cn, user);
+130 −94
Original line number Diff line number Diff line
@@ -16,24 +16,35 @@
package com.android.launcher3.icons.cache

import android.content.ComponentName
import android.database.Cursor
import android.content.pm.ApplicationInfo
import android.database.sqlite.SQLiteException
import android.os.Handler
import android.os.SystemClock
import android.os.UserHandle
import android.util.ArrayMap
import android.util.Log
import android.util.SparseBooleanArray
import androidx.annotation.VisibleForTesting
import com.android.launcher3.icons.cache.BaseIconCache.IconDB
import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.SQLiteCacheHelper
import java.util.ArrayDeque

/** Utility class to handle updating the Icon cache */
class IconCacheUpdateHandler(private val iconCache: BaseIconCache) {
class IconCacheUpdateHandler(
    private val iconCache: BaseIconCache,
    private val cacheDb: SQLiteCacheHelper,
    private val workerHandler: Handler,
) {

    private val packagesToIgnore = ArrayMap<UserHandle, MutableSet<String>>()
    private val itemsToDelete = SparseBooleanArray()
    // Map of packageKey to ApplicationInfo, dynamically created based on all incoming data
    private val packageAppInfoMap = HashMap<ComponentKey, ApplicationInfo?>()

    private val itemsToDelete = HashSet<UpdateRow>()

    private var filterMode = MODE_SET_INVALID_ITEMS
    // During the first pass, we load all the items from DB and add all invalid items to
    // mItemsToDelete. In follow up passes, we  go through the items in mItemsToDelete, and if the
    // item is valid, removes it from the list, or leave it there.
    private var firstPass = true

    /** Sets a package to ignore for processing */
    fun addPackagesToIgnore(userHandle: UserHandle, packageName: String) {
@@ -53,51 +64,95 @@ class IconCacheUpdateHandler(private val iconCache: BaseIconCache) {
    ) {
        // Filter the list per user
        val userComponentMap = HashMap<UserHandle, HashMap<ComponentName, T>>()
        apps.forEach { app ->
        for (app in apps) {
            val userHandle = cachingLogic.getUser(app)
            var componentMap = userComponentMap.getOrPut(userHandle) { HashMap() }
            componentMap[cachingLogic.getComponent(app)] = app
            val cn = cachingLogic.getComponent(app)
            userComponentMap.getOrPut(userHandle) { HashMap() }[cn] = app

            // Populate application info map
            val packageKey = BaseIconCache.getPackageKey(cn.packageName, userHandle)
            packageAppInfoMap.getOrPut(packageKey) { cachingLogic.getApplicationInfo(app) }
        }

        for ((key, value) in userComponentMap) {
            updateIconsPerUser(key, value, cachingLogic, onUpdateCallback)
        if (firstPass) {
            userComponentMap.forEach { (user, componentMap) ->
                updateIconsPerUserForFirstPass(user, componentMap, cachingLogic, onUpdateCallback)
            }
        } else {
            userComponentMap.forEach { (user, componentMap) ->
                updateIconsPerUserForSecondPass(user, componentMap, cachingLogic, onUpdateCallback)
            }
        }

        // From now on, clear every valid item from the global valid map.
        filterMode = MODE_CLEAR_VALID_ITEMS
        firstPass = false
    }

    /**
     * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
     * the DB and are updated.
     * During the first pass, all the items from the cache are verified one-by-one and any entry
     * with no corresponding entry in {@code componentMap} is added to {@code itemsToDelete}
     *
     * @return The set of packages for which icons have updated.
     * Also starts a SerializedIconUpdateTask for all updated entries
     */
    private fun <T : Any> updateIconsPerUser(
    private fun <T : Any> updateIconsPerUserForFirstPass(
        user: UserHandle,
        componentMap: HashMap<ComponentName, T>,
        componentMap: MutableMap<ComponentName, T>,
        cachingLogic: CachingLogic<T>,
        onUpdateCallback: OnUpdateCallback,
    ) {
        var ignorePackages: Set<String> = packagesToIgnore[user] ?: emptySet()
        val userSerial = iconCache.getSerialNumberForUser(user)
        val appsToUpdate = ArrayDeque<T>()

        val userSerial = iconCache.getSerialNumberForUser(user)
        try {
            iconCache.mIconDb
            cacheDb
                .query(
                    arrayOf(
                        IconDB.COLUMN_ROWID,
                        IconDB.COLUMN_COMPONENT,
                        IconDB.COLUMN_FRESHNESS_ID,
                    ),
                    IconDB.COLUMN_USER + " = ? ",
                    "${IconDB.COLUMN_USER} = ? ",
                    arrayOf(userSerial.toString()),
                )
                .use { c ->
                    var ignorePackages = packagesToIgnore[user] ?: emptySet()

                    val indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT)
                    val indexFreshnessId = c.getColumnIndex(IconDB.COLUMN_FRESHNESS_ID)
                    val rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID)

                    while (c.moveToNext()) {
                        updateOrDeleteIcon(c, componentMap, ignorePackages, user, cachingLogic)
                            ?.let { appsToUpdate.add(it) }
                        val rowId = c.getInt(rowIndex)
                        val cn = c.getString(indexComponent)
                        val freshnessId = c.getString(indexFreshnessId) ?: ""

                        val component = ComponentName.unflattenFromString(cn)
                        if (component == null) {
                            // b/357725795
                            Log.e(TAG, "Invalid component name while updating icon cache: $cn")
                            itemsToDelete.add(
                                UpdateRow(rowId, ComponentName("", ""), user, freshnessId)
                            )
                            continue
                        }

                        val app = componentMap.remove(component)
                        if (app == null) {
                            if (!ignorePackages.contains(component.packageName)) {
                                iconCache.remove(component, user)
                                itemsToDelete.add(UpdateRow(rowId, component, user, freshnessId))
                            }
                            continue
                        }

                        if (
                            freshnessId ==
                                cachingLogic.getFreshnessIdentifier(app, iconCache.iconProvider)
                        ) {
                            // Item is up-to-date
                            continue
                        }
                        appsToUpdate.add(app)
                    }
                }
        } catch (e: SQLiteException) {
@@ -106,9 +161,8 @@ class IconCacheUpdateHandler(private val iconCache: BaseIconCache) {
        }

        // Insert remaining apps.
        if (componentMap.isNotEmpty() || !appsToUpdate.isEmpty()) {
            val appsToAdd = ArrayDeque<T>()
            appsToAdd.addAll(componentMap.values)
        if (componentMap.isNotEmpty() || appsToUpdate.isNotEmpty()) {
            val appsToAdd = ArrayDeque(componentMap.values)
            SerializedIconUpdateTask(
                    userSerial,
                    user,
@@ -122,52 +176,46 @@ class IconCacheUpdateHandler(private val iconCache: BaseIconCache) {
    }

    /**
     * This method retrieves the component and either adds it to the list of apps to update or adds
     * it to a list of apps to delete from cache later. Returns the individual app if it should be
     * updated, or null if nothing should be updated.
     * During the second pass, we go through the items in {@code itemsToDelete}, and remove any item
     * with corresponding entry in {@code componentMap}.
     */
    @VisibleForTesting
    fun <T : Any> updateOrDeleteIcon(
        c: Cursor,
        componentMap: MutableMap<ComponentName, out T>,
        ignorePackages: Set<String>,
    private fun <T : Any> updateIconsPerUserForSecondPass(
        user: UserHandle,
        componentMap: MutableMap<ComponentName, T>,
        cachingLogic: CachingLogic<T>,
    ): T? {
        val indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT)
        val indexFreshnessId = c.getColumnIndex(IconDB.COLUMN_FRESHNESS_ID)
        val rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID)
        onUpdateCallback: OnUpdateCallback,
    ) {
        val userSerial = iconCache.getSerialNumberForUser(user)
        val appsToUpdate = ArrayDeque<T>()

        val rowId = c.getInt(rowIndex)
        val cn = c.getString(indexComponent)
        val component = ComponentName.unflattenFromString(cn)
        if (component == null) {
            // b/357725795
            Log.e(TAG, "Invalid component name while updating icon cache: $cn")
            itemsToDelete.put(rowId, true)
            return null
        }
        val itr = itemsToDelete.iterator()
        while (itr.hasNext()) {
            val row = itr.next()
            if (user != row.user) continue
            val app = componentMap.remove(row.componentName) ?: continue

        val app = componentMap.remove(component)
        if (app == null) {
            if (!ignorePackages.contains(component.packageName)) {
                if (filterMode == MODE_SET_INVALID_ITEMS) {
                    iconCache.remove(component, user)
                    itemsToDelete.put(rowId, true)
                }
            itr.remove()
            if (
                row.freshnessId != cachingLogic.getFreshnessIdentifier(app, iconCache.iconProvider)
            ) {
                appsToUpdate.add(app)
            }
            return null
        }

        val freshnessId = c.getString(indexFreshnessId)
        if (freshnessId == cachingLogic.getFreshnessIdentifier(app, iconCache.iconProvider)) {
            if (filterMode == MODE_CLEAR_VALID_ITEMS) {
                itemsToDelete.put(rowId, false)
            }
            return null
        // Insert remaining apps.
        if (componentMap.isNotEmpty() || appsToUpdate.isNotEmpty()) {
            val appsToAdd = ArrayDeque<T>()
            appsToAdd.addAll(componentMap.values)
            SerializedIconUpdateTask(
                    userSerial,
                    user,
                    appsToAdd,
                    appsToUpdate,
                    cachingLogic,
                    onUpdateCallback,
                )
                .scheduleNext()
        }

        return app
    }

    /**
@@ -175,27 +223,27 @@ class IconCacheUpdateHandler(private val iconCache: BaseIconCache) {
     * this class after this.
     */
    fun finish() {
        // Commit all deletes
        var deleteCount = 0
        val queryBuilder = StringBuilder().append(IconDB.COLUMN_ROWID).append(" IN (")

        val count = itemsToDelete.size()
        for (i in 0 until count) {
            if (itemsToDelete.valueAt(i)) {
                if (deleteCount > 0) {
                    queryBuilder.append(", ")
                }
                queryBuilder.append(itemsToDelete.keyAt(i))
                deleteCount++
        // Ignore any application info entries which are already correct
        itemsToDelete.removeIf { row ->
            val info = packageAppInfoMap[ComponentKey(row.componentName, row.user)]
            info != null && row.freshnessId == iconCache.iconProvider.getStateForApp(info)
        }
        }
        queryBuilder.append(')')

        if (deleteCount > 0) {
            iconCache.mIconDb.delete(queryBuilder.toString(), null)
        // Commit all deletes
        if (itemsToDelete.isNotEmpty()) {
            val r = itemsToDelete.joinToString { it.rowId.toString() }
            cacheDb.delete("${IconDB.COLUMN_ROWID} IN ($r)", null)
            Log.d(TAG, "Deleting obsolete entries, count=" + itemsToDelete.size)
        }
    }

    data class UpdateRow(
        val rowId: Int,
        val componentName: ComponentName,
        val user: UserHandle,
        val freshnessId: String,
    )

    /**
     * A runnable that updates invalid icons and adds missing icons in the DB for the provided
     * LauncherActivityInfo list. Items are updated/added one at a time, so that the worker thread
@@ -235,7 +283,7 @@ class IconCacheUpdateHandler(private val iconCache: BaseIconCache) {
        }

        fun scheduleNext() {
            iconCache.workerHandler.postAtTime(
            workerHandler.postAtTime(
                this,
                iconCache.iconUpdateToken,
                SystemClock.uptimeMillis() + 1,
@@ -243,23 +291,11 @@ class IconCacheUpdateHandler(private val iconCache: BaseIconCache) {
        }
    }

    interface OnUpdateCallback {
    fun interface OnUpdateCallback {
        fun onPackageIconsUpdated(updatedPackages: HashSet<String>, user: UserHandle)
    }

    companion object {
        private const val TAG = "IconCacheUpdateHandler"

        /**
         * In this mode, all invalid icons are marked as to-be-deleted in [.mItemsToDelete]. This
         * mode is used for the first run.
         */
        private const val MODE_SET_INVALID_ITEMS = true

        /**
         * In this mode, any valid icon is removed from [.mItemsToDelete]. This is used for all
         * subsequent runs, which essentially acts as set-union of all valid items.
         */
        private const val MODE_CLEAR_VALID_ITEMS = false
    }
}