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