Skip to content

Commit

Permalink
Handle loading application icons and resources outside our package.
Browse files Browse the repository at this point in the history
This also adds support for an additional Uri format that uses an integer
resource id in the path instead of a type and a name.
  • Loading branch information
sjudd committed Oct 19, 2017
1 parent 69700c4 commit a0628b8
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 29 deletions.
1 change: 1 addition & 0 deletions library/src/main/java/com/bumptech/glide/Glide.java
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ private static GeneratedAppGlideModule getAnnotationGeneratedGlideModules() {
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
.append(byte[].class, ByteBuffer.class, new ByteArrayLoader.ByteBufferFactory())
.append(byte[].class, InputStream.class, new ByteArrayLoader.StreamFactory())
.append(Uri.class, Uri.class, new UnitModelLoader.Factory<Uri>())
/* Transcoders */
.register(Bitmap.class, BitmapDrawable.class,
new BitmapDrawableTranscoder(resources, bitmapPool))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,19 @@ private DrawableDecoderCompat() {
// Utility class.
}

/**
* @see #getDrawable(Context, int, Theme)
*/
public static Drawable getDrawable(Context context, @DrawableRes int id) {
return getDrawable(context, id, /*theme=*/ null);
}

/**
* Loads a Drawable using {@link AppCompatResources} if available and {@link ResourcesCompat}
* otherwise, depending on whether or not the v7 support library is included in the application.
*
* @param theme Used instead of the {@link Theme} returned from the given {@link Context} if
* non-null when loading the {@link Drawable}.
*/
public static Drawable getDrawable(Context context, @DrawableRes int id, @Nullable Theme theme) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.ResourceDecoder;
Expand All @@ -12,13 +15,21 @@
import java.util.List;

/**
* Decodes {@link Drawable}s given resource {@link Uri}s in the form
* android.resource://<package_name>/<type>/<name>.
* Decodes {@link Drawable}s given resource {@link Uri}s.
*
* <p>This is typically used as a fallback for resource types that either aren't Bitmaps (see #350)
* or for resource types that we can't obtain an {@link java.io.InputStream} for using a standard
* {@link ContentResolver}, including some types of application icons and resources loaded from
* other packages.
*/
public class ResourceDrawableDecoder implements ResourceDecoder<Uri, Drawable> {
private static final int EXPECTED_PATH_SEGMENTS = 2;
// android.resource://<package_name>/<type>/<name>.
private static final int NAME_URI_PATH_SEGMENTS = 2;
private static final int TYPE_PATH_SEGMENT_INDEX = 0;
private static final int NAME_PATH_SEGMENT_INDEX = 1;
// android.resource://<package_name>/<resource_id>
private static final int ID_PATH_SEGMENTS = 1;
private static final int RESOURCE_ID_SEGMENT_INDEX = 0;

private final Context context;

Expand All @@ -35,36 +46,72 @@ public boolean handles(Uri source, Options options) throws IOException {
@Override
public Resource<Drawable> decode(Uri source, int width, int height, Options options)
throws IOException {
// Parsing is based on the logic in ResourceLoader/the android framework that constructs
// resource Uris.
@DrawableRes int resId = loadResourceIdFromUri(source);
String packageName = source.getAuthority();
Context toUse = packageName.equals(context.getPackageName())
? context : getContextForPackage(source, packageName);
// We can't get a theme from another application.
Drawable drawable = DrawableDecoderCompat.getDrawable(toUse, resId);
return new InternalDrawableResource(drawable);
}

@NonNull
private Context getContextForPackage(Uri source, String packageName) {
try {
return context.createPackageContext(packageName, /*flags=*/ 0);
} catch (NameNotFoundException e) {
throw new IllegalArgumentException(
"Failed to obtain context or unrecognized Uri format for: " + source, e);
}
}

@DrawableRes
private int loadResourceIdFromUri(Uri source) {
List<String> segments = source.getPathSegments();
if (segments.size() != EXPECTED_PATH_SEGMENTS) {
throw new IOException("Unexpected path segments for: " + source + " segments: " + segments);
@DrawableRes Integer result = null;
if (segments.size() == NAME_URI_PATH_SEGMENTS) {
String packageName = source.getAuthority();
String typeName = segments.get(TYPE_PATH_SEGMENT_INDEX);
String resourceName = segments.get(NAME_PATH_SEGMENT_INDEX);
result = context.getResources().getIdentifier(resourceName, typeName, packageName);
} else if (segments.size() == ID_PATH_SEGMENTS) {
try {
result = Integer.valueOf(segments.get(RESOURCE_ID_SEGMENT_INDEX));
} catch (NumberFormatException e) {
// Ignored.
}
}

if (result == null) {
throw new IllegalArgumentException("Unrecognized Uri format: " + source);
} else if (result == 0) {
throw new IllegalArgumentException("Failed to obtain resource id for: " + source);
}
return result;
}

private static final class InternalDrawableResource extends DrawableResource<Drawable> {

InternalDrawableResource(Drawable drawable) {
super(drawable);
}
String packageName = source.getAuthority();
String typeName = segments.get(TYPE_PATH_SEGMENT_INDEX);
String resourceName = segments.get(NAME_PATH_SEGMENT_INDEX);
int id = context.getResources().getIdentifier(resourceName, typeName, packageName);
Drawable drawable = DrawableDecoderCompat.getDrawable(context, id, null /*theme*/);
if (drawable == null) {
throw new IOException("ContextCompat#getDrawable returned null for: " + source);

@SuppressWarnings("unchecked")
@Override
public Class<Drawable> getResourceClass() {
return (Class<Drawable>) drawable.getClass();
}
return new DrawableResource<Drawable>(drawable) {
@SuppressWarnings("unchecked")
@Override
public Class<Drawable> getResourceClass() {
return (Class<Drawable>) drawable.getClass();
}

@Override
public int getSize() {
return 1;
}
@Override
public int getSize() {
// 4 bytes per pixel for ARGB_8888 Bitmaps is something of a reasonable approximation. If
// there are no intrinsic bounds, we can fall back just to 1.
return Math.max(1, drawable.getIntrinsicWidth() * drawable.getIntrinsicHeight() * 4);
}

@Override
public void recycle() {
// Do nothing.
}
};
@Override
public void recycle() {
// Do nothing.
}
}
}

0 comments on commit a0628b8

Please sign in to comment.