From 76f37d91fd3ebc0c3e5569d4dba4c3209763184f Mon Sep 17 00:00:00 2001 From: Fares Alhassen Date: Mon, 27 Jul 2026 15:07:21 -0700 Subject: [PATCH] Reject non-image URIs in UriBitmapImageDecoderResourceDecoder. When Glide__enable_image_decoder_bitmaps is enabled, Glide registers UriBitmapImageDecoderResourceDecoder to handle URIs. However, this decoder was handling all content/file URIs including videos, and trying to decode them via ImageDecoder, which does not support videos and causes crashes. This CL fixes it by using a positive MIME type check in `handles()`. It now only handles URIs if they are confirmed images (excluding GIFs). For `android.resource://` URIs, it still allows null MIME types to preserve resource image loading performance, as they can return null in tests/Robolectric. Additionally, this CL fixes Javadoc compilation warnings in `GlideBuilder.java` and `GlideFutures.java` (using fully qualified names or code font for non-imported or private classes) to resolve build failures during export to GitHub. PiperOrigin-RevId: 954851141 --- .../integration/concurrent/GlideFutures.java | 2 +- .../java/com/bumptech/glide/GlideBuilder.java | 17 +++++----- .../UriBitmapImageDecoderResourceDecoder.java | 33 +++++++++++++++---- ...BitmapImageDecoderResourceDecoderTest.java | 30 +++++++++++++++++ 4 files changed, 67 insertions(+), 15 deletions(-) diff --git a/integration/concurrent/src/main/java/com/bumptech/glide/integration/concurrent/GlideFutures.java b/integration/concurrent/src/main/java/com/bumptech/glide/integration/concurrent/GlideFutures.java index 77be63ca91..c244dbbd48 100644 --- a/integration/concurrent/src/main/java/com/bumptech/glide/integration/concurrent/GlideFutures.java +++ b/integration/concurrent/src/main/java/com/bumptech/glide/integration/concurrent/GlideFutures.java @@ -62,7 +62,7 @@ public interface ResourceConsumer { * Glide's pool. In particular, if the request is cancelled after the resource is loaded by Glide, * but before {@code action} is run on {@code executor}, the resource will not be returned. We * have the unfortunate choice between unsafely returning resources to the pool immediately when - * cancel is called while they may still be in use via {@link + * cancel is called while they may still be in use via {@code * com.google.common.util.concurrent.ClosingFuture} or occasionally failing to return resources to * the pool. Because failing to return resources to the pool is inefficient, but safe, that's the * route we've chosen. A more sophisticated implementation may allow us to avoid the resource diff --git a/library/src/main/java/com/bumptech/glide/GlideBuilder.java b/library/src/main/java/com/bumptech/glide/GlideBuilder.java index f6185db447..2fa362f6e9 100644 --- a/library/src/main/java/com/bumptech/glide/GlideBuilder.java +++ b/library/src/main/java/com/bumptech/glide/GlideBuilder.java @@ -489,7 +489,7 @@ public GlideBuilder setImageDecoderEnabledForBitmaps(boolean isEnabled) { /** * Set to {@code true} to make Glide use {@link android.graphics.ImageDecoder} when decoding - * {@link Bitmap}s from local {@link Uri}s on Android Q and higher. + * {@link Bitmap}s from local {@link android.net.Uri}s on Android Q and higher. * *

This functionality is also guarded by {@link #setImageDecoderEnabledForBitmaps(boolean)} and * will only be active if that flag is also enabled. @@ -507,7 +507,8 @@ public GlideBuilder setUriImageDecoderEnabled(boolean isEnabled) { /** * Set to {@code true} to make Glide use a heap buffer instead of a direct buffer when decoding - * {@link Bitmap}s from an {@link InputStream} using {@link android.graphics.ImageDecoder}. + * {@link Bitmap}s from an {@link java.io.InputStream} using {@link + * android.graphics.ImageDecoder}. * *

This flag is experimental and may be removed without deprecation in a future version. */ @@ -518,8 +519,8 @@ public GlideBuilder setUseHeapBufferForImageDecoderWithInputStream(boolean isEna /** * Set to {@code true} to make Glide pool intermediate reading buffers and allocate precisely one - * tailored {@link ByteBuffer} using {@link ArrayPool} when decoding from an {@link InputStream} - * via {@link android.graphics.ImageDecoder}. + * tailored {@link java.nio.ByteBuffer} using {@link ArrayPool} when decoding from an {@link + * java.io.InputStream} via {@link android.graphics.ImageDecoder}. * *

This flag is experimental and may be removed without deprecation in a future version. */ @@ -530,8 +531,8 @@ public GlideBuilder setUseArrayPoolForImageDecoderByteBufferAllocation(boolean i } /** - * Set to {@code true} to enable direct {@link ByteBuffer} decoding instead of wrapping buffers in - * an {@link java.io.InputStream}. Disabled by default. + * Set to {@code true} to enable direct {@link java.nio.ByteBuffer} decoding instead of wrapping + * buffers in an {@link java.io.InputStream}. Disabled by default. * *

This flag is experimental and may be removed without deprecation in a future version. */ @@ -541,8 +542,8 @@ public GlideBuilder setEnableDirectByteBufferDecoding(boolean isEnabled) { } /** - * Override the OS thread priority of threads created in {@link - * com.bumptech.glide.load.engine.executor.GlideExecutor#DefaultThreadFactory} with {@link + * Override the OS thread priority of threads created in {@code + * com.bumptech.glide.load.engine.executor.GlideExecutor.DefaultThreadFactory} with {@link * com.bumptech.glide.load.engine.DecodeJob#GLIDE_THREAD_PRIORITY_OVERRIDE} Glide Option. * *

This is an experimental API that may be removed in the future. diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/UriBitmapImageDecoderResourceDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/UriBitmapImageDecoderResourceDecoder.java index ff33171452..ed6ca48047 100644 --- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/UriBitmapImageDecoderResourceDecoder.java +++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/UriBitmapImageDecoderResourceDecoder.java @@ -8,12 +8,15 @@ import android.net.Uri; import android.os.Build; import android.util.Log; +import android.webkit.MimeTypeMap; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.bumptech.glide.load.Options; import com.bumptech.glide.load.ResourceDecoder; import com.bumptech.glide.load.engine.Resource; import java.io.IOException; +import java.util.Locale; /** Decodes {@link Bitmap}s from {@link Uri}s using {@link ImageDecoder}. */ @RequiresApi(Build.VERSION_CODES.P) @@ -36,14 +39,32 @@ public boolean handles(@NonNull Uri uri, @NonNull Options options) throws IOExce if (!isSupportedScheme) { return false; } + String mimeType = getMimeType(uri); + if (mimeType == null) { + // ContentResolver.getType() can return null for resources in tests (Robolectric) or for some + // raw resources. We want to be lenient and handle them as they are internal to the app, + // but we reject null MIME types for content/file URIs to be safe. + return ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme); + } + return mimeType.startsWith("image/") && !mimeType.equals("image/gif"); + } + + @Nullable + private String getMimeType(@NonNull Uri uri) { String mimeType = context.getContentResolver().getType(uri); - // Skip GIFs to avoid decoding them as static Bitmaps. Glide otherwise prefers this direct path - // over the indirect animation path when a Drawable is requested, which prevents GIFs from - // animating. - if (mimeType != null && mimeType.equals("image/gif")) { - return false; + if (mimeType == null && ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { + String lastSegment = uri.getLastPathSegment(); + if (lastSegment != null) { + int lastDot = lastSegment.lastIndexOf('.'); + if (lastDot != -1) { + String extension = lastSegment.substring(lastDot + 1); + mimeType = + MimeTypeMap.getSingleton() + .getMimeTypeFromExtension(extension.toLowerCase(Locale.ROOT)); + } + } } - return true; + return mimeType; } @Override diff --git a/library/src/test/java/com/bumptech/glide/load/resource/bitmap/UriBitmapImageDecoderResourceDecoderTest.java b/library/src/test/java/com/bumptech/glide/load/resource/bitmap/UriBitmapImageDecoderResourceDecoderTest.java index 5e0dbe886f..2fc377df34 100644 --- a/library/src/test/java/com/bumptech/glide/load/resource/bitmap/UriBitmapImageDecoderResourceDecoderTest.java +++ b/library/src/test/java/com/bumptech/glide/load/resource/bitmap/UriBitmapImageDecoderResourceDecoderTest.java @@ -104,4 +104,34 @@ public void decode_solidColor_returnsExactColor() throws IOException { assertThat(decoded.getPixel(0, 0)).isEqualTo(Color.RED); } + + @Test + public void handles_returnsFalseForVideoUri() throws IOException { + ContentValues values = new ContentValues(); + values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4"); + Uri uri = + context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values); + assertThat(decoder.handles(uri, options)).isFalse(); + } + + @Test + public void handles_returnsFalseForVideoFileUri() throws IOException { + Uri uri = Uri.parse("file:///path/to/video.mp4"); + assertThat(decoder.handles(uri, options)).isFalse(); + } + + @Test + public void handles_returnsFalseForTextUri() throws IOException { + ContentValues values = new ContentValues(); + values.put(MediaStore.Files.FileColumns.MIME_TYPE, "text/plain"); + Uri uri = + context.getContentResolver().insert(MediaStore.Files.getContentUri("external"), values); + assertThat(decoder.handles(uri, options)).isFalse(); + } + + @Test + public void handles_returnsFalseForUnknownExtensionFileUri() throws IOException { + Uri uri = Uri.parse("file:///path/to/file.unknown"); + assertThat(decoder.handles(uri, options)).isFalse(); + } }