| /* |
| * Copyright (C) 2009 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.quicksearchbox; |
| |
| import com.android.quicksearchbox.util.CachedLater; |
| import com.android.quicksearchbox.util.NamedTask; |
| import com.android.quicksearchbox.util.NamedTaskExecutor; |
| import com.android.quicksearchbox.util.Now; |
| import com.android.quicksearchbox.util.NowOrLater; |
| import com.android.quicksearchbox.util.Util; |
| |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.res.Resources; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.os.Handler; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.List; |
| |
| /** |
| * Loads icons from other packages. |
| * |
| * Code partly stolen from {@link ContentResolver} and android.app.SuggestionsAdapter. |
| */ |
| public class PackageIconLoader implements IconLoader { |
| |
| private static final boolean DBG = false; |
| private static final String TAG = "QSB.PackageIconLoader"; |
| |
| private final Context mContext; |
| |
| private final String mPackageName; |
| |
| private Context mPackageContext; |
| |
| private final Handler mUiThread; |
| |
| private final NamedTaskExecutor mIconLoaderExecutor; |
| |
| /** |
| * Creates a new icon loader. |
| * |
| * @param context The QSB application context. |
| * @param packageName The name of the package from which the icons will be loaded. |
| * Resource IDs without an explicit package will be resolved against the package |
| * of this context. |
| */ |
| public PackageIconLoader(Context context, String packageName, Handler uiThread, |
| NamedTaskExecutor iconLoaderExecutor) { |
| mContext = context; |
| mPackageName = packageName; |
| mUiThread = uiThread; |
| mIconLoaderExecutor = iconLoaderExecutor; |
| } |
| |
| private boolean ensurePackageContext() { |
| if (mPackageContext == null) { |
| try { |
| mPackageContext = mContext.createPackageContext(mPackageName, |
| Context.CONTEXT_RESTRICTED); |
| } catch (PackageManager.NameNotFoundException ex) { |
| // This should only happen if the app has just be uninstalled |
| Log.e(TAG, "Application not found " + mPackageName); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public NowOrLater<Drawable> getIcon(final String drawableId) { |
| if (DBG) Log.d(TAG, "getIcon(" + drawableId + ")"); |
| if (TextUtils.isEmpty(drawableId) || "0".equals(drawableId)) { |
| return new Now<Drawable>(null); |
| } |
| if (!ensurePackageContext()) { |
| return new Now<Drawable>(null); |
| } |
| NowOrLater<Drawable> drawable; |
| try { |
| // First, see if it's just an integer |
| int resourceId = Integer.parseInt(drawableId); |
| // If so, find it by resource ID |
| Drawable icon = mPackageContext.getResources().getDrawable(resourceId); |
| drawable = new Now<Drawable>(icon); |
| } catch (NumberFormatException nfe) { |
| // It's not an integer, use it as a URI |
| Uri uri = Uri.parse(drawableId); |
| if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) { |
| // load all resources synchronously, to reduce UI flickering |
| drawable = new Now<Drawable>(getDrawable(uri)); |
| } else { |
| drawable = new IconLaterTask(uri); |
| } |
| } catch (Resources.NotFoundException nfe) { |
| // It was an integer, but it couldn't be found, bail out |
| Log.w(TAG, "Icon resource not found: " + drawableId); |
| drawable = new Now<Drawable>(null); |
| } |
| return drawable; |
| } |
| |
| public Uri getIconUri(String drawableId) { |
| if (TextUtils.isEmpty(drawableId) || "0".equals(drawableId)) { |
| return null; |
| } |
| if (!ensurePackageContext()) return null; |
| try { |
| int resourceId = Integer.parseInt(drawableId); |
| return Util.getResourceUri(mPackageContext, resourceId); |
| } catch (NumberFormatException nfe) { |
| return Uri.parse(drawableId); |
| } |
| } |
| |
| /** |
| * Gets a drawable by URI. |
| * |
| * @return A drawable, or {@code null} if the drawable could not be loaded. |
| */ |
| private Drawable getDrawable(Uri uri) { |
| try { |
| String scheme = uri.getScheme(); |
| if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) { |
| // Load drawables through Resources, to get the source density information |
| OpenResourceIdResult r = getResourceId(uri); |
| try { |
| return r.r.getDrawable(r.id); |
| } catch (Resources.NotFoundException ex) { |
| throw new FileNotFoundException("Resource does not exist: " + uri); |
| } |
| } else { |
| // Let the ContentResolver handle content and file URIs. |
| InputStream stream = mPackageContext.getContentResolver().openInputStream(uri); |
| if (stream == null) { |
| throw new FileNotFoundException("Failed to open " + uri); |
| } |
| try { |
| return Drawable.createFromStream(stream, null); |
| } finally { |
| try { |
| stream.close(); |
| } catch (IOException ex) { |
| Log.e(TAG, "Error closing icon stream for " + uri, ex); |
| } |
| } |
| } |
| } catch (FileNotFoundException fnfe) { |
| Log.w(TAG, "Icon not found: " + uri + ", " + fnfe.getMessage()); |
| return null; |
| } |
| } |
| |
| /** |
| * A resource identified by the {@link Resources} that contains it, and a resource id. |
| */ |
| private class OpenResourceIdResult { |
| public Resources r; |
| public int id; |
| } |
| |
| /** |
| * Resolves an android.resource URI to a {@link Resources} and a resource id. |
| */ |
| private OpenResourceIdResult getResourceId(Uri uri) throws FileNotFoundException { |
| String authority = uri.getAuthority(); |
| Resources r; |
| if (TextUtils.isEmpty(authority)) { |
| throw new FileNotFoundException("No authority: " + uri); |
| } else { |
| try { |
| r = mPackageContext.getPackageManager().getResourcesForApplication(authority); |
| } catch (NameNotFoundException ex) { |
| throw new FileNotFoundException("Failed to get resources: " + ex); |
| } |
| } |
| List<String> path = uri.getPathSegments(); |
| if (path == null) { |
| throw new FileNotFoundException("No path: " + uri); |
| } |
| int len = path.size(); |
| int id; |
| if (len == 1) { |
| try { |
| id = Integer.parseInt(path.get(0)); |
| } catch (NumberFormatException e) { |
| throw new FileNotFoundException("Single path segment is not a resource ID: " + uri); |
| } |
| } else if (len == 2) { |
| id = r.getIdentifier(path.get(1), path.get(0), authority); |
| } else { |
| throw new FileNotFoundException("More than two path segments: " + uri); |
| } |
| if (id == 0) { |
| throw new FileNotFoundException("No resource found for: " + uri); |
| } |
| OpenResourceIdResult res = new OpenResourceIdResult(); |
| res.r = r; |
| res.id = id; |
| return res; |
| } |
| |
| private class IconLaterTask extends CachedLater<Drawable> implements NamedTask { |
| private final Uri mUri; |
| |
| public IconLaterTask(Uri iconUri) { |
| mUri = iconUri; |
| } |
| |
| @Override |
| protected void create() { |
| mIconLoaderExecutor.execute(this); |
| } |
| |
| @Override |
| public void run() { |
| final Drawable icon = getIcon(); |
| mUiThread.post(new Runnable(){ |
| public void run() { |
| store(icon); |
| }}); |
| } |
| |
| @Override |
| public String getName() { |
| return mPackageName; |
| } |
| |
| private Drawable getIcon() { |
| try { |
| return getDrawable(mUri); |
| } catch (Throwable t) { |
| // we're making a call into another package, which could throw any exception. |
| // Make sure it doesn't crash QSB |
| Log.e(TAG, "Failed to load icon " + mUri, t); |
| return null; |
| } |
| } |
| } |
| } |