diff --git a/instrumentation/src/androidTest/java/com/bumptech/glide/NonBitmapDrawableResourcesTest.java b/instrumentation/src/androidTest/java/com/bumptech/glide/NonBitmapDrawableResourcesTest.java index 3a57f5293b..efc1fa5d25 100644 --- a/instrumentation/src/androidTest/java/com/bumptech/glide/NonBitmapDrawableResourcesTest.java +++ b/instrumentation/src/androidTest/java/com/bumptech/glide/NonBitmapDrawableResourcesTest.java @@ -395,7 +395,7 @@ public void load_withApplicationIconResourceIdUri_asBitmap_producesNonNullBitmap @Test public void load_withApplicationIconResourceIdUri_asBitmap_withTransformation_nonNullBitmap() - throws NameNotFoundException, ExecutionException, InterruptedException { + throws ExecutionException, InterruptedException { for (String packageName : getInstalledPackages()) { int iconResourceId = getResourceId(packageName); @@ -426,9 +426,8 @@ public void load_withApplicationIconResourceNameUri_asDrawable_producesNonNullDr Uri uri = new Uri.Builder() .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) .authority(packageName) - .path(resources.getResourceTypeName(iconResourceId)) - .path(resources.getResourceEntryName(iconResourceId)) - .path(String.valueOf(iconResourceId)) + .appendPath(resources.getResourceTypeName(iconResourceId)) + .appendPath(resources.getResourceEntryName(iconResourceId)) .build(); Drawable drawable = Glide.with(context) @@ -439,7 +438,6 @@ public void load_withApplicationIconResourceNameUri_asDrawable_producesNonNullDr } } - @Test public void load_withApplicationIconResourceNameUri_asDrawable_withTransform_nonNullDrawable() throws ExecutionException, InterruptedException, NameNotFoundException { @@ -451,9 +449,8 @@ public void load_withApplicationIconResourceNameUri_asDrawable_withTransform_non Uri uri = new Uri.Builder() .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) .authority(packageName) - .path(resources.getResourceTypeName(iconResourceId)) - .path(resources.getResourceEntryName(iconResourceId)) - .path(String.valueOf(iconResourceId)) + .appendPath(resources.getResourceTypeName(iconResourceId)) + .appendPath(resources.getResourceEntryName(iconResourceId)) .build(); Drawable drawable = Glide.with(context) @@ -476,9 +473,8 @@ public void load_withApplicationIconResourceNameUri_asBitmap_producesNonNullBitm Uri uri = new Uri.Builder() .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) .authority(packageName) - .path(resources.getResourceTypeName(iconResourceId)) - .path(resources.getResourceEntryName(iconResourceId)) - .path(String.valueOf(iconResourceId)) + .appendPath(resources.getResourceTypeName(iconResourceId)) + .appendPath(resources.getResourceEntryName(iconResourceId)) .build(); Bitmap bitmap = Glide.with(context) @@ -501,9 +497,8 @@ public void load_withApplicationIconResourceNameUri_asBitmap_withTransform_nonNu Uri uri = new Uri.Builder() .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) .authority(packageName) - .path(resources.getResourceTypeName(iconResourceId)) - .path(resources.getResourceEntryName(iconResourceId)) - .path(String.valueOf(iconResourceId)) + .appendPath(resources.getResourceTypeName(iconResourceId)) + .appendPath(resources.getResourceEntryName(iconResourceId)) .build(); Bitmap bitmap = Glide.with(context) @@ -524,8 +519,10 @@ private Set getInstalledPackages() { packageManager.queryIntentActivities(mainIntent, /*flags=*/ 0); Set result = new HashSet<>(); for (ResolveInfo info : pkgAppsList) { - int iconResourceId = getResourceId(info.activityInfo.packageName); - if (iconResourceId != 0) { + String packageName = info.activityInfo.packageName; + int iconResourceId = getResourceId(packageName); + if (iconResourceId != 0 + && doesApplicationPackageNameMatchResourcePackageName(packageName, iconResourceId)) { result.add(info.activityInfo.packageName); } } @@ -541,4 +538,44 @@ private int getResourceId(String packageName) { } return packageInfo.applicationInfo.icon; } + + /** + * Returns {@code true} iff the resource package name is exactly the same as the containing + * application package name for a given resource id. + * + *

The resource package name is the value returned by + * {@link Resources#getResourcePackageName(int)}. The application package name is package name of + * the enclosing application. If these two things are equal, then we can both construct a Context + * for that package and retrieve a resource id for that package from a "standard" resource Uri + * containing a name instead of an id. If they aren't equal, then we can do only one of the two + * required tasks, so our Uri load will always fail. To handle this properly, we'd need callers to + * include both package names in the Uri. I'm not aware of any standardized Uri format for doing + * so, so these requests will just be treated as unsupported for the time being. + * + *

Take Calendar (emulators API 24 and below) as an example: + *

+ * We can construct one of two possible Uris: + * + * From the first Uri, we can obtain the correct Context/Resources for the calendar package, but + * our attempts to resolve the correct resource id will fail because we do not have the resource + * package name. From the second Uri we cannot obtain the Context/Resources for the calendar + * package because the resource package name doesn't match the application package name. + */ + private boolean doesApplicationPackageNameMatchResourcePackageName( + String applicationPackageName, int iconResourceId) { + try { + Context current = context.createPackageContext(applicationPackageName, /*flags=*/ 0); + String resourcePackageName = current.getResources().getResourcePackageName(iconResourceId); + return applicationPackageName.equals(resourcePackageName); + } catch (NameNotFoundException e) { + // This should never happen + throw new RuntimeException(e); + } + } } diff --git a/library/src/main/java/com/bumptech/glide/RequestManager.java b/library/src/main/java/com/bumptech/glide/RequestManager.java index 2b306eef0f..e145500664 100644 --- a/library/src/main/java/com/bumptech/glide/RequestManager.java +++ b/library/src/main/java/com/bumptech/glide/RequestManager.java @@ -27,6 +27,7 @@ import com.bumptech.glide.manager.RequestManagerTreeNode; import com.bumptech.glide.manager.RequestTracker; import com.bumptech.glide.manager.TargetTracker; +import com.bumptech.glide.request.BaseRequestOptions; import com.bumptech.glide.request.Request; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.RequestOptions; @@ -153,7 +154,7 @@ private void updateRequestOptions(@NonNull RequestOptions toUpdate) { * *

The modified options will only be applied to loads started after this method is called. * - * @see RequestBuilder#apply(com.bumptech.glide.request.BaseRequestOptions) + * @see RequestBuilder#apply(BaseRequestOptions) * * @return This request manager. */ diff --git a/library/src/main/java/com/bumptech/glide/load/resource/drawable/ResourceDrawableDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/drawable/ResourceDrawableDecoder.java index fb0854109e..0923ffceb3 100644 --- a/library/src/main/java/com/bumptech/glide/load/resource/drawable/ResourceDrawableDecoder.java +++ b/library/src/main/java/com/bumptech/glide/load/resource/drawable/ResourceDrawableDecoder.java @@ -3,6 +3,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.net.Uri; import android.support.annotation.DrawableRes; @@ -22,6 +23,18 @@ * other packages. */ public class ResourceDrawableDecoder implements ResourceDecoder { + /** + * The package name to provide {@link Resources#getIdentifier(String, String, String)} when trying + * to find system resource ids. + * + *

As far as I can tell this is undocumented, but works. + */ + private static final String ANDROID_PACKAGE_NAME = "android"; + /** + * {@link Resources#getIdentifier(String, String, String)} documents that it will return 0 and + * that 0 is not a valid resouce id. + */ + private static final int MISSING_RESOURCE_ID = 0; // android.resource:////. private static final int NAME_URI_PATH_SEGMENTS = 2; private static final int TYPE_PATH_SEGMENT_INDEX = 0; @@ -45,49 +58,71 @@ public boolean handles(@NonNull Uri source, @NonNull Options options) { @Override public Resource decode(@NonNull Uri source, int width, int height, @NonNull Options options) { - @DrawableRes int resId = loadResourceIdFromUri(source); String packageName = source.getAuthority(); - Context targetContext = - packageName.equals(context.getPackageName()) - ? context - : getContextForPackage(source, packageName); + Context targetContext = findContextForPackage(source, packageName); + @DrawableRes int resId = findResourceIdFromUri(targetContext, source); // We can't get a theme from another application. Drawable drawable = DrawableDecoderCompat.getDrawable(context, targetContext, resId); return NonOwnedDrawableResource.newInstance(drawable); } @NonNull - private Context getContextForPackage(Uri source, String packageName) { + private Context findContextForPackage(Uri source, String packageName) { + // Fast path + if (packageName.equals(context.getPackageName())) { + return context; + } + try { return context.createPackageContext(packageName, /*flags=*/ 0); } catch (NameNotFoundException e) { + // The parent APK holds the correct context if the resource is located in a split + if (packageName.contains(context.getPackageName())) { + return context; + } + throw new IllegalArgumentException( "Failed to obtain context or unrecognized Uri format for: " + source, e); } } @DrawableRes - private int loadResourceIdFromUri(Uri source) { + private int findResourceIdFromUri(Context context, Uri source) { List segments = source.getPathSegments(); - @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); + return findResourceIdFromTypeAndNameResourceUri(context, source); } else if (segments.size() == ID_PATH_SEGMENTS) { - try { - result = Integer.valueOf(segments.get(RESOURCE_ID_SEGMENT_INDEX)); - } catch (NumberFormatException e) { - // Ignored. - } + return findResourceIdFromResourceIdUri(source); + } else { + throw new IllegalArgumentException("Unrecognized Uri format: " + source); } + } - if (result == null) { - throw new IllegalArgumentException("Unrecognized Uri format: " + source); - } else if (result == 0) { - throw new IllegalArgumentException("Failed to obtain resource id for: " + source); + // android.resource://com.android.camera2/mipmap/logo_camera_color + @DrawableRes + private int findResourceIdFromTypeAndNameResourceUri(Context context, Uri source) { + List segments = source.getPathSegments(); + String packageName = source.getAuthority(); + String typeName = segments.get(TYPE_PATH_SEGMENT_INDEX); + String resourceName = segments.get(NAME_PATH_SEGMENT_INDEX); + int result = context.getResources().getIdentifier(resourceName, typeName, packageName); + if (result == MISSING_RESOURCE_ID) { + result = Resources.getSystem().getIdentifier(resourceName, typeName, ANDROID_PACKAGE_NAME); + } + if (result == MISSING_RESOURCE_ID) { + throw new IllegalArgumentException("Failed to find resource id for: " + source); } return result; } + + // android.resource://com.android.camera2/123456 + @DrawableRes + private int findResourceIdFromResourceIdUri(Uri source) { + List segments = source.getPathSegments(); + try { + return Integer.parseInt(segments.get(RESOURCE_ID_SEGMENT_INDEX)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Unrecognized Uri format: " + source, e); + } + } } diff --git a/library/src/main/java/com/bumptech/glide/request/target/CustomTarget.java b/library/src/main/java/com/bumptech/glide/request/target/CustomTarget.java index bc14314e6a..ce7ebba38d 100644 --- a/library/src/main/java/com/bumptech/glide/request/target/CustomTarget.java +++ b/library/src/main/java/com/bumptech/glide/request/target/CustomTarget.java @@ -57,7 +57,7 @@ public CustomTarget() { } /** - * Creates a new {@code CustomTarget} that will return the given {@code width} and {@code length} + * Creates a new {@code CustomTarget} that will return the given {@code width} and {@code height} * as the requested size (unless overridden by * {@link com.bumptech.glide.request.RequestOptions#override(int)} in the request). *