Skip to content

Commit

Permalink
Support parsing package local resource uris to ids
Browse files Browse the repository at this point in the history
This lets us load resource Uris with the same theming benefits that we
do normal resource ids. This is really only for backwards compatibility
so that we can more readily remove the old ResourceLoader and support
for resource Uris in UriLoader.
  • Loading branch information
sjudd committed Dec 15, 2022
1 parent 5816903 commit a912e0f
Show file tree
Hide file tree
Showing 4 changed files with 329 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
import static com.bumptech.glide.testutil.BitmapSubject.assertThat;
import static org.junit.Assume.assumeTrue;

import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
Expand Down Expand Up @@ -47,7 +50,7 @@ public void before() {
assumeTrue(VERSION.SDK_INT >= VERSION_CODES.Q);
}

// TODO(judds): The way we handle data loads in the background for resoures is not Theme
// TODO(judds): The way we handle data loads in the background for resources is not Theme
// compatible. In particular, the theme gets lost when we convert the resource id to a Uri and
// we don't use the user provided theme. While ResourceBitmapDecoder and ResourceDrawableDecoder
// will use the theme, they're not called for most resource ids because those instead go through
Expand Down Expand Up @@ -105,6 +108,50 @@ public void load_withDarkModeActivity_darkModeTheme_usesDarkModeDrawable() {
.theme(activity.getTheme()));
}

@Test
public void loadResourceNameUri_withDarkModeActivity_darkModeTheme_usesDarkModeDrawable() {
runActivityTest(
darkModeActivity(),
R.raw.dog_dark,
activity ->
Glide.with(activity)
.load(newResourceNameUri(activity, R.drawable.dog))
.override(Target.SIZE_ORIGINAL)
.theme(activity.getTheme()));
}

@Test
public void loadResourceIdUri_withDarkModeActivity_darkModeTheme_usesDarkModeDrawable() {
runActivityTest(
darkModeActivity(),
R.raw.dog_dark,
activity ->
Glide.with(activity)
.load(newResourceIdUri(activity, R.drawable.dog))
.override(Target.SIZE_ORIGINAL)
.theme(activity.getTheme()));
}

private static Uri newResourceNameUri(Context context, int resourceId) {
Resources resources = context.getResources();
return newResourceUriBuilder(context)
.appendPath(resources.getResourceTypeName(resourceId))
.appendPath(resources.getResourceEntryName(resourceId))
.build();
}

private static Uri newResourceIdUri(Context context, int resourceId) {
return newResourceUriBuilder(context)
.appendPath(String.valueOf(resourceId))
.build();
}

private static Uri.Builder newResourceUriBuilder(Context context) {
return new Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(context.getPackageName());
}

@Test
public void load_withDarkModeFragment_darkModeTheme_usesDarkModeDrawable() {
runFragmentTest(
Expand All @@ -117,6 +164,30 @@ public void load_withDarkModeFragment_darkModeTheme_usesDarkModeDrawable() {
.theme(fragment.requireActivity().getTheme()));
}

@Test
public void loadResourceNameUri_withDarkModeFragment_darkModeTheme_usesDarkModeDrawable() {
runFragmentTest(
darkModeActivity(),
R.raw.dog_dark,
fragment ->
Glide.with(fragment)
.load(newResourceNameUri(fragment.requireContext(), R.drawable.dog))
.override(Target.SIZE_ORIGINAL)
.theme(fragment.requireActivity().getTheme()));
}

@Test
public void loadResourceIdUri_withDarkModeFragment_darkModeTheme_usesDarkModeDrawable() {
runFragmentTest(
darkModeActivity(),
R.raw.dog_dark,
fragment ->
Glide.with(fragment)
.load(newResourceIdUri(fragment.requireContext(), R.drawable.dog))
.override(Target.SIZE_ORIGINAL)
.theme(fragment.requireActivity().getTheme()));
}

@Test
public void load_withApplicationContext_darkTheme_usesDarkModeDrawable() {
runActivityTest(
Expand All @@ -129,6 +200,30 @@ public void load_withApplicationContext_darkTheme_usesDarkModeDrawable() {
.theme(input.getTheme()));
}

@Test
public void loadResourceNameUri_withApplicationContext_darkTheme_usesDarkModeDrawable() {
runActivityTest(
darkModeActivity(),
R.raw.dog_dark,
input ->
Glide.with(input.getApplicationContext())
.load(newResourceNameUri(input.getApplicationContext(), R.drawable.dog))
.override(Target.SIZE_ORIGINAL)
.theme(input.getTheme()));
}

@Test
public void loadResourceIdUri_withApplicationContext_darkTheme_usesDarkModeDrawable() {
runActivityTest(
darkModeActivity(),
R.raw.dog_dark,
input ->
Glide.with(input.getApplicationContext())
.load(newResourceIdUri(input.getApplicationContext(), R.drawable.dog))
.override(Target.SIZE_ORIGINAL)
.theme(input.getTheme()));
}

@Test
public void load_withApplicationContext_lightTheme_usesLightModeDrawable() {
runActivityTest(
Expand Down
15 changes: 11 additions & 4 deletions library/src/main/java/com/bumptech/glide/RegistryFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.bumptech.glide.load.model.MediaStoreFileLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.ResourceLoader;
import com.bumptech.glide.load.model.ResourceUriLoader;
import com.bumptech.glide.load.model.StreamEncoder;
import com.bumptech.glide.load.model.StringLoader;
import com.bumptech.glide.load.model.UnitModelLoader;
Expand Down Expand Up @@ -280,7 +281,12 @@ Uri.class, Bitmap.class, new ResourceBitmapDecoder(resourceDrawableDecoder, bitm
.append(int.class, InputStream.class, inputStreamFactory)
.append(Integer.class, InputStream.class, inputStreamFactory)
.append(int.class, AssetFileDescriptor.class, assetFileDescriptorFactory)
.append(Integer.class, AssetFileDescriptor.class, assetFileDescriptorFactory);
.append(Integer.class, AssetFileDescriptor.class, assetFileDescriptorFactory)
.append(Uri.class, InputStream.class, ResourceUriLoader.newStreamFactory(context))
.append(
Uri.class,
AssetFileDescriptor.class,
ResourceUriLoader.newAssetFileDescriptorFactory(context));
} else {
ResourceLoader.StreamFactory resourceLoaderStreamFactory =
new ResourceLoader.StreamFactory(resources);
Expand Down Expand Up @@ -325,15 +331,16 @@ Uri.class, Bitmap.class, new ResourceBitmapDecoder(resourceDrawableDecoder, bitm
new QMediaStoreUriLoader.FileDescriptorFactory(context));
}
registry
.append(Uri.class, InputStream.class, new UriLoader.StreamFactory(contentResolver))
.append(
Uri.class, InputStream.class, new UriLoader.StreamFactory(contentResolver, experiments))
.append(
Uri.class,
ParcelFileDescriptor.class,
new UriLoader.FileDescriptorFactory(contentResolver))
new UriLoader.FileDescriptorFactory(contentResolver, experiments))
.append(
Uri.class,
AssetFileDescriptor.class,
new UriLoader.AssetFileDescriptorFactory(contentResolver))
new UriLoader.AssetFileDescriptorFactory(contentResolver, experiments))
.append(Uri.class, InputStream.class, new UrlUriLoader.StreamFactory())
.append(URL.class, InputStream.class, new UrlLoader.StreamFactory())
.append(Uri.class, File.class, new MediaStoreFileLoader.Factory(context))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package com.bumptech.glide.load.model;

import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.load.Options;
import java.io.InputStream;
import java.util.List;

/**
* Converts Resource Uris to resource ids if the resource Uri points to a resource in this package.
*
* <p>This class really shouldn't need to exist. If you need to load resources, just pass
* in the integer resource id directly using {@link com.bumptech.glide.RequestManager#load(Integer)}
* instead. It'll be more correct in terms of caching and more efficient to load. The only reason
* we're supporting this case is for backwards compatibility.
*
* @param <DataT> The type of data produced, e.g. {@link InputStream} or
* {@link AssetFileDescriptor}.
*/
public final class ResourceUriLoader<DataT> implements ModelLoader<Uri, DataT> {
/**
* See the javadoc on {@link
* android.content.res.Resources#getIdentifier(java.lang.String, java.lang.String, java.lang.String)}.
*/
private static final int INVALID_RESOURCE_ID = 0;
private static final String TAG = "ResourceUriLoader";

private final Context context;
private final ModelLoader<Integer, DataT> delegate;

public static ModelLoaderFactory<Uri, InputStream> newStreamFactory(Context context) {
return new InputStreamFactory(context);
}

public static ModelLoaderFactory<Uri, AssetFileDescriptor> newAssetFileDescriptorFactory(
Context context) {
return new AssetFileDescriptorFactory(context);
}

ResourceUriLoader(Context context, ModelLoader<Integer, DataT> delegate) {
this.context = context.getApplicationContext();
this.delegate = delegate;
}

@Nullable
@Override
public LoadData<DataT> buildLoadData(@NonNull Uri uri, int width, int height, @NonNull Options options) {
List<String> pathSegments = uri.getPathSegments();
// android.resource//<package_name>/<resource_id>
if (pathSegments.size() == 1) {
return parseResourceIdUri(uri, width, height, options);
}
// android.resource//<package_name>/<drawable>/<resource_name>
if (pathSegments.size() == 2) {
return parseResourceNameUri(uri, width, height, options);
}
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Failed to parse resource uri: " + uri);
}
return null;
}

@Nullable
private LoadData<DataT> parseResourceNameUri(
@NonNull Uri uri, int width, int height, @NonNull Options options) {
List<String> pathSegments = uri.getPathSegments();
String resourceType = pathSegments.get(0);
String resourceName = pathSegments.get(1);

// Yes it's bad, but the caller has chosen to give us a resource uri...
@SuppressLint("DiscouragedApi") int identifier =
context.getResources().getIdentifier(resourceName, resourceType, context.getPackageName());
if (identifier == INVALID_RESOURCE_ID) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Failed to find resource id for: " + uri);
}
return null;
}

return delegate.buildLoadData(identifier, width, height, options);
}

@Nullable
private LoadData<DataT> parseResourceIdUri(
@NonNull Uri uri, int width, int height, @NonNull Options options) {
try {
int resourceId = Integer.parseInt(uri.getPathSegments().get(0));
if (resourceId == INVALID_RESOURCE_ID) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Failed to parse a valid non-0 resource id from: " + uri);
}
return null;
}
return delegate.buildLoadData(resourceId, width, height, options);
} catch (NumberFormatException e) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Failed to parse resource id from: " + uri ,e);
}
}
return null;
}

@Override
public boolean handles(@NonNull Uri uri) {
return ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())
&& context.getPackageName().equals(uri.getAuthority());
}

private static final class InputStreamFactory implements ModelLoaderFactory<Uri, InputStream> {

private final Context context;

InputStreamFactory(Context context) {
this.context = context;
}

@NonNull
@Override
public ModelLoader<Uri, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
return new ResourceUriLoader<>(context, multiFactory.build(Integer.class, InputStream.class));
}

@Override public void teardown() {}
}

private static final class AssetFileDescriptorFactory implements ModelLoaderFactory<Uri, AssetFileDescriptor> {

private final Context context;

AssetFileDescriptorFactory(Context context) {
this.context = context;
}

@NonNull
@Override
public ModelLoader<Uri, AssetFileDescriptor> build(
@NonNull MultiModelLoaderFactory multiFactory) {
return new ResourceUriLoader<>(
context, multiFactory.build(Integer.class, AssetFileDescriptor.class));
}

@Override public void teardown() {}
}
}
Loading

0 comments on commit a912e0f

Please sign in to comment.