Home | History | Annotate | Download | only in launcher3
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.launcher3;
     18 
     19 import com.android.launcher3.backup.BackupProtos;
     20 
     21 import android.app.ActivityManager;
     22 import android.content.ComponentName;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.pm.ActivityInfo;
     26 import android.content.pm.PackageManager;
     27 import android.content.pm.ResolveInfo;
     28 import android.content.res.Resources;
     29 import android.graphics.Bitmap;
     30 import android.graphics.BitmapFactory;
     31 import android.graphics.Canvas;
     32 import android.graphics.Paint;
     33 import android.graphics.drawable.Drawable;
     34 import android.util.Log;
     35 
     36 import java.io.ByteArrayOutputStream;
     37 import java.io.File;
     38 import java.io.FileInputStream;
     39 import java.io.FileNotFoundException;
     40 import java.io.FileOutputStream;
     41 import java.io.IOException;
     42 import java.util.HashMap;
     43 import java.util.HashSet;
     44 import java.util.Iterator;
     45 import java.util.Map.Entry;
     46 
     47 /**
     48  * Cache of application icons.  Icons can be made from any thread.
     49  */
     50 public class IconCache {
     51     @SuppressWarnings("unused")
     52     private static final String TAG = "Launcher.IconCache";
     53 
     54     private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
     55     private static final String RESOURCE_FILE_PREFIX = "icon_";
     56 
     57     private static final boolean DEBUG = true;
     58 
     59     private static class CacheEntry {
     60         public Bitmap icon;
     61         public String title;
     62     }
     63 
     64     private final Bitmap mDefaultIcon;
     65     private final Context mContext;
     66     private final PackageManager mPackageManager;
     67     private final HashMap<ComponentName, CacheEntry> mCache =
     68             new HashMap<ComponentName, CacheEntry>(INITIAL_ICON_CACHE_CAPACITY);
     69     private int mIconDpi;
     70 
     71     public IconCache(Context context) {
     72         ActivityManager activityManager =
     73                 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
     74 
     75         mContext = context;
     76         mPackageManager = context.getPackageManager();
     77         mIconDpi = activityManager.getLauncherLargeIconDensity();
     78 
     79         // need to set mIconDpi before getting default icon
     80         mDefaultIcon = makeDefaultIcon();
     81     }
     82 
     83     public Drawable getFullResDefaultActivityIcon() {
     84         return getFullResIcon(Resources.getSystem(),
     85                 android.R.mipmap.sym_def_app_icon);
     86     }
     87 
     88     public Drawable getFullResIcon(Resources resources, int iconId) {
     89         Drawable d;
     90         try {
     91             d = resources.getDrawableForDensity(iconId, mIconDpi);
     92         } catch (Resources.NotFoundException e) {
     93             d = null;
     94         }
     95 
     96         return (d != null) ? d : getFullResDefaultActivityIcon();
     97     }
     98 
     99     public Drawable getFullResIcon(String packageName, int iconId) {
    100         Resources resources;
    101         try {
    102             resources = mPackageManager.getResourcesForApplication(packageName);
    103         } catch (PackageManager.NameNotFoundException e) {
    104             resources = null;
    105         }
    106         if (resources != null) {
    107             if (iconId != 0) {
    108                 return getFullResIcon(resources, iconId);
    109             }
    110         }
    111         return getFullResDefaultActivityIcon();
    112     }
    113 
    114     public Drawable getFullResIcon(ResolveInfo info) {
    115         return getFullResIcon(info.activityInfo);
    116     }
    117 
    118     public Drawable getFullResIcon(ActivityInfo info) {
    119 
    120         Resources resources;
    121         try {
    122             resources = mPackageManager.getResourcesForApplication(
    123                     info.applicationInfo);
    124         } catch (PackageManager.NameNotFoundException e) {
    125             resources = null;
    126         }
    127         if (resources != null) {
    128             int iconId = info.getIconResource();
    129             if (iconId != 0) {
    130                 return getFullResIcon(resources, iconId);
    131             }
    132         }
    133 
    134         return getFullResDefaultActivityIcon();
    135     }
    136 
    137     private Bitmap makeDefaultIcon() {
    138         Drawable d = getFullResDefaultActivityIcon();
    139         Bitmap b = Bitmap.createBitmap(Math.max(d.getIntrinsicWidth(), 1),
    140                 Math.max(d.getIntrinsicHeight(), 1),
    141                 Bitmap.Config.ARGB_8888);
    142         Canvas c = new Canvas(b);
    143         d.setBounds(0, 0, b.getWidth(), b.getHeight());
    144         d.draw(c);
    145         c.setBitmap(null);
    146         return b;
    147     }
    148 
    149     /**
    150      * Remove any records for the supplied ComponentName.
    151      */
    152     public void remove(ComponentName componentName) {
    153         synchronized (mCache) {
    154             mCache.remove(componentName);
    155         }
    156     }
    157 
    158     /**
    159      * Remove any records for the supplied package name.
    160      */
    161     public void remove(String packageName) {
    162         HashSet<ComponentName> forDeletion = new HashSet<ComponentName>();
    163         for (ComponentName componentName: mCache.keySet()) {
    164             if (componentName.getPackageName().equals(packageName)) {
    165                 forDeletion.add(componentName);
    166             }
    167         }
    168         for (ComponentName condemned: forDeletion) {
    169             remove(condemned);
    170         }
    171     }
    172 
    173     /**
    174      * Empty out the cache.
    175      */
    176     public void flush() {
    177         synchronized (mCache) {
    178             mCache.clear();
    179         }
    180     }
    181 
    182     /**
    183      * Empty out the cache that aren't of the correct grid size
    184      */
    185     public void flushInvalidIcons(DeviceProfile grid) {
    186         synchronized (mCache) {
    187             Iterator<Entry<ComponentName, CacheEntry>> it = mCache.entrySet().iterator();
    188             while (it.hasNext()) {
    189                 final CacheEntry e = it.next().getValue();
    190                 if (e.icon.getWidth() < grid.iconSizePx || e.icon.getHeight() < grid.iconSizePx) {
    191                     it.remove();
    192                 }
    193             }
    194         }
    195     }
    196 
    197     /**
    198      * Fill in "application" with the icon and label for "info."
    199      */
    200     public void getTitleAndIcon(AppInfo application, ResolveInfo info,
    201             HashMap<Object, CharSequence> labelCache) {
    202         synchronized (mCache) {
    203             CacheEntry entry = cacheLocked(application.componentName, info, labelCache);
    204 
    205             application.title = entry.title;
    206             application.iconBitmap = entry.icon;
    207         }
    208     }
    209 
    210     public Bitmap getIcon(Intent intent) {
    211         return getIcon(intent, null);
    212     }
    213 
    214     public Bitmap getIcon(Intent intent, String title) {
    215         synchronized (mCache) {
    216             final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0);
    217             ComponentName component = intent.getComponent();
    218 
    219             if (component == null) {
    220                 return mDefaultIcon;
    221             }
    222 
    223             CacheEntry entry = cacheLocked(component, resolveInfo, null);
    224             if (title != null) {
    225                 entry.title = title;
    226             }
    227             return entry.icon;
    228         }
    229     }
    230 
    231     public Bitmap getIcon(ComponentName component, ResolveInfo resolveInfo,
    232             HashMap<Object, CharSequence> labelCache) {
    233         synchronized (mCache) {
    234             if (resolveInfo == null || component == null) {
    235                 return null;
    236             }
    237 
    238             CacheEntry entry = cacheLocked(component, resolveInfo, labelCache);
    239             return entry.icon;
    240         }
    241     }
    242 
    243     public boolean isDefaultIcon(Bitmap icon) {
    244         return mDefaultIcon == icon;
    245     }
    246 
    247     private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info,
    248             HashMap<Object, CharSequence> labelCache) {
    249         CacheEntry entry = mCache.get(componentName);
    250         if (entry == null) {
    251             entry = new CacheEntry();
    252 
    253             mCache.put(componentName, entry);
    254 
    255             if (info != null) {
    256                 ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);
    257                 if (labelCache != null && labelCache.containsKey(key)) {
    258                     entry.title = labelCache.get(key).toString();
    259                 } else {
    260                     entry.title = info.loadLabel(mPackageManager).toString();
    261                     if (labelCache != null) {
    262                         labelCache.put(key, entry.title);
    263                     }
    264                 }
    265                 if (entry.title == null) {
    266                     entry.title = info.activityInfo.name;
    267                 }
    268 
    269                 entry.icon = Utilities.createIconBitmap(
    270                         getFullResIcon(info), mContext);
    271             } else {
    272                 entry.title = "";
    273                 Bitmap preloaded = getPreloadedIcon(componentName);
    274                 if (preloaded != null) {
    275                     if (DEBUG) Log.d(TAG, "using preloaded icon for " +
    276                             componentName.toShortString());
    277                     entry.icon = preloaded;
    278                 } else {
    279                     if (DEBUG) Log.d(TAG, "using default icon for " +
    280                             componentName.toShortString());
    281                     entry.icon = mDefaultIcon;
    282                 }
    283             }
    284         }
    285         return entry;
    286     }
    287 
    288     public HashMap<ComponentName,Bitmap> getAllIcons() {
    289         synchronized (mCache) {
    290             HashMap<ComponentName,Bitmap> set = new HashMap<ComponentName,Bitmap>();
    291             for (ComponentName cn : mCache.keySet()) {
    292                 final CacheEntry e = mCache.get(cn);
    293                 set.put(cn, e.icon);
    294             }
    295             return set;
    296         }
    297     }
    298 
    299     /**
    300      * Pre-load an icon into the persistent cache.
    301      *
    302      * <P>Queries for a component that does not exist in the package manager
    303      * will be answered by the persistent cache.
    304      *
    305      * @param context application context
    306      * @param componentName the icon should be returned for this component
    307      * @param icon the icon to be persisted
    308      * @param dpi the native density of the icon
    309      */
    310     public static void preloadIcon(Context context, ComponentName componentName, Bitmap icon,
    311             int dpi) {
    312         // TODO rescale to the correct native DPI
    313         try {
    314             PackageManager packageManager = context.getPackageManager();
    315             packageManager.getActivityIcon(componentName);
    316             // component is present on the system already, do nothing
    317             return;
    318         } catch (PackageManager.NameNotFoundException e) {
    319             // pass
    320         }
    321 
    322         final String key = componentName.flattenToString();
    323         FileOutputStream resourceFile = null;
    324         try {
    325             resourceFile = context.openFileOutput(getResourceFilename(componentName),
    326                     Context.MODE_PRIVATE);
    327             ByteArrayOutputStream os = new ByteArrayOutputStream();
    328             if (icon.compress(android.graphics.Bitmap.CompressFormat.PNG, 75, os)) {
    329                 byte[] buffer = os.toByteArray();
    330                 resourceFile.write(buffer, 0, buffer.length);
    331             } else {
    332                 Log.w(TAG, "failed to encode cache for " + key);
    333                 return;
    334             }
    335         } catch (FileNotFoundException e) {
    336             Log.w(TAG, "failed to pre-load cache for " + key, e);
    337         } catch (IOException e) {
    338             Log.w(TAG, "failed to pre-load cache for " + key, e);
    339         } finally {
    340             if (resourceFile != null) {
    341                 try {
    342                     resourceFile.close();
    343                 } catch (IOException e) {
    344                     Log.d(TAG, "failed to save restored icon for: " + key, e);
    345                 }
    346             }
    347         }
    348     }
    349 
    350     /**
    351      * Read a pre-loaded icon from the persistent icon cache.
    352      *
    353      * @param componentName the component that should own the icon
    354      * @returns a bitmap if one is cached, or null.
    355      */
    356     private Bitmap getPreloadedIcon(ComponentName componentName) {
    357         final String key = componentName.flattenToShortString();
    358 
    359         if (DEBUG) Log.v(TAG, "looking for pre-load icon for " + key);
    360         Bitmap icon = null;
    361         FileInputStream resourceFile = null;
    362         try {
    363             resourceFile = mContext.openFileInput(getResourceFilename(componentName));
    364             byte[] buffer = new byte[1024];
    365             ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    366             int bytesRead = 0;
    367             while(bytesRead >= 0) {
    368                 bytes.write(buffer, 0, bytesRead);
    369                 bytesRead = resourceFile.read(buffer, 0, buffer.length);
    370             }
    371             if (DEBUG) Log.d(TAG, "read " + bytes.size());
    372             icon = BitmapFactory.decodeByteArray(bytes.toByteArray(), 0, bytes.size());
    373             if (icon == null) {
    374                 Log.w(TAG, "failed to decode pre-load icon for " + key);
    375             }
    376         } catch (FileNotFoundException e) {
    377             if (DEBUG) Log.d(TAG, "there is no restored icon for: " + key, e);
    378         } catch (IOException e) {
    379             Log.w(TAG, "failed to read pre-load icon for: " + key, e);
    380         } finally {
    381             if(resourceFile != null) {
    382                 try {
    383                     resourceFile.close();
    384                 } catch (IOException e) {
    385                     Log.d(TAG, "failed to manage pre-load icon file: " + key, e);
    386                 }
    387             }
    388         }
    389 
    390         if (icon != null) {
    391             // TODO: handle alpha mask in the view layer
    392             Bitmap b = Bitmap.createBitmap(Math.max(icon.getWidth(), 1),
    393                     Math.max(icon.getHeight(), 1),
    394                     Bitmap.Config.ARGB_8888);
    395             Canvas c = new Canvas(b);
    396             Paint paint = new Paint();
    397             paint.setAlpha(127);
    398             c.drawBitmap(icon, 0, 0, paint);
    399             c.setBitmap(null);
    400             icon.recycle();
    401             icon = b;
    402         }
    403 
    404         return icon;
    405     }
    406 
    407     /**
    408      * Remove a pre-loaded icon from the persistent icon cache.
    409      *
    410      * @param componentName the component that should own the icon
    411      * @returns true on success
    412      */
    413     public boolean deletePreloadedIcon(ComponentName componentName) {
    414         if (componentName == null) {
    415             return false;
    416         }
    417         if (mCache.remove(componentName) != null) {
    418             if (DEBUG) Log.d(TAG, "removed pre-loaded icon from the in-memory cache");
    419         }
    420         boolean success = mContext.deleteFile(getResourceFilename(componentName));
    421         if (DEBUG && success) Log.d(TAG, "removed pre-loaded icon from persistent cache");
    422 
    423         return success;
    424     }
    425 
    426     private static String getResourceFilename(ComponentName component) {
    427         String resourceName = component.flattenToShortString();
    428         String filename = resourceName.replace(File.separatorChar, '_');
    429         return RESOURCE_FILE_PREFIX + filename;
    430     }
    431 }
    432