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

Commit e7d2215c authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Removing dependency on PackageInfo in IconCache

Instead of maintaining 3 different states (systemState,
versionCode and lastUpdateTime), using a single freshnessId
and embedding everything in that. This allows for better
customization by overrides

Bug: 363324203
Flag: EXEMPT bugfix
Test: atest IconCacheTest
      atest IconCacheUpdateHandler
Change-Id: I208f17ad6030843bb5b3dded3ceceefa2e54f9e1
parent d2aa99a7
Loading
Loading
Loading
Loading
+35 −6
Original line number Diff line number Diff line
@@ -78,6 +78,9 @@ public class IconProvider {
    private final ComponentName mCalendar;
    private final ComponentName mClock;

    @NonNull
    private String mSystemState = "";

    public IconProvider(Context context) {
        mContext = context;
        mCalendar = parseComponentOrNull(context, R.string.calendar_component_name);
@@ -85,15 +88,31 @@ public class IconProvider {
    }

    /**
     * Adds any modification to the provided systemState for dynamic icons. This system state
     * is used by caches to check for icon invalidation.
     * Returns a string representing the current state of the app icon. It can be used as a
     * identifier to invalidate any resources loaded from the app.
     * It also incorporated ay system state, that can affect the loaded resource
     *
     * @see #updateSystemState()
     */
    public String getSystemStateForPackage(String systemState, String packageName) {
        if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
            return systemState + SYSTEM_STATE_SEPARATOR + getDay();
    public String getStateForApp(@Nullable ApplicationInfo appInfo) {
        if (appInfo == null) {
            return mSystemState;
        }

        if (mCalendar != null && mCalendar.getPackageName().equals(appInfo.packageName)) {
            return mSystemState + SYSTEM_STATE_SEPARATOR + getDay() + SYSTEM_STATE_SEPARATOR
                    + getApplicationInfoHash(appInfo);
        } else {
            return systemState;
            return mSystemState + SYSTEM_STATE_SEPARATOR + getApplicationInfoHash(appInfo);
        }
    }

    /**
     * Returns a hash to uniquely identify a particular version of appInfo
     */
    protected String getApplicationInfoHash(@NonNull ApplicationInfo appInfo) {
        // The hashString in source dir changes with every install
        return appInfo.sourceDir;
    }

    /**
@@ -249,6 +268,16 @@ public class IconProvider {
        }
    }

    /**
     * Refreshes the system state definition used to check the validity of an app icon. It
     * incorporates all the properties that can affect the app icon like the list of enabled locale
     * and system-version.
     */
    public void updateSystemState() {
        mSystemState = mContext.getResources().getConfiguration().getLocales().toLanguageTags()
                + "," + Build.VERSION.SDK_INT;
    }

    /**
     * @return Today's day of the month, zero-indexed.
     */
+41 −69
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package com.android.launcher3.icons.cache;

import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.graphics.BitmapFactory.decodeByteArray;

import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON;
@@ -28,7 +29,7 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
@@ -39,11 +40,8 @@ import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.os.LocaleList;
import android.os.Looper;
import android.os.Process;
import android.os.Trace;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -152,12 +150,6 @@ public abstract class BaseIconCache {
    @NonNull
    protected IconDB mIconDb;

    @NonNull
    protected LocaleList mLocaleList = LocaleList.getEmptyLocaleList();

    @NonNull
    protected String mSystemState = "";

    @Nullable
    private BitmapInfo mDefaultIcon;

@@ -193,7 +185,7 @@ public abstract class BaseIconCache {
            mCache = new HashMap<>(INITIAL_ICON_CACHE_CAPACITY);
        } else {
            // Use a dummy cache
            mCache = new AbstractMap<ComponentKey, CacheEntry>() {
            mCache = new AbstractMap<>() {
                @Override
                public Set<Entry<ComponentKey, CacheEntry>> entrySet() {
                    return Collections.emptySet();
@@ -298,6 +290,10 @@ public abstract class BaseIconCache {
    @NonNull
    public IconCacheUpdateHandler getUpdateHandler() {
        updateSystemState();

        // Remove all active icon update tasks.
        workerHandler.removeCallbacksAndMessages(iconUpdateToken);

        return new IconCacheUpdateHandler(this);
    }

@@ -307,16 +303,10 @@ public abstract class BaseIconCache {
     * and system-version.
     */
    private void updateSystemState() {
        mLocaleList = mContext.getResources().getConfiguration().getLocales();
        mSystemState = mLocaleList.toLanguageTags() + "," + Build.VERSION.SDK_INT;
        mIconProvider.updateSystemState();
        mUserFormatString.clear();
    }

    @NonNull
    protected String getIconSystemState(@Nullable final String packageName) {
        return mIconProvider.getSystemStateForPackage(mSystemState, packageName);
    }

    public IconProvider getIconProvider() {
        return mIconProvider;
    }
@@ -342,8 +332,7 @@ public abstract class BaseIconCache {
     * entry fails to load
     */
    protected synchronized <T> void addIconToDBAndMemCache(@NonNull final T object,
            @NonNull final CachingLogic<T> cachingLogic, @NonNull final PackageInfo info,
            final long userSerial) {
            @NonNull final CachingLogic<T> cachingLogic, final long userSerial) {
        UserHandle user = cachingLogic.getUser(object);
        ComponentName componentName = cachingLogic.getComponent(object);
        final ComponentKey key = new ComponentKey(componentName, user);
@@ -371,24 +360,10 @@ public abstract class BaseIconCache {
            mCache.put(key, entry);
        }

        ContentValues values = newContentValues(
                bitmapInfo, entryTitle.toString(), componentName.getPackageName());
        addIconToDB(values, componentName, info, userSerial,
                cachingLogic.getLastUpdatedTime(object, info));
        String freshnessId = cachingLogic.getFreshnessIdentifier(object, mIconProvider);
        if (freshnessId != null) {
            addOrUpdateCacheDbEntry(bitmapInfo, entryTitle, componentName, userSerial, freshnessId);
        }

    /**
     * Updates {@param values} to contain versioning information and adds it to the DB.
     *
     * @param values {@link ContentValues} containing icon & title
     */
    private void addIconToDB(@NonNull final ContentValues values, @NonNull final ComponentName key,
            @NonNull final PackageInfo info, final long userSerial, final long lastUpdateTime) {
        values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
        values.put(IconDB.COLUMN_USER, userSerial);
        values.put(IconDB.COLUMN_LAST_UPDATED, lastUpdateTime);
        values.put(IconDB.COLUMN_VERSION, info.versionCode);
        mIconDb.insertOrReplace(values);
    }

    @NonNull
@@ -530,8 +505,7 @@ public abstract class BaseIconCache {
        if (TextUtils.isEmpty(entry.title)) {
            entry.title = cachingLogic.getComponent(object).getPackageName();
        }
        entry.contentDescription = getUserBadgedLabel(
                cachingLogic.getDescription(object, entry.title), user);
        entry.contentDescription = getUserBadgedLabel(entry.title, user);
    }

    public synchronized void clearMemoryCache() {
@@ -613,28 +587,23 @@ public abstract class BaseIconCache {
            // Check the DB first.
            if (!getEntryFromDBLocked(cacheKey, entry, useLowResIcon)) {
                try {
                    long flags = Process.myUserHandle().equals(user) ? 0 :
                            PackageManager.GET_UNINSTALLED_PACKAGES;
                    flags |= PackageManager.MATCH_ARCHIVED_PACKAGES;
                    PackageInfo info = mPackageManager.getPackageInfo(packageName,
                            PackageManager.PackageInfoFlags.of(flags));
                    ApplicationInfo appInfo = info.applicationInfo;
                    ApplicationInfo appInfo = mContext.getSystemService(LauncherApps.class)
                            .getApplicationInfo(packageName, MATCH_UNINSTALLED_PACKAGES, user);
                    if (appInfo == null) {
                        NameNotFoundException e = new NameNotFoundException(
                                "ApplicationInfo is null");
                        NameNotFoundException e =
                                new NameNotFoundException("ApplicationInfo is null");
                        logdPersistently(TAG,
                                String.format("ApplicationInfo is null for %s", packageName),
                                e);
                                String.format("ApplicationInfo is null for %s", packageName), e);
                        throw e;
                    }

                    BaseIconFactory li = getIconFactory();
                    // Load the full res icon for the application, but if useLowResIcon is set, then
                    // only keep the low resolution icon instead of the larger full-sized icon
                    Drawable appIcon = appInfo.loadIcon(mPackageManager);
                    Drawable appIcon = mIconProvider.getIcon(appInfo);
                    if (mPackageManager.isDefaultApplicationIcon(appIcon)) {
                        logdPersistently(TAG,
                                String.format("Default icon returned for %s", packageName),
                                String.format("Default icon returned for %s", appInfo.packageName),
                                null);
                    }
                    BitmapInfo iconInfo = li.createBadgedIconBitmap(appIcon,
@@ -649,11 +618,12 @@ public abstract class BaseIconCache {

                    // Add the icon in the DB here, since these do not get written during
                    // package updates.
                    ContentValues values = newContentValues(
                            iconInfo, entry.title.toString(), packageName);
                    addIconToDB(values, cacheKey.componentName, info, getSerialNumberForUser(user),
                            info.lastUpdateTime);

                    String freshnessId = mIconProvider.getStateForApp(appInfo);
                    if (freshnessId != null) {
                        addOrUpdateCacheDbEntry(
                                iconInfo, entry.title, cacheKey.componentName,
                                getSerialNumberForUser(user), freshnessId);
                    }
                } catch (NameNotFoundException e) {
                    if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
                    entryUpdated = false;
@@ -758,20 +728,18 @@ public abstract class BaseIconCache {
    public static final class IconDB extends SQLiteCacheHelper {
        // Ensures archived app icons are invalidated after flag is flipped.
        // TODO: Remove conditional with FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS
        private static final int RELEASE_VERSION = Flags.useNewIconForArchivedApps() ? 35 : 34;
        private static final int RELEASE_VERSION = Flags.useNewIconForArchivedApps() ? 2 : 1;

        public static final String TABLE_NAME = "icons";
        public static final String COLUMN_ROWID = "rowid";
        public static final String COLUMN_COMPONENT = "componentName";
        public static final String COLUMN_USER = "profileId";
        public static final String COLUMN_LAST_UPDATED = "lastUpdated";
        public static final String COLUMN_VERSION = "version";
        public static final String COLUMN_FRESHNESS_ID = "freshnessId";
        public static final String COLUMN_ICON = "icon";
        public static final String COLUMN_ICON_COLOR = "icon_color";
        public static final String COLUMN_MONO_ICON = "mono_icon";
        public static final String COLUMN_FLAGS = "flags";
        public static final String COLUMN_LABEL = "label";
        public static final String COLUMN_SYSTEM_STATE = "system_state";

        public static final String[] COLUMNS_LOW_RES = new String[]{
                COLUMN_COMPONENT,
@@ -802,22 +770,24 @@ public abstract class BaseIconCache {
            db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
                    + COLUMN_COMPONENT + " TEXT NOT NULL, "
                    + COLUMN_USER + " INTEGER NOT NULL, "
                    + COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, "
                    + COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, "
                    + COLUMN_FRESHNESS_ID + " TEXT, "
                    + COLUMN_ICON + " BLOB, "
                    + COLUMN_MONO_ICON + " BLOB, "
                    + COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, "
                    + COLUMN_FLAGS + " INTEGER NOT NULL DEFAULT 0, "
                    + COLUMN_LABEL + " TEXT, "
                    + COLUMN_SYSTEM_STATE + " TEXT, "
                    + "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") "
                    + ");");
        }
    }

    @NonNull
    private ContentValues newContentValues(@NonNull final BitmapInfo bitmapInfo,
            @NonNull final String label, @NonNull final String packageName) {
    private void addOrUpdateCacheDbEntry(
            @NonNull final BitmapInfo bitmapInfo,
            @NonNull final CharSequence label,
            @NonNull final ComponentName key,
            final long userSerial,
            @NonNull final String freshnessId) {
        ContentValues values = new ContentValues();
        if (bitmapInfo.canPersist()) {
            values.put(IconDB.COLUMN_ICON, flattenBitmap(bitmapInfo.icon));
@@ -839,10 +809,12 @@ public abstract class BaseIconCache {
        }
        values.put(IconDB.COLUMN_ICON_COLOR, bitmapInfo.color);
        values.put(IconDB.COLUMN_FLAGS, bitmapInfo.flags);
        values.put(IconDB.COLUMN_LABEL, label.toString());

        values.put(IconDB.COLUMN_LABEL, label);
        values.put(IconDB.COLUMN_SYSTEM_STATE, getIconSystemState(packageName));
        return values;
        values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
        values.put(IconDB.COLUMN_USER, userSerial);
        values.put(IconDB.COLUMN_FRESHNESS_ID, freshnessId);
        mIconDb.insertOrReplace(values);
    }

    private void assertWorkerThread() {
+12 −0
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.launcher3.icons.IconProvider;

/**
 * A simple interface to represent an object which can be added to icon cache
 *
@@ -60,4 +62,14 @@ public interface CachedObject<T extends BaseIconCache> {
     */
    @Nullable
    ApplicationInfo getApplicationInfo();


    /**
     * Returns a persistable string that can be used to indicate indicate the correctness of the
     * cache for the provided item
     */
    @Nullable
    default String getFreshnessIdentifier(@NonNull IconProvider iconProvider) {
        return iconProvider.getStateForApp(getApplicationInfo());
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.Context
import android.os.UserHandle
import com.android.launcher3.icons.BaseIconFactory.IconOptions
import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.IconProvider

/** Caching logic for ComponentWithLabelAndIcon */
class CachedObjectCachingLogic<T : BaseIconCache>(context: Context) :
@@ -46,4 +47,7 @@ class CachedObjectCachingLogic<T : BaseIconCache>(context: Context) :
    }

    override fun getApplicationInfo(info: CachedObject<T>) = info.applicationInfo

    override fun getFreshnessIdentifier(item: CachedObject<T>, provider: IconProvider): String? =
        item.getFreshnessIdentifier(provider)
}
+6 −11
Original line number Diff line number Diff line
@@ -18,13 +18,13 @@ package com.android.launcher3.icons.cache;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.os.UserHandle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.IconProvider;

public interface CachingLogic<T> {

@@ -40,12 +40,6 @@ public interface CachingLogic<T> {
    @Nullable
    CharSequence getLabel(@NonNull final T object);

    @NonNull
    default CharSequence getDescription(@NonNull final T object,
            @NonNull final CharSequence fallback) {
        return fallback;
    }

    /**
     * Returns the application info associated with the object. This is used to maintain the
     * "freshness" of the disk cache. If null, the item will not be persisted to the disk
@@ -57,9 +51,10 @@ public interface CachingLogic<T> {
    BitmapInfo loadIcon(@NonNull Context context, @NonNull BaseIconCache cache, @NonNull T object);

    /**
     * Returns the timestamp the entry was last updated in cache.
     * Returns a persistable string that can be used to indicate indicate the correctness of the
     * cache for the provided item
     */
    default long getLastUpdatedTime(@Nullable final T object, @NonNull final PackageInfo info) {
        return info.lastUpdateTime;
    }
    @Nullable
    String getFreshnessIdentifier(@NonNull T item, @NonNull IconProvider iconProvider);

}
Loading