| /* |
| * 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.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; |
| |
| /** |
| * 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 static class CacheEntry { |
| public Bitmap icon; |
| public String title; |
| } |
| |
| private final Bitmap mDefaultIcon; |
| private final Context mContext; |
| private final PackageManager mPackageManager; |
| private final HashMap<ComponentName, CacheEntry> mCache = |
| new HashMap<ComponentName, 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(); |
| mIconDpi = activityManager.getLauncherLargeIconDensity(); |
| |
| // need to set mIconDpi before getting default icon |
| mDefaultIcon = makeDefaultIcon(); |
| } |
| |
| 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 = 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; |
| } |
| |
| /** |
| * 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<ComponentName> forDeletion = new HashSet<ComponentName>(); |
| 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(); |
| } |
| } |
| |
| /** |
| * Empty out the cache that aren't of the correct grid size |
| */ |
| public void flushInvalidIcons(DeviceProfile grid) { |
| synchronized (mCache) { |
| Iterator<Entry<ComponentName, CacheEntry>> 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<Object, CharSequence> 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<Object, CharSequence> 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<Object, CharSequence> 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<ComponentName,Bitmap> getAllIcons() { |
| synchronized (mCache) { |
| HashMap<ComponentName,Bitmap> set = new HashMap<ComponentName,Bitmap>(); |
| 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. |
| * |
| * <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 (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; |
| } |
| } |