/* * 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.android.launcher3; import com.android.launcher3.backup.BackupProtos; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.text.TextUtils; 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; import java.util.Iterator; import java.util.Map.Entry; import com.android.launcher3.settings.SettingsProvider; /** * Cache of application icons. Icons can be made from any thread. */ public class IconCache { @SuppressWarnings("unused") private static final String TAG = "Launcher.IconCache"; private static final int INITIAL_ICON_CACHE_CAPACITY = 50; private static final String RESOURCE_FILE_PREFIX = "icon_"; private static final boolean DEBUG = true; private IconPackHelper mIconPackHelper; private static class CacheEntry { public Bitmap icon; public String title; } private final Bitmap mDefaultIcon; private final Context mContext; private final PackageManager mPackageManager; private final HashMap mCache = new HashMap(INITIAL_ICON_CACHE_CAPACITY); private int mIconDpi; public IconCache(Context context) { ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); mContext = context; mPackageManager = context.getPackageManager(); mIconDpi = activityManager.getLauncherLargeIconDensity(); // need to set mIconDpi before getting default icon mDefaultIcon = makeDefaultIcon(); mIconPackHelper = new IconPackHelper(context); loadIconPack(); } public Drawable getFullResDefaultActivityIcon() { return getFullResIcon(Resources.getSystem(), android.R.mipmap.sym_def_app_icon); } public 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 (PackageManager.NameNotFoundException e) { resources = null; } if (resources != null) { if (iconId != 0) { return getFullResIcon(resources, iconId); } } return getFullResDefaultActivityIcon(); } public Drawable getFullResIcon(ResolveInfo info) { return getFullResIcon(info.activityInfo); } public Drawable getFullResIcon(ActivityInfo info) { Resources resources; try { resources = mPackageManager.getResourcesForApplication( info.applicationInfo); } catch (PackageManager.NameNotFoundException e) { resources = null; } if (resources != null) { int iconId = 0; if (mIconPackHelper != null && mIconPackHelper.isIconPackLoaded()) { iconId = mIconPackHelper.getResourceIdForActivityIcon(info); if (iconId != 0) { return getFullResIcon(mIconPackHelper.getIconPackResources(), iconId); } } iconId = info.getIconResource(); if (iconId != 0) { return getFullResIcon(resources, iconId); } } return getFullResDefaultActivityIcon(); } private Bitmap makeDefaultIcon() { Drawable d = getFullResDefaultActivityIcon(); 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; } private void loadIconPack() { mIconPackHelper.unloadIconPack(); String iconPack = SettingsProvider.getStringCustomDefault(mContext, SettingsProvider.SETTINGS_UI_GENERAL_ICONS_ICON_PACK, ""); if (!TextUtils.isEmpty(iconPack) && !mIconPackHelper.loadIconPack(iconPack)) { SettingsProvider.putString(mContext, SettingsProvider.SETTINGS_UI_GENERAL_ICONS_ICON_PACK, ""); } } /** * Remove any records for the supplied ComponentName. */ public void remove(ComponentName componentName) { synchronized (mCache) { mCache.remove(componentName); } } /** * Remove any records for the supplied package name. */ public void remove(String packageName) { HashSet forDeletion = new HashSet(); for (ComponentName componentName: mCache.keySet()) { if (componentName.getPackageName().equals(packageName)) { forDeletion.add(componentName); } } for (ComponentName condemned: forDeletion) { remove(condemned); } } /** * Empty out the cache. */ public void flush() { synchronized (mCache) { mCache.clear(); } loadIconPack(); } /** * Empty out the cache that aren't of the correct grid size */ public void flushInvalidIcons(DeviceProfile grid) { synchronized (mCache) { Iterator> it = mCache.entrySet().iterator(); while (it.hasNext()) { final CacheEntry e = it.next().getValue(); if (e.icon.getWidth() < grid.iconSizePx || e.icon.getHeight() < grid.iconSizePx) { it.remove(); } } } } /** * Fill in "application" with the icon and label for "info." */ public void getTitleAndIcon(AppInfo application, ResolveInfo info, HashMap labelCache) { synchronized (mCache) { CacheEntry entry = cacheLocked(application.componentName, info, labelCache); application.title = entry.title; application.iconBitmap = entry.icon; } } public Bitmap getIcon(Intent intent) { return getIcon(intent, null); } public Bitmap getIcon(Intent intent, String title) { synchronized (mCache) { final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0); ComponentName component = intent.getComponent(); if (component == null) { return mDefaultIcon; } CacheEntry entry = cacheLocked(component, resolveInfo, null); if (title != null) { entry.title = title; } return entry.icon; } } public Bitmap getIcon(ComponentName component, ResolveInfo resolveInfo, HashMap labelCache) { synchronized (mCache) { if (resolveInfo == null || component == null) { return null; } CacheEntry entry = cacheLocked(component, resolveInfo, labelCache); return entry.icon; } } public boolean isDefaultIcon(Bitmap icon) { return mDefaultIcon == icon; } private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info, HashMap labelCache) { CacheEntry entry = mCache.get(componentName); if (entry == null) { entry = new CacheEntry(); mCache.put(componentName, entry); if (info != null) { ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info); if (labelCache != null && labelCache.containsKey(key)) { entry.title = labelCache.get(key).toString(); } else { entry.title = info.loadLabel(mPackageManager).toString(); if (labelCache != null) { labelCache.put(key, entry.title); } } if (entry.title == null) { entry.title = info.activityInfo.name; } entry.icon = Utilities.createIconBitmap( getFullResIcon(info), mContext); } else { entry.title = ""; Bitmap preloaded = getPreloadedIcon(componentName); if (preloaded != null) { if (DEBUG) Log.d(TAG, "using preloaded icon for " + componentName.toShortString()); entry.icon = preloaded; } else { if (DEBUG) Log.d(TAG, "using default icon for " + componentName.toShortString()); entry.icon = mDefaultIcon; } } } return entry; } public HashMap getAllIcons() { synchronized (mCache) { HashMap set = new HashMap(); for (ComponentName cn : mCache.keySet()) { final CacheEntry e = mCache.get(cn); set.put(cn, e.icon); } return set; } } /** * Pre-load an icon into the persistent cache. * *

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 (PackageManager.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(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); } } } } /** * 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) { final String key = componentName.flattenToShortString(); 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, e); } 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); } } } if (icon != null) { // TODO: handle alpha mask in the view layer Bitmap b = Bitmap.createBitmap(Math.max(icon.getWidth(), 1), Math.max(icon.getHeight(), 1), Bitmap.Config.ARGB_8888); Canvas c = new Canvas(b); Paint paint = new Paint(); paint.setAlpha(127); c.drawBitmap(icon, 0, 0, paint); c.setBitmap(null); icon.recycle(); icon = b; } return icon; } /** * Remove a pre-loaded icon from the persistent icon cache. * * @param componentName the component that should own the icon * @returns true on success */ public boolean deletePreloadedIcon(ComponentName componentName) { if (componentName == null) { return false; } if (mCache.remove(componentName) != null) { if (DEBUG) Log.d(TAG, "removed pre-loaded icon from the in-memory cache"); } boolean success = mContext.deleteFile(getResourceFilename(componentName)); if (DEBUG && success) Log.d(TAG, "removed pre-loaded icon from persistent cache"); return success; } private static String getResourceFilename(ComponentName component) { String resourceName = component.flattenToShortString(); String filename = resourceName.replace(File.separatorChar, '_'); return RESOURCE_FILE_PREFIX + filename; } }