Home | History | Annotate | Download | only in quicksearchbox
      1 /*
      2  * Copyright (C) 2009 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.quicksearchbox;
     18 
     19 import com.android.quicksearchbox.util.CachedLater;
     20 import com.android.quicksearchbox.util.NamedTask;
     21 import com.android.quicksearchbox.util.NamedTaskExecutor;
     22 import com.android.quicksearchbox.util.Now;
     23 import com.android.quicksearchbox.util.NowOrLater;
     24 import com.android.quicksearchbox.util.Util;
     25 
     26 import android.content.ContentResolver;
     27 import android.content.Context;
     28 import android.content.pm.PackageManager;
     29 import android.content.pm.PackageManager.NameNotFoundException;
     30 import android.content.res.Resources;
     31 import android.graphics.drawable.Drawable;
     32 import android.net.Uri;
     33 import android.os.Handler;
     34 import android.text.TextUtils;
     35 import android.util.Log;
     36 
     37 import java.io.FileNotFoundException;
     38 import java.io.IOException;
     39 import java.io.InputStream;
     40 import java.util.List;
     41 
     42 /**
     43  * Loads icons from other packages.
     44  *
     45  * Code partly stolen from {@link ContentResolver} and android.app.SuggestionsAdapter.
     46   */
     47 public class PackageIconLoader implements IconLoader {
     48 
     49     private static final boolean DBG = false;
     50     private static final String TAG = "QSB.PackageIconLoader";
     51 
     52     private final Context mContext;
     53 
     54     private final String mPackageName;
     55 
     56     private Context mPackageContext;
     57 
     58     private final Handler mUiThread;
     59 
     60     private final NamedTaskExecutor mIconLoaderExecutor;
     61 
     62     /**
     63      * Creates a new icon loader.
     64      *
     65      * @param context The QSB application context.
     66      * @param packageName The name of the package from which the icons will be loaded.
     67      *        Resource IDs without an explicit package will be resolved against the package
     68      *        of this context.
     69      */
     70     public PackageIconLoader(Context context, String packageName, Handler uiThread,
     71             NamedTaskExecutor iconLoaderExecutor) {
     72         mContext = context;
     73         mPackageName = packageName;
     74         mUiThread = uiThread;
     75         mIconLoaderExecutor = iconLoaderExecutor;
     76     }
     77 
     78     private boolean ensurePackageContext() {
     79         if (mPackageContext == null) {
     80             try {
     81                 mPackageContext = mContext.createPackageContext(mPackageName,
     82                         Context.CONTEXT_RESTRICTED);
     83             } catch (PackageManager.NameNotFoundException ex) {
     84                 // This should only happen if the app has just be uninstalled
     85                 Log.e(TAG, "Application not found " + mPackageName);
     86                 return false;
     87             }
     88         }
     89         return true;
     90     }
     91 
     92     public NowOrLater<Drawable> getIcon(final String drawableId) {
     93         if (DBG) Log.d(TAG, "getIcon(" + drawableId + ")");
     94         if (TextUtils.isEmpty(drawableId) || "0".equals(drawableId)) {
     95             return new Now<Drawable>(null);
     96         }
     97         if (!ensurePackageContext()) {
     98             return new Now<Drawable>(null);
     99         }
    100         NowOrLater<Drawable> drawable;
    101         try {
    102             // First, see if it's just an integer
    103             int resourceId = Integer.parseInt(drawableId);
    104             // If so, find it by resource ID
    105             Drawable icon = mPackageContext.getResources().getDrawable(resourceId);
    106             drawable = new Now<Drawable>(icon);
    107         } catch (NumberFormatException nfe) {
    108             // It's not an integer, use it as a URI
    109             Uri uri = Uri.parse(drawableId);
    110             if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) {
    111                 // load all resources synchronously, to reduce UI flickering
    112                 drawable = new Now<Drawable>(getDrawable(uri));
    113             } else {
    114                 drawable = new IconLaterTask(uri);
    115             }
    116         } catch (Resources.NotFoundException nfe) {
    117             // It was an integer, but it couldn't be found, bail out
    118             Log.w(TAG, "Icon resource not found: " + drawableId);
    119             drawable = new Now<Drawable>(null);
    120         }
    121         return drawable;
    122     }
    123 
    124     public Uri getIconUri(String drawableId) {
    125         if (TextUtils.isEmpty(drawableId) || "0".equals(drawableId)) {
    126             return null;
    127         }
    128         if (!ensurePackageContext()) return null;
    129         try {
    130             int resourceId = Integer.parseInt(drawableId);
    131             return Util.getResourceUri(mPackageContext, resourceId);
    132         } catch (NumberFormatException nfe) {
    133             return Uri.parse(drawableId);
    134         }
    135     }
    136 
    137     /**
    138      * Gets a drawable by URI.
    139      *
    140      * @return A drawable, or {@code null} if the drawable could not be loaded.
    141      */
    142     private Drawable getDrawable(Uri uri) {
    143         try {
    144             String scheme = uri.getScheme();
    145             if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
    146                 // Load drawables through Resources, to get the source density information
    147                 OpenResourceIdResult r = getResourceId(uri);
    148                 try {
    149                     return r.r.getDrawable(r.id);
    150                 } catch (Resources.NotFoundException ex) {
    151                     throw new FileNotFoundException("Resource does not exist: " + uri);
    152                 }
    153             } else {
    154                 // Let the ContentResolver handle content and file URIs.
    155                 InputStream stream = mPackageContext.getContentResolver().openInputStream(uri);
    156                 if (stream == null) {
    157                     throw new FileNotFoundException("Failed to open " + uri);
    158                 }
    159                 try {
    160                     return Drawable.createFromStream(stream, null);
    161                 } finally {
    162                     try {
    163                         stream.close();
    164                     } catch (IOException ex) {
    165                         Log.e(TAG, "Error closing icon stream for " + uri, ex);
    166                     }
    167                 }
    168             }
    169         } catch (FileNotFoundException fnfe) {
    170             Log.w(TAG, "Icon not found: " + uri + ", " + fnfe.getMessage());
    171             return null;
    172         }
    173     }
    174 
    175     /**
    176      * A resource identified by the {@link Resources} that contains it, and a resource id.
    177      */
    178     private class OpenResourceIdResult {
    179         public Resources r;
    180         public int id;
    181     }
    182 
    183     /**
    184      * Resolves an android.resource URI to a {@link Resources} and a resource id.
    185      */
    186     private OpenResourceIdResult getResourceId(Uri uri) throws FileNotFoundException {
    187         String authority = uri.getAuthority();
    188         Resources r;
    189         if (TextUtils.isEmpty(authority)) {
    190             throw new FileNotFoundException("No authority: " + uri);
    191         } else {
    192             try {
    193                 r = mPackageContext.getPackageManager().getResourcesForApplication(authority);
    194             } catch (NameNotFoundException ex) {
    195                 throw new FileNotFoundException("Failed to get resources: " + ex);
    196             }
    197         }
    198         List<String> path = uri.getPathSegments();
    199         if (path == null) {
    200             throw new FileNotFoundException("No path: " + uri);
    201         }
    202         int len = path.size();
    203         int id;
    204         if (len == 1) {
    205             try {
    206                 id = Integer.parseInt(path.get(0));
    207             } catch (NumberFormatException e) {
    208                 throw new FileNotFoundException("Single path segment is not a resource ID: " + uri);
    209             }
    210         } else if (len == 2) {
    211             id = r.getIdentifier(path.get(1), path.get(0), authority);
    212         } else {
    213             throw new FileNotFoundException("More than two path segments: " + uri);
    214         }
    215         if (id == 0) {
    216             throw new FileNotFoundException("No resource found for: " + uri);
    217         }
    218         OpenResourceIdResult res = new OpenResourceIdResult();
    219         res.r = r;
    220         res.id = id;
    221         return res;
    222     }
    223 
    224     private class IconLaterTask extends CachedLater<Drawable> implements NamedTask {
    225         private final Uri mUri;
    226 
    227         public IconLaterTask(Uri iconUri) {
    228             mUri = iconUri;
    229         }
    230 
    231         @Override
    232         protected void create() {
    233             mIconLoaderExecutor.execute(this);
    234         }
    235 
    236         @Override
    237         public void run() {
    238             final Drawable icon = getIcon();
    239             mUiThread.post(new Runnable(){
    240                 public void run() {
    241                     store(icon);
    242                 }});
    243         }
    244 
    245         @Override
    246         public String getName() {
    247             return mPackageName;
    248         }
    249 
    250         private Drawable getIcon() {
    251             try {
    252                 return getDrawable(mUri);
    253             } catch (Throwable t) {
    254                 // we're making a call into another package, which could throw any exception.
    255                 // Make sure it doesn't crash QSB
    256                 Log.e(TAG, "Failed to load icon " + mUri, t);
    257                 return null;
    258             }
    259         }
    260     }
    261 }
    262