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