/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lb.wallpaper_picker_library; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.util.Log; 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.HashMap; import java.util.HashSet; /** * Cache of application icons. Icons can be made from any thread. */ 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; CacheKey(ComponentName componentName, UserHandleCompat user) { this.componentName = componentName; this.user = user; } @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); } } private final HashMap<UserHandleCompat, Bitmap> mDefaultIcons = new HashMap<UserHandleCompat, Bitmap>(); private final Context mContext; private final PackageManager mPackageManager; private 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); 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)); } public Drawable getFullResDefaultActivityIcon() { return getFullResIcon(Resources.getSystem(), android.R.mipmap.sym_def_app_icon); } private Drawable getFullResIcon(Resources resources, int iconId) { Drawable d; try { d = resources.getDrawableForDensity(iconId, mIconDpi); } catch (Resources.NotFoundException e) { d = null; } return (d != null) ? d : getFullResDefaultActivityIcon(); } public Drawable getFullResIcon(String packageName, int iconId) { Resources resources; try { resources = mPackageManager.getResourcesForApplication(packageName); } catch (NameNotFoundException e) { resources = null; } if (resources != null) { if (iconId != 0) { return getFullResIcon(resources, iconId); } } return getFullResDefaultActivityIcon(); } public int getFullResIconDpi() { return mIconDpi; } public Drawable getFullResIcon(ActivityInfo info) { Resources resources; try { resources = mPackageManager.getResourcesForApplication( info.applicationInfo); } catch (NameNotFoundException e) { resources = null; } if (resources != null) { int iconId = info.getIconResource(); if (iconId != 0) { return getFullResIcon(resources, iconId); } } return getFullResDefaultActivityIcon(); } private Bitmap makeDefaultIcon(UserHandleCompat user) { Drawable unbadged = getFullResDefaultActivityIcon(); Drawable d = mUserManager.getBadgedDrawableForUser(unbadged, user); Bitmap b = Bitmap.createBitmap(Math.max(d.getIntrinsicWidth(), 1), Math.max(d.getIntrinsicHeight(), 1), Bitmap.Config.ARGB_8888); Canvas c = new Canvas(b); d.setBounds(0, 0, b.getWidth(), b.getHeight()); d.draw(c); c.setBitmap(null); return b; } /** * Remove any records for the supplied ComponentName. */ public synchronized void remove(ComponentName componentName, UserHandleCompat user) { mCache.remove(new CacheKey(componentName, user)); } /** * Remove any records for the supplied package name. */ public synchronized void remove(String packageName, UserHandleCompat user) { HashSet<CacheKey> forDeletion = new HashSet<CacheKey>(); for (CacheKey key: mCache.keySet()) { if (key.componentName.getPackageName().equals(packageName) && key.user.equals(user)) { forDeletion.add(key); } } for (CacheKey condemned: forDeletion) { mCache.remove(condemned); } } /** * Empty out the cache. */ public synchronized void flush() { mCache.clear(); } /** * Empty out the cache that aren't of the correct grid size */ // 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(); // } // } // } /** * Fill in "application" with the icon and label for "info." */ // public synchronized void getTitleAndIcon(AppInfo application, LauncherActivityInfoCompat info, // HashMap<Object, CharSequence> labelCache) { // CacheEntry entry = cacheLocked(application.componentName, info, labelCache, // info.getUser(), false); // // application.title = entry.title; // application.iconBitmap = entry.icon; // application.contentDescription = entry.contentDescription; // } // 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 // // we should still look in the cache for restored app icons. // if (component == null) { // return getDefaultIcon(user); // } // // LauncherActivityInfoCompat launcherActInfo = mLauncherApps.resolveActivity(intent, user); // CacheEntry entry = cacheLocked(component, launcherActInfo, null, user, true); // return entry.icon; // } /** * Fill in "shortcutInfo" with the icon and label for "info." */ // public synchronized void getTitleAndIcon(ShortcutInfo shortcutInfo, Intent intent, // UserHandleCompat user, boolean usePkgIcon) { // 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. // if (component == null) { // shortcutInfo.setIcon(getDefaultIcon(user)); // shortcutInfo.title = ""; // shortcutInfo.usingFallbackIcon = true; // } 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); // } // } public synchronized Bitmap getDefaultIcon(UserHandleCompat user) { if (!mDefaultIcons.containsKey(user)) { mDefaultIcons.put(user, makeDefaultIcon(user)); } 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; } /** * Retrieves the entry from the cache. If the entry is not present, it creates a new entry. * 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); // CacheEntry entry = mCache.get(cacheKey); // if (entry == null) { // 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; // } else { // if (usePackageIcon) { // CacheEntry packageEntry = getEntryForPackage( // componentName.getPackageName(), user); // if (packageEntry != null) { // if (DEBUG) Log.d(TAG, "using package default icon for " + // componentName.toShortString()); // entry.icon = packageEntry.icon; // entry.title = packageEntry.title; // } // } // if (entry.icon == null) { // if (DEBUG) Log.d(TAG, "using default icon for " + // componentName.toShortString()); // entry.icon = getDefaultIcon(user); // } // } // } // } // return entry; // } /** * Adds a default package entry in the cache. This entry is not persisted and will be removed * when the cache is flushed. */ // public synchronized void cachePackageInstallInfo(String packageName, UserHandleCompat user, // Bitmap icon, CharSequence title) { // remove(packageName, user); // // CacheEntry entry = getEntryForPackage(packageName, user); // if (!TextUtils.isEmpty(title)) { // entry.title = title; // } // if (icon != null) { // entry.icon = Utilities.createIconBitmap(icon, mContext); // } // } /** * 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); // CacheEntry entry = mCache.get(cacheKey); // if (entry == null) { // entry = new CacheEntry(); // entry.title = ""; // mCache.put(cacheKey, entry); // // 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); // } // // if (entry.icon == null) { // entry.icon = getPreloadedIcon(cn, user); // } // } // 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) { // TODO rescale to the correct native DPI try { PackageManager packageManager = context.getPackageManager(); packageManager.getActivityIcon(componentName); // component is present on the system already, do nothing return; } catch (NameNotFoundException e) { // pass } final String key = componentName.flattenToString(); FileOutputStream resourceFile = null; try { resourceFile = context.openFileOutput(getResourceFilename(componentName), Context.MODE_PRIVATE); ByteArrayOutputStream os = new ByteArrayOutputStream(); if (icon.compress(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); } } } } /** * 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. */ 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; } 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); } 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); } } 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); } } } return icon; } /** * 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; } remove(componentName, user); boolean success = mContext.deleteFile(getResourceFilename(componentName)); if (DEBUG && success) Log.d(TAG, "removed pre-loaded icon from persistent cache"); } private static String getResourceFilename(ComponentName component) { String resourceName = component.flattenToShortString(); String filename = resourceName.replace(File.separatorChar, '_'); return RESOURCE_FILE_PREFIX + filename; } }