Skip to content

Commit

Permalink
Merge pull request #4975 from sjudd:parse_package_local_resource_uris
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 495916229
  • Loading branch information
glide-copybara-robot committed Dec 16, 2022
2 parents 5816903 + a912e0f commit 2aed37c
Show file tree
Hide file tree
Showing 4 changed files with 327 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
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.net.Uri;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
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,48 @@ 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 +162,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 +198,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,156 @@
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 2aed37c

Please sign in to comment.