summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/IconCache.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/launcher3/IconCache.java')
-rw-r--r--src/com/android/launcher3/IconCache.java813
1 files changed, 584 insertions, 229 deletions
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 5a0875b30..59ab8397d 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -16,19 +16,28 @@
package com.android.launcher3;
-import android.app.ActivityManager;
import android.content.ComponentName;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
@@ -36,17 +45,17 @@ import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Thunk;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map.Entry;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.Stack;
/**
* Cache of application icons. Icons can be made from any thread.
@@ -56,66 +65,71 @@ public class IconCache {
private static final String TAG = "Launcher.IconCache";
private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
- private static final String RESOURCE_FILE_PREFIX = "icon_";
// Empty class name is used for storing package default entry.
private static final String EMPTY_CLASS_NAME = ".";
private static final boolean DEBUG = false;
- private static class CacheEntry {
- public Bitmap icon;
- public CharSequence title;
- public CharSequence contentDescription;
- }
-
- private static class CacheKey {
- public ComponentName componentName;
- public UserHandleCompat user;
+ private static final int LOW_RES_SCALE_FACTOR = 5;
- CacheKey(ComponentName componentName, UserHandleCompat user) {
- this.componentName = componentName;
- this.user = user;
- }
+ @Thunk static final Object ICON_UPDATE_TOKEN = new Object();
- @Override
- public int hashCode() {
- return componentName.hashCode() + user.hashCode();
- }
-
- @Override
- public boolean equals(Object o) {
- CacheKey other = (CacheKey) o;
- return other.componentName.equals(componentName) && other.user.equals(user);
- }
+ @Thunk static class CacheEntry {
+ public Bitmap icon;
+ public CharSequence title = "";
+ public CharSequence contentDescription = "";
+ public boolean isLowResIcon;
}
- private final HashMap<UserHandleCompat, Bitmap> mDefaultIcons =
- new HashMap<UserHandleCompat, Bitmap>();
+ private final HashMap<UserHandleCompat, Bitmap> mDefaultIcons = new HashMap<>();
+ @Thunk final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
+
private final Context mContext;
private final PackageManager mPackageManager;
- private final UserManagerCompat mUserManager;
+ @Thunk final UserManagerCompat mUserManager;
private final LauncherAppsCompat mLauncherApps;
- private final HashMap<CacheKey, CacheEntry> mCache =
- new HashMap<CacheKey, CacheEntry>(INITIAL_ICON_CACHE_CAPACITY);
- private int mIconDpi;
-
- public IconCache(Context context) {
- ActivityManager activityManager =
- (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
-
+ private final HashMap<ComponentKey, CacheEntry> mCache =
+ new HashMap<ComponentKey, CacheEntry>(INITIAL_ICON_CACHE_CAPACITY);
+ private final int mIconDpi;
+ @Thunk final IconDB mIconDb;
+
+ @Thunk final Handler mWorkerHandler;
+
+ // The background color used for activity icons. Since these icons are displayed in all-apps
+ // and folders, this would be same as the light quantum panel background. This color
+ // is used to convert icons to RGB_565.
+ private final int mActivityBgColor;
+ // The background color used for package icons. These are displayed in widget tray, which
+ // has a dark quantum panel background.
+ private final int mPackageBgColor;
+ private final BitmapFactory.Options mLowResOptions;
+
+ private String mSystemState;
+ private Bitmap mLowResBitmap;
+ private Canvas mLowResCanvas;
+ private Paint mLowResPaint;
+
+ public IconCache(Context context, InvariantDeviceProfile inv) {
mContext = context;
mPackageManager = context.getPackageManager();
mUserManager = UserManagerCompat.getInstance(mContext);
mLauncherApps = LauncherAppsCompat.getInstance(mContext);
- mIconDpi = activityManager.getLauncherLargeIconDensity();
-
- // need to set mIconDpi before getting default icon
- UserHandleCompat myUser = UserHandleCompat.myUserHandle();
- mDefaultIcons.put(myUser, makeDefaultIcon(myUser));
+ mIconDpi = inv.fillResIconDpi;
+ mIconDb = new IconDB(context);
+
+ mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
+
+ mActivityBgColor = context.getResources().getColor(R.color.quantum_panel_bg_color);
+ mPackageBgColor = context.getResources().getColor(R.color.quantum_panel_bg_color_dark);
+ mLowResOptions = new BitmapFactory.Options();
+ // Always prefer RGB_565 config for low res. If the bitmap has transparency, it will
+ // automatically be loaded as ALPHA_8888.
+ mLowResOptions.inPreferredConfig = Bitmap.Config.RGB_565;
+ updateSystemStateString();
}
- public Drawable getFullResDefaultActivityIcon() {
+ private Drawable getFullResDefaultActivityIcon() {
return getFullResIcon(Resources.getSystem(), android.R.mipmap.sym_def_app_icon);
}
@@ -145,10 +159,6 @@ public class IconCache {
return getFullResDefaultActivityIcon();
}
- public int getFullResIconDpi() {
- return mIconDpi;
- }
-
public Drawable getFullResIcon(ActivityInfo info) {
Resources resources;
try {
@@ -184,59 +194,269 @@ public class IconCache {
* Remove any records for the supplied ComponentName.
*/
public synchronized void remove(ComponentName componentName, UserHandleCompat user) {
- mCache.remove(new CacheKey(componentName, user));
+ mCache.remove(new ComponentKey(componentName, user));
}
/**
- * Remove any records for the supplied package name.
+ * Remove any records for the supplied package name from memory.
*/
- public synchronized void remove(String packageName, UserHandleCompat user) {
- HashSet<CacheKey> forDeletion = new HashSet<CacheKey>();
- for (CacheKey key: mCache.keySet()) {
+ private void removeFromMemCacheLocked(String packageName, UserHandleCompat user) {
+ HashSet<ComponentKey> forDeletion = new HashSet<ComponentKey>();
+ for (ComponentKey key: mCache.keySet()) {
if (key.componentName.getPackageName().equals(packageName)
&& key.user.equals(user)) {
forDeletion.add(key);
}
}
- for (CacheKey condemned: forDeletion) {
+ for (ComponentKey condemned: forDeletion) {
mCache.remove(condemned);
}
}
/**
- * Empty out the cache.
+ * Updates the entries related to the given package in memory and persistent DB.
*/
- public synchronized void flush() {
- mCache.clear();
+ public synchronized void updateIconsForPkg(String packageName, UserHandleCompat user) {
+ removeIconsForPkg(packageName, user);
+ try {
+ PackageInfo info = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES);
+ long userSerial = mUserManager.getSerialNumberForUser(user);
+ for (LauncherActivityInfoCompat app : mLauncherApps.getActivityList(packageName, user)) {
+ addIconToDBAndMemCache(app, info, userSerial);
+ }
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "Package not found", e);
+ return;
+ }
}
/**
- * Empty out the cache that aren't of the correct grid size
+ * Removes the entries related to the given package in memory and persistent DB.
*/
- public synchronized void flushInvalidIcons(DeviceProfile grid) {
- Iterator<Entry<CacheKey, CacheEntry>> it = mCache.entrySet().iterator();
- while (it.hasNext()) {
- final CacheEntry e = it.next().getValue();
- if ((e.icon != null) && (e.icon.getWidth() < grid.iconSizePx
- || e.icon.getHeight() < grid.iconSizePx)) {
- it.remove();
+ public synchronized void removeIconsForPkg(String packageName, UserHandleCompat user) {
+ removeFromMemCacheLocked(packageName, user);
+ long userSerial = mUserManager.getSerialNumberForUser(user);
+ mIconDb.getWritableDatabase().delete(IconDB.TABLE_NAME,
+ IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?",
+ new String[] {packageName + "/%", Long.toString(userSerial)});
+ }
+
+ public void updateDbIcons(Set<String> ignorePackagesForMainUser) {
+ // Remove all active icon update tasks.
+ mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN);
+
+ updateSystemStateString();
+ for (UserHandleCompat user : mUserManager.getUserProfiles()) {
+ // Query for the set of apps
+ final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
+ // Fail if we don't have any apps
+ // TODO: Fix this. Only fail for the current user.
+ if (apps == null || apps.isEmpty()) {
+ return;
}
+
+ // Update icon cache. This happens in segments and {@link #onPackageIconsUpdated}
+ // is called by the icon cache when the job is complete.
+ updateDBIcons(user, apps, UserHandleCompat.myUserHandle().equals(user)
+ ? ignorePackagesForMainUser : Collections.<String>emptySet());
}
}
/**
- * Fill in "application" with the icon and label for "info."
+ * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
+ * the DB and are updated.
+ * @return The set of packages for which icons have updated.
+ */
+ private void updateDBIcons(UserHandleCompat user, List<LauncherActivityInfoCompat> apps,
+ Set<String> ignorePackages) {
+ long userSerial = mUserManager.getSerialNumberForUser(user);
+ PackageManager pm = mContext.getPackageManager();
+ HashMap<String, PackageInfo> pkgInfoMap = new HashMap<String, PackageInfo>();
+ for (PackageInfo info : pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
+ pkgInfoMap.put(info.packageName, info);
+ }
+
+ HashMap<ComponentName, LauncherActivityInfoCompat> componentMap = new HashMap<>();
+ for (LauncherActivityInfoCompat app : apps) {
+ componentMap.put(app.getComponentName(), app);
+ }
+
+ Cursor c = mIconDb.getReadableDatabase().query(IconDB.TABLE_NAME,
+ new String[] {IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
+ IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
+ IconDB.COLUMN_SYSTEM_STATE},
+ IconDB.COLUMN_USER + " = ? ",
+ new String[] {Long.toString(userSerial)},
+ null, null, null);
+
+ final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
+ final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
+ final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
+ final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
+ final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
+
+ HashSet<Integer> itemsToRemove = new HashSet<Integer>();
+ Stack<LauncherActivityInfoCompat> appsToUpdate = new Stack<>();
+
+ while (c.moveToNext()) {
+ String cn = c.getString(indexComponent);
+ ComponentName component = ComponentName.unflattenFromString(cn);
+ PackageInfo info = pkgInfoMap.get(component.getPackageName());
+ if (info == null) {
+ if (!ignorePackages.contains(component.getPackageName())) {
+ remove(component, user);
+ itemsToRemove.add(c.getInt(rowIndex));
+ }
+ continue;
+ }
+ if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) {
+ // Application is not present
+ continue;
+ }
+
+ long updateTime = c.getLong(indexLastUpdate);
+ int version = c.getInt(indexVersion);
+ LauncherActivityInfoCompat app = componentMap.remove(component);
+ if (version == info.versionCode && updateTime == info.lastUpdateTime &&
+ TextUtils.equals(mSystemState, c.getString(systemStateIndex))) {
+ continue;
+ }
+ if (app == null) {
+ remove(component, user);
+ itemsToRemove.add(c.getInt(rowIndex));
+ } else {
+ appsToUpdate.add(app);
+ }
+ }
+ c.close();
+ if (!itemsToRemove.isEmpty()) {
+ mIconDb.getWritableDatabase().delete(IconDB.TABLE_NAME,
+ Utilities.createDbSelectionQuery(IconDB.COLUMN_ROWID, itemsToRemove),
+ null);
+ }
+
+ // Insert remaining apps.
+ if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) {
+ Stack<LauncherActivityInfoCompat> appsToAdd = new Stack<>();
+ appsToAdd.addAll(componentMap.values());
+ new SerializedIconUpdateTask(userSerial, pkgInfoMap,
+ appsToAdd, appsToUpdate).scheduleNext();
+ }
+ }
+
+ @Thunk void addIconToDBAndMemCache(LauncherActivityInfoCompat app, PackageInfo info,
+ long userSerial) {
+ // Reuse the existing entry if it already exists in the DB. This ensures that we do not
+ // create bitmap if it was already created during loader.
+ ContentValues values = updateCacheAndGetContentValues(app, false);
+ addIconToDB(values, app.getComponentName(), info, userSerial);
+ }
+
+ /**
+ * Updates {@param values} to contain versoning information and adds it to the DB.
+ * @param values {@link ContentValues} containing icon & title
+ */
+ private void addIconToDB(ContentValues values, ComponentName key,
+ PackageInfo info, long userSerial) {
+ values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
+ values.put(IconDB.COLUMN_USER, userSerial);
+ values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
+ values.put(IconDB.COLUMN_VERSION, info.versionCode);
+ mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values,
+ SQLiteDatabase.CONFLICT_REPLACE);
+ }
+
+ @Thunk ContentValues updateCacheAndGetContentValues(LauncherActivityInfoCompat app,
+ boolean replaceExisting) {
+ final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser());
+ CacheEntry entry = null;
+ if (!replaceExisting) {
+ entry = mCache.get(key);
+ // We can't reuse the entry if the high-res icon is not present.
+ if (entry == null || entry.isLowResIcon || entry.icon == null) {
+ entry = null;
+ }
+ }
+ if (entry == null) {
+ entry = new CacheEntry();
+ entry.icon = Utilities.createIconBitmap(app.getBadgedIcon(mIconDpi), mContext);
+ }
+ entry.title = app.getLabel();
+ entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
+ mCache.put(new ComponentKey(app.getComponentName(), app.getUser()), entry);
+
+ return newContentValues(entry.icon, entry.title.toString(), mActivityBgColor);
+ }
+
+ /**
+ * Fetches high-res icon for the provided ItemInfo and updates the caller when done.
+ * @return a request ID that can be used to cancel the request.
*/
- public synchronized void getTitleAndIcon(AppInfo application, LauncherActivityInfoCompat info,
- HashMap<Object, CharSequence> labelCache) {
- CacheEntry entry = cacheLocked(application.componentName, info, labelCache,
- info.getUser(), false);
+ public IconLoadRequest updateIconInBackground(final BubbleTextView caller, final ItemInfo info) {
+ Runnable request = new Runnable() {
+
+ @Override
+ public void run() {
+ if (info instanceof AppInfo) {
+ getTitleAndIcon((AppInfo) info, null, false);
+ } else if (info instanceof ShortcutInfo) {
+ ShortcutInfo st = (ShortcutInfo) info;
+ getTitleAndIcon(st,
+ st.promisedIntent != null ? st.promisedIntent : st.intent,
+ st.user, false);
+ } else if (info instanceof PackageItemInfo) {
+ PackageItemInfo pti = (PackageItemInfo) info;
+ getTitleAndIconForApp(pti.packageName, pti.user, false, pti);
+ }
+ mMainThreadExecutor.execute(new Runnable() {
- application.title = entry.title;
- application.iconBitmap = entry.icon;
+ @Override
+ public void run() {
+ caller.reapplyItemInfo(info);
+ }
+ });
+ }
+ };
+ mWorkerHandler.post(request);
+ return new IconLoadRequest(request, mWorkerHandler);
+ }
+
+ private Bitmap getNonNullIcon(CacheEntry entry, UserHandleCompat user) {
+ return entry.icon == null ? getDefaultIcon(user) : entry.icon;
+ }
+
+ /**
+ * Fill in "application" with the icon and label for "info."
+ */
+ public synchronized void getTitleAndIcon(AppInfo application,
+ LauncherActivityInfoCompat info, boolean useLowResIcon) {
+ UserHandleCompat user = info == null ? application.user : info.getUser();
+ CacheEntry entry = cacheLocked(application.componentName, info, user,
+ false, useLowResIcon);
+ application.title = Utilities.trim(entry.title);
+ application.iconBitmap = getNonNullIcon(entry, user);
application.contentDescription = entry.contentDescription;
+ application.usingLowResIcon = entry.isLowResIcon;
}
+ /**
+ * Updates {@param application} only if a valid entry is found.
+ */
+ public synchronized void updateTitleAndIcon(AppInfo application) {
+ CacheEntry entry = cacheLocked(application.componentName, null, application.user,
+ false, application.usingLowResIcon);
+ if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
+ application.title = Utilities.trim(entry.title);
+ application.iconBitmap = entry.icon;
+ application.contentDescription = entry.contentDescription;
+ application.usingLowResIcon = entry.isLowResIcon;
+ }
+ }
+
+ /**
+ * Returns a high res icon for the given intent and user
+ */
public synchronized Bitmap getIcon(Intent intent, UserHandleCompat user) {
ComponentName component = intent.getComponent();
// null info means not installed, but if we have a component from the intent then
@@ -246,15 +466,16 @@ public class IconCache {
}
LauncherActivityInfoCompat launcherActInfo = mLauncherApps.resolveActivity(intent, user);
- CacheEntry entry = cacheLocked(component, launcherActInfo, null, user, true);
+ CacheEntry entry = cacheLocked(component, launcherActInfo, user, true, false /* useLowRes */);
return entry.icon;
}
/**
- * Fill in "shortcutInfo" with the icon and label for "info."
+ * Fill in {@param shortcutInfo} with the icon and label for {@param intent}. If the
+ * corresponding activity is not found, it reverts to the package icon.
*/
public synchronized void getTitleAndIcon(ShortcutInfo shortcutInfo, Intent intent,
- UserHandleCompat user, boolean usePkgIcon) {
+ UserHandleCompat user, boolean useLowResIcon) {
ComponentName component = intent.getComponent();
// null info means not installed, but if we have a component from the intent then
// we should still look in the cache for restored app icons.
@@ -262,17 +483,38 @@ public class IconCache {
shortcutInfo.setIcon(getDefaultIcon(user));
shortcutInfo.title = "";
shortcutInfo.usingFallbackIcon = true;
+ shortcutInfo.usingLowResIcon = false;
} else {
- LauncherActivityInfoCompat launcherActInfo =
- mLauncherApps.resolveActivity(intent, user);
- CacheEntry entry = cacheLocked(component, launcherActInfo, null, user, usePkgIcon);
-
- shortcutInfo.setIcon(entry.icon);
- shortcutInfo.title = entry.title;
- shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
+ LauncherActivityInfoCompat info = mLauncherApps.resolveActivity(intent, user);
+ getTitleAndIcon(shortcutInfo, component, info, user, true, useLowResIcon);
}
}
+ /**
+ * Fill in {@param shortcutInfo} with the icon and label for {@param info}
+ */
+ public synchronized void getTitleAndIcon(
+ ShortcutInfo shortcutInfo, ComponentName component, LauncherActivityInfoCompat info,
+ UserHandleCompat user, boolean usePkgIcon, boolean useLowResIcon) {
+ CacheEntry entry = cacheLocked(component, info, user, usePkgIcon, useLowResIcon);
+ shortcutInfo.setIcon(getNonNullIcon(entry, user));
+ shortcutInfo.title = Utilities.trim(entry.title);
+ shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
+ shortcutInfo.usingLowResIcon = entry.isLowResIcon;
+ }
+
+ /**
+ * Fill in {@param appInfo} with the icon and label for {@param packageName}
+ */
+ public synchronized void getTitleAndIconForApp(
+ String packageName, UserHandleCompat user, boolean useLowResIcon,
+ PackageItemInfo infoOut) {
+ CacheEntry entry = getEntryForPackageLocked(packageName, user, useLowResIcon);
+ infoOut.iconBitmap = getNonNullIcon(entry, user);
+ infoOut.title = Utilities.trim(entry.title);
+ infoOut.usingLowResIcon = entry.isLowResIcon;
+ infoOut.contentDescription = entry.contentDescription;
+ }
public synchronized Bitmap getDefaultIcon(UserHandleCompat user) {
if (!mDefaultIcons.containsKey(user)) {
@@ -281,16 +523,6 @@ public class IconCache {
return mDefaultIcons.get(user);
}
- public synchronized Bitmap getIcon(ComponentName component, LauncherActivityInfoCompat info,
- HashMap<Object, CharSequence> labelCache) {
- if (info == null || component == null) {
- return null;
- }
-
- CacheEntry entry = cacheLocked(component, info, labelCache, info.getUser(), false);
- return entry.icon;
- }
-
public boolean isDefaultIcon(Bitmap icon, UserHandleCompat user) {
return mDefaultIcons.get(user) == icon;
}
@@ -300,44 +532,27 @@ public class IconCache {
* This method is not thread safe, it must be called from a synchronized method.
*/
private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
- HashMap<Object, CharSequence> labelCache, UserHandleCompat user, boolean usePackageIcon) {
- CacheKey cacheKey = new CacheKey(componentName, user);
+ UserHandleCompat user, boolean usePackageIcon, boolean useLowResIcon) {
+ ComponentKey cacheKey = new ComponentKey(componentName, user);
CacheEntry entry = mCache.get(cacheKey);
- if (entry == null) {
+ if (entry == null || (entry.isLowResIcon && !useLowResIcon)) {
entry = new CacheEntry();
-
mCache.put(cacheKey, entry);
- if (info != null) {
- ComponentName labelKey = info.getComponentName();
- if (labelCache != null && labelCache.containsKey(labelKey)) {
- entry.title = labelCache.get(labelKey).toString();
- } else {
- entry.title = info.getLabel().toString();
- if (labelCache != null) {
- labelCache.put(labelKey, entry.title);
- }
- }
-
- entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
- entry.icon = Utilities.createIconBitmap(
- info.getBadgedIcon(mIconDpi), mContext);
- } else {
- entry.title = "";
- Bitmap preloaded = getPreloadedIcon(componentName, user);
- if (preloaded != null) {
- if (DEBUG) Log.d(TAG, "using preloaded icon for " +
- componentName.toShortString());
- entry.icon = preloaded;
+ // Check the DB first.
+ if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
+ if (info != null) {
+ entry.icon = Utilities.createIconBitmap(info.getBadgedIcon(mIconDpi), mContext);
} else {
if (usePackageIcon) {
- CacheEntry packageEntry = getEntryForPackage(
- componentName.getPackageName(), user);
+ CacheEntry packageEntry = getEntryForPackageLocked(
+ componentName.getPackageName(), user, false);
if (packageEntry != null) {
if (DEBUG) Log.d(TAG, "using package default icon for " +
componentName.toShortString());
entry.icon = packageEntry.icon;
entry.title = packageEntry.title;
+ entry.contentDescription = packageEntry.contentDescription;
}
}
if (entry.icon == null) {
@@ -347,6 +562,11 @@ public class IconCache {
}
}
}
+
+ if (TextUtils.isEmpty(entry.title) && info != null) {
+ entry.title = info.getLabel();
+ entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+ }
}
return entry;
}
@@ -357,9 +577,16 @@ public class IconCache {
*/
public synchronized void cachePackageInstallInfo(String packageName, UserHandleCompat user,
Bitmap icon, CharSequence title) {
- remove(packageName, user);
+ removeFromMemCacheLocked(packageName, user);
+
+ ComponentKey cacheKey = getPackageKey(packageName, user);
+ CacheEntry entry = mCache.get(cacheKey);
- CacheEntry entry = getEntryForPackage(packageName, user);
+ // For icon caching, do not go through DB. Just update the in-memory entry.
+ if (entry == null) {
+ entry = new CacheEntry();
+ mCache.put(cacheKey, entry);
+ }
if (!TextUtils.isEmpty(title)) {
entry.title = title;
}
@@ -368,59 +595,77 @@ public class IconCache {
}
}
+ private static ComponentKey getPackageKey(String packageName, UserHandleCompat user) {
+ ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
+ return new ComponentKey(cn, user);
+ }
+
/**
* Gets an entry for the package, which can be used as a fallback entry for various components.
* This method is not thread safe, it must be called from a synchronized method.
*/
- private CacheEntry getEntryForPackage(String packageName, UserHandleCompat user) {
- ComponentName cn = new ComponentName(packageName, EMPTY_CLASS_NAME);;
- CacheKey cacheKey = new CacheKey(cn, user);
+ private CacheEntry getEntryForPackageLocked(String packageName, UserHandleCompat user,
+ boolean useLowResIcon) {
+ ComponentKey cacheKey = getPackageKey(packageName, user);
CacheEntry entry = mCache.get(cacheKey);
- if (entry == null) {
+
+ if (entry == null || (entry.isLowResIcon && !useLowResIcon)) {
entry = new CacheEntry();
- entry.title = "";
- mCache.put(cacheKey, entry);
+ boolean entryUpdated = true;
- try {
- ApplicationInfo info = mPackageManager.getApplicationInfo(packageName, 0);
- entry.title = info.loadLabel(mPackageManager);
- entry.icon = Utilities.createIconBitmap(info.loadIcon(mPackageManager), mContext);
- } catch (NameNotFoundException e) {
- if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
+ // Check the DB first.
+ if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
+ try {
+ int flags = UserHandleCompat.myUserHandle().equals(user) ? 0 :
+ PackageManager.GET_UNINSTALLED_PACKAGES;
+ PackageInfo info = mPackageManager.getPackageInfo(packageName, flags);
+ ApplicationInfo appInfo = info.applicationInfo;
+ if (appInfo == null) {
+ throw new NameNotFoundException("ApplicationInfo is null");
+ }
+ Drawable drawable = mUserManager.getBadgedDrawableForUser(
+ appInfo.loadIcon(mPackageManager), user);
+ entry.icon = Utilities.createIconBitmap(drawable, mContext);
+ entry.title = appInfo.loadLabel(mPackageManager);
+ entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+ entry.isLowResIcon = false;
+
+ // Add the icon in the DB here, since these do not get written during
+ // package updates.
+ ContentValues values =
+ newContentValues(entry.icon, entry.title.toString(), mPackageBgColor);
+ addIconToDB(values, cacheKey.componentName, info,
+ mUserManager.getSerialNumberForUser(user));
+
+ } catch (NameNotFoundException e) {
+ if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
+ entryUpdated = false;
+ }
}
- if (entry.icon == null) {
- entry.icon = getPreloadedIcon(cn, user);
+ // Only add a filled-out entry to the cache
+ if (entryUpdated) {
+ mCache.put(cacheKey, entry);
}
}
return entry;
}
- public synchronized HashMap<ComponentName,Bitmap> getAllIcons() {
- HashMap<ComponentName,Bitmap> set = new HashMap<ComponentName,Bitmap>();
- for (CacheKey ck : mCache.keySet()) {
- final CacheEntry e = mCache.get(ck);
- set.put(ck.componentName, e.icon);
- }
- return set;
- }
-
/**
* Pre-load an icon into the persistent cache.
*
* <P>Queries for a component that does not exist in the package manager
* will be answered by the persistent cache.
*
- * @param context application context
* @param componentName the icon should be returned for this component
* @param icon the icon to be persisted
* @param dpi the native density of the icon
*/
- public static void preloadIcon(Context context, ComponentName componentName, Bitmap icon,
- int dpi) {
+ public void preloadIcon(ComponentName componentName, Bitmap icon, int dpi, String label,
+ long userSerial, InvariantDeviceProfile idp) {
// TODO rescale to the correct native DPI
try {
- PackageManager packageManager = context.getPackageManager();
+ PackageManager packageManager = mContext.getPackageManager();
packageManager.getActivityIcon(componentName);
// component is present on the system already, do nothing
return;
@@ -428,100 +673,210 @@ public class IconCache {
// pass
}
- final String key = componentName.flattenToString();
- FileOutputStream resourceFile = null;
+ ContentValues values = newContentValues(
+ Bitmap.createScaledBitmap(icon, idp.iconBitmapSize, idp.iconBitmapSize, true),
+ label, Color.TRANSPARENT);
+ values.put(IconDB.COLUMN_COMPONENT, componentName.flattenToString());
+ values.put(IconDB.COLUMN_USER, userSerial);
+ mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values,
+ SQLiteDatabase.CONFLICT_REPLACE);
+ }
+
+ private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
+ Cursor c = mIconDb.getReadableDatabase().query(IconDB.TABLE_NAME,
+ new String[] {lowRes ? IconDB.COLUMN_ICON_LOW_RES : IconDB.COLUMN_ICON,
+ IconDB.COLUMN_LABEL},
+ IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
+ new String[] {cacheKey.componentName.flattenToString(),
+ Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))},
+ null, null, null);
try {
- resourceFile = context.openFileOutput(getResourceFilename(componentName),
- Context.MODE_PRIVATE);
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- if (icon.compress(android.graphics.Bitmap.CompressFormat.PNG, 75, os)) {
- byte[] buffer = os.toByteArray();
- resourceFile.write(buffer, 0, buffer.length);
- } else {
- Log.w(TAG, "failed to encode cache for " + key);
- return;
- }
- } catch (FileNotFoundException e) {
- Log.w(TAG, "failed to pre-load cache for " + key, e);
- } catch (IOException e) {
- Log.w(TAG, "failed to pre-load cache for " + key, e);
- } finally {
- if (resourceFile != null) {
- try {
- resourceFile.close();
- } catch (IOException e) {
- Log.d(TAG, "failed to save restored icon for: " + key, e);
+ if (c.moveToNext()) {
+ entry.icon = loadIconNoResize(c, 0, lowRes ? mLowResOptions : null);
+ entry.isLowResIcon = lowRes;
+ entry.title = c.getString(1);
+ if (entry.title == null) {
+ entry.title = "";
+ entry.contentDescription = "";
+ } else {
+ entry.contentDescription = mUserManager.getBadgedLabelForUser(
+ entry.title, cacheKey.user);
}
+ return true;
}
+ } finally {
+ c.close();
+ }
+ return false;
+ }
+
+ public static class IconLoadRequest {
+ private final Runnable mRunnable;
+ private final Handler mHandler;
+
+ IconLoadRequest(Runnable runnable, Handler handler) {
+ mRunnable = runnable;
+ mHandler = handler;
+ }
+
+ public void cancel() {
+ mHandler.removeCallbacks(mRunnable);
}
}
/**
- * Read a pre-loaded icon from the persistent icon cache.
- *
- * @param componentName the component that should own the icon
- * @returns a bitmap if one is cached, or null.
+ * A runnable that updates invalid icons and adds missing icons in the DB for the provided
+ * LauncherActivityInfoCompat list. Items are updated/added one at a time, so that the
+ * worker thread doesn't get blocked.
*/
- private Bitmap getPreloadedIcon(ComponentName componentName, UserHandleCompat user) {
- final String key = componentName.flattenToShortString();
-
- // We don't keep icons for other profiles in persistent cache.
- if (!user.equals(UserHandleCompat.myUserHandle())) {
- return null;
+ @Thunk class SerializedIconUpdateTask implements Runnable {
+ private final long mUserSerial;
+ private final HashMap<String, PackageInfo> mPkgInfoMap;
+ private final Stack<LauncherActivityInfoCompat> mAppsToAdd;
+ private final Stack<LauncherActivityInfoCompat> mAppsToUpdate;
+ private final HashSet<String> mUpdatedPackages = new HashSet<String>();
+
+ @Thunk SerializedIconUpdateTask(long userSerial, HashMap<String, PackageInfo> pkgInfoMap,
+ Stack<LauncherActivityInfoCompat> appsToAdd,
+ Stack<LauncherActivityInfoCompat> appsToUpdate) {
+ mUserSerial = userSerial;
+ mPkgInfoMap = pkgInfoMap;
+ mAppsToAdd = appsToAdd;
+ mAppsToUpdate = appsToUpdate;
}
- if (DEBUG) Log.v(TAG, "looking for pre-load icon for " + key);
- Bitmap icon = null;
- FileInputStream resourceFile = null;
- try {
- resourceFile = mContext.openFileInput(getResourceFilename(componentName));
- byte[] buffer = new byte[1024];
- ByteArrayOutputStream bytes = new ByteArrayOutputStream();
- int bytesRead = 0;
- while(bytesRead >= 0) {
- bytes.write(buffer, 0, bytesRead);
- bytesRead = resourceFile.read(buffer, 0, buffer.length);
+ @Override
+ public void run() {
+ if (!mAppsToUpdate.isEmpty()) {
+ LauncherActivityInfoCompat app = mAppsToUpdate.pop();
+ String cn = app.getComponentName().flattenToString();
+ ContentValues values = updateCacheAndGetContentValues(app, true);
+ mIconDb.getWritableDatabase().update(IconDB.TABLE_NAME, values,
+ IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
+ new String[] {cn, Long.toString(mUserSerial)});
+ mUpdatedPackages.add(app.getComponentName().getPackageName());
+
+ if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) {
+ // No more app to update. Notify model.
+ LauncherAppState.getInstance().getModel().onPackageIconsUpdated(
+ mUpdatedPackages, mUserManager.getUserForSerialNumber(mUserSerial));
+ }
+
+ // Let it run one more time.
+ scheduleNext();
+ } else if (!mAppsToAdd.isEmpty()) {
+ LauncherActivityInfoCompat app = mAppsToAdd.pop();
+ PackageInfo info = mPkgInfoMap.get(app.getComponentName().getPackageName());
+ if (info != null) {
+ synchronized (IconCache.this) {
+ addIconToDBAndMemCache(app, info, mUserSerial);
+ }
+ }
+
+ if (!mAppsToAdd.isEmpty()) {
+ scheduleNext();
+ }
}
- if (DEBUG) Log.d(TAG, "read " + bytes.size());
- icon = BitmapFactory.decodeByteArray(bytes.toByteArray(), 0, bytes.size());
- if (icon == null) {
- Log.w(TAG, "failed to decode pre-load icon for " + key);
+ }
+
+ public void scheduleNext() {
+ mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN, SystemClock.uptimeMillis() + 1);
+ }
+ }
+
+ private void updateSystemStateString() {
+ mSystemState = Locale.getDefault().toString();
+ }
+
+ private static final class IconDB extends SQLiteOpenHelper {
+ private final static int DB_VERSION = 7;
+
+ private final static String TABLE_NAME = "icons";
+ private final static String COLUMN_ROWID = "rowid";
+ private final static String COLUMN_COMPONENT = "componentName";
+ private final static String COLUMN_USER = "profileId";
+ private final static String COLUMN_LAST_UPDATED = "lastUpdated";
+ private final static String COLUMN_VERSION = "version";
+ private final static String COLUMN_ICON = "icon";
+ private final static String COLUMN_ICON_LOW_RES = "icon_low_res";
+ private final static String COLUMN_LABEL = "label";
+ private final static String COLUMN_SYSTEM_STATE = "system_state";
+
+ public IconDB(Context context) {
+ super(context, LauncherFiles.APP_ICONS_DB, null, DB_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ 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_ICON + " BLOB, " +
+ COLUMN_ICON_LOW_RES + " BLOB, " +
+ COLUMN_LABEL + " TEXT, " +
+ COLUMN_SYSTEM_STATE + " TEXT, " +
+ "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " +
+ ");");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion != newVersion) {
+ clearDB(db);
}
- } catch (FileNotFoundException e) {
- if (DEBUG) Log.d(TAG, "there is no restored icon for: " + key);
- } catch (IOException e) {
- Log.w(TAG, "failed to read pre-load icon for: " + key, e);
- } finally {
- if(resourceFile != null) {
- try {
- resourceFile.close();
- } catch (IOException e) {
- Log.d(TAG, "failed to manage pre-load icon file: " + key, e);
- }
+ }
+
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion != newVersion) {
+ clearDB(db);
}
}
- return icon;
+ private void clearDB(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
+ onCreate(db);
+ }
}
- /**
- * Remove a pre-loaded icon from the persistent icon cache.
- *
- * @param componentName the component that should own the icon
- */
- public void deletePreloadedIcon(ComponentName componentName, UserHandleCompat user) {
- // We don't keep icons for other profiles in persistent cache.
- if (!user.equals(UserHandleCompat.myUserHandle()) || componentName == null) {
- return;
+ private ContentValues newContentValues(Bitmap icon, String label, int lowResBackgroundColor) {
+ ContentValues values = new ContentValues();
+ values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon));
+
+ values.put(IconDB.COLUMN_LABEL, label);
+ values.put(IconDB.COLUMN_SYSTEM_STATE, mSystemState);
+
+ if (lowResBackgroundColor == Color.TRANSPARENT) {
+ values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(
+ Bitmap.createScaledBitmap(icon,
+ icon.getWidth() / LOW_RES_SCALE_FACTOR,
+ icon.getHeight() / LOW_RES_SCALE_FACTOR, true)));
+ } else {
+ synchronized (this) {
+ if (mLowResBitmap == null) {
+ mLowResBitmap = Bitmap.createBitmap(icon.getWidth() / LOW_RES_SCALE_FACTOR,
+ icon.getHeight() / LOW_RES_SCALE_FACTOR, Bitmap.Config.RGB_565);
+ mLowResCanvas = new Canvas(mLowResBitmap);
+ mLowResPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
+ }
+ mLowResCanvas.drawColor(lowResBackgroundColor);
+ mLowResCanvas.drawBitmap(icon, new Rect(0, 0, icon.getWidth(), icon.getHeight()),
+ new Rect(0, 0, mLowResBitmap.getWidth(), mLowResBitmap.getHeight()),
+ mLowResPaint);
+ values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(mLowResBitmap));
+ }
}
- remove(componentName, user);
- boolean success = mContext.deleteFile(getResourceFilename(componentName));
- if (DEBUG && success) Log.d(TAG, "removed pre-loaded icon from persistent cache");
+ return values;
}
- private static String getResourceFilename(ComponentName component) {
- String resourceName = component.flattenToShortString();
- String filename = resourceName.replace(File.separatorChar, '_');
- return RESOURCE_FILE_PREFIX + filename;
+ private static Bitmap loadIconNoResize(Cursor c, int iconIndex, BitmapFactory.Options options) {
+ byte[] data = c.getBlob(iconIndex);
+ try {
+ return BitmapFactory.decodeByteArray(data, 0, data.length, options);
+ } catch (Exception e) {
+ return null;
+ }
}
}