-
Notifications
You must be signed in to change notification settings - Fork 6.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix vector/shape drawables not loading with DirectResourceLoader. #4999
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
import android.content.Context; | ||
import android.content.res.Resources; | ||
import android.graphics.Bitmap; | ||
import android.graphics.Canvas; | ||
import android.graphics.drawable.BitmapDrawable; | ||
import android.graphics.drawable.Drawable; | ||
import android.net.Uri; | ||
|
@@ -51,6 +52,45 @@ public void before() { | |
assumeTrue(VERSION.SDK_INT >= VERSION_CODES.Q); | ||
} | ||
|
||
@Test | ||
public void load_withDarkModeActivity_vectorDrawable_usesDarkModeColor() { | ||
try (ActivityScenario<FragmentActivity> scenario = darkModeActivity()) { | ||
scenario.onActivity( | ||
activity -> { | ||
ViewGroup container = findContainer(activity); | ||
ImageView imageView = newFixedSizeImageView(activity); | ||
container.addView(imageView); | ||
|
||
Glide.with(activity) | ||
.load(R.drawable.vector_drawable) | ||
.override(Target.SIZE_ORIGINAL) | ||
.into(imageView); | ||
}); | ||
|
||
onIdle(); | ||
scenario.onActivity( | ||
activity -> { | ||
ViewGroup container = findContainer(activity); | ||
ImageView imageView = (ImageView) container.getChildAt(0); | ||
Bitmap result = drawToBitmap(imageView.getDrawable()); | ||
Bitmap expected = drawToBitmap(activity.getDrawable(R.drawable.vector_drawable_dark)); | ||
assertThat(result).sameAs(expected); | ||
}); | ||
} | ||
} | ||
Comment on lines
+55
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optimization Issue: The method 'load_withDarkModeActivity_vectorDrawable_usesDarkModeColor' performs a drawable to bitmap conversion inside the activity's onActivity method. This process is not optimized for performance. Converting a drawable to a bitmap is a CPU-intensive operation and doing it on the main thread can lead to frame drops, especially if the drawable is complex or the device is low-end.
|
||
|
||
private static Bitmap drawToBitmap(Drawable drawable) { | ||
int width = drawable.getIntrinsicWidth(); | ||
int height = drawable.getIntrinsicHeight(); | ||
|
||
Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); | ||
Canvas canvas = new Canvas(result); | ||
drawable.setBounds(0, 0, width, height); | ||
drawable.draw(canvas); | ||
canvas.setBitmap(null); | ||
return result; | ||
} | ||
Comment on lines
+55
to
+92
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Consider breaking down the test method load_withDarkModeActivity_vectorDrawable_usesDarkModeColor into smaller, more focused test cases. This can improve test readability and make it easier to identify specific points of failure.
Comment on lines
+82
to
+92
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Performance Issue: The method 'drawToBitmap' creates a new Bitmap and Canvas every time it's called. This can be highly inefficient and memory-intensive, especially for large numbers of drawables or high-resolution images. Reusing bitmaps or using a bitmap pool could significantly improve memory usage and performance.
|
||
|
||
@Test | ||
public void load_withDarkModeActivity_useDarkModeDrawable() { | ||
runActivityTest( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,10 +17,10 @@ | |
import android.graphics.drawable.Drawable; | ||
import android.net.Uri; | ||
import androidx.test.core.app.ApplicationProvider; | ||
import androidx.test.ext.junit.runners.AndroidJUnit4; | ||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners; | ||
import com.bumptech.glide.test.ResourceIds; | ||
import com.bumptech.glide.testutil.TearDownGlide; | ||
Comment on lines
20
to
22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Code Structure Issue: The import 'com.google.common.collect.ImmutableList' is added, which introduces a new dependency. Ensure this dependency is necessary and properly documented.
|
||
import com.google.common.collect.ImmutableList; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
|
@@ -31,19 +31,29 @@ | |
import org.junit.function.ThrowingRunnable; | ||
import org.junit.rules.TestName; | ||
import org.junit.runner.RunWith; | ||
import org.mockito.MockitoAnnotations; | ||
import org.junit.runners.Parameterized; | ||
import org.junit.runners.Parameterized.Parameter; | ||
import org.junit.runners.Parameterized.Parameters; | ||
|
||
@RunWith(AndroidJUnit4.class) | ||
@RunWith(Parameterized.class) | ||
public class NonBitmapDrawableResourcesTest { | ||
@Rule public final TestName testName = new TestName(); | ||
@Rule public final TearDownGlide tearDownGlide = new TearDownGlide(); | ||
|
||
@Parameter public boolean useDirectResourceLoader; | ||
|
||
@Parameters(name = "useDirectResourceLoader = {0}") | ||
public static ImmutableList<Boolean> parameters() { | ||
return ImmutableList.of(true, false); | ||
} | ||
Comment on lines
+43
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Ensure that the parameterized test setup with useDirectResourceLoader is thoroughly validated across different test scenarios to confirm that both true and false states behave as expected without unintended side effects.
|
||
|
||
private Context context; | ||
|
||
@Before | ||
public void setUp() { | ||
MockitoAnnotations.initMocks(this); | ||
context = ApplicationProvider.getApplicationContext(); | ||
|
||
Glide.init(context, new GlideBuilder().useDirectResourceLoader(useDirectResourceLoader)); | ||
} | ||
|
||
@Test | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,9 @@ | |
package="com.bumptech.glide.instrumentation"> | ||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> | ||
<application tools:ignore="MissingApplicationIcon" > | ||
<application | ||
tools:ignore="MissingApplicationIcon" | ||
android:theme="@style/AppTheme"> | ||
Comment on lines
+6
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Code Structure Issue: The 'android:theme' attribute is added to the 'application' tag, potentially overriding themes set in individual activities or fragments.
|
||
<activity | ||
android:name="com.bumptech.glide.test.GlideWithBeforeSuperOnCreateActivity" | ||
android:exported="false" /> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ | |
android:viewportHeight="24.0" | ||
android:viewportWidth="24.0"> | ||
<path | ||
android:fillColor="#f9b840" | ||
android:fillColor="?attr/colorControlNormal" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Ensure that using "?attr/colorControlNormal" for the fillColor in vector_drawable.xml aligns with the intended color theme across different app states and Android versions.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optimization Issue: The fillColor attribute of the vector drawable uses a hard-coded color value. This approach is not theme-friendly and can lead to issues with dark mode or when trying to change the app's theme dynamically.
|
||
android:pathData="M15.5,5.5c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 | ||
-2,2 0.9,2 2,2zM5,12c-2.8,0 -5,2.2 -5,5s2.2,5 5,5 5,-2.2 5,-5 -2.2, | ||
-5 -5,-5zM5,20.5c-1.9,0 -3.5,-1.6 -3.5,-3.5s1.6,-3.5 3.5,-3.5 3.5, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
android:width="64dp" | ||
android:height="64dp" | ||
android:viewportHeight="24.0" | ||
android:viewportWidth="24.0"> | ||
<path | ||
android:fillColor="@color/colorPrimaryDark" | ||
android:pathData="M15.5,5.5c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 | ||
-2,2 0.9,2 2,2zM5,12c-2.8,0 -5,2.2 -5,5s2.2,5 5,5 5,-2.2 5,-5 -2.2, | ||
-5 -5,-5zM5,20.5c-1.9,0 -3.5,-1.6 -3.5,-3.5s1.6,-3.5 3.5,-3.5 3.5, | ||
1.6 3.5,3.5 -1.6,3.5 -3.5,3.5zM10.8,10.5l2.4,-2.4 0.8,0.8c1.3,1.3 | ||
3,2.1 5.1,2.1L19.1,9c-1.5,0 -2.7,-0.6 -3.6,-1.5l-1.9,-1.9c-0.5,-0.4 | ||
-1,-0.6 -1.6,-0.6s-1.1,0.2 -1.4,0.6L7.8,8.4c-0.4,0.4 -0.6,0.9 -0.6, | ||
1.4 0,0.6 0.2,1.1 0.6,1.4L11,14v5h2v-6.2l-2.2,-2.3zM19,12c-2.8,0 -5, | ||
2.2 -5,5s2.2,5 5,5 5,-2.2 5,-5 -2.2,-5 -5,-5zM19,20.5c-1.9,0 -3.5,-1.6 | ||
-3.5,-3.5s1.6,-3.5 3.5,-3.5 3.5,1.6 3.5,3.5 -1.6,3.5 -3.5,3.5z" /> | ||
</vector> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
android:width="64dp" | ||
android:height="64dp" | ||
android:viewportHeight="24.0" | ||
android:viewportWidth="24.0"> | ||
<path | ||
android:fillColor="@color/colorPrimary" | ||
android:pathData="M15.5,5.5c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 | ||
-2,2 0.9,2 2,2zM5,12c-2.8,0 -5,2.2 -5,5s2.2,5 5,5 5,-2.2 5,-5 -2.2, | ||
-5 -5,-5zM5,20.5c-1.9,0 -3.5,-1.6 -3.5,-3.5s1.6,-3.5 3.5,-3.5 3.5, | ||
1.6 3.5,3.5 -1.6,3.5 -3.5,3.5zM10.8,10.5l2.4,-2.4 0.8,0.8c1.3,1.3 | ||
3,2.1 5.1,2.1L19.1,9c-1.5,0 -2.7,-0.6 -3.6,-1.5l-1.9,-1.9c-0.5,-0.4 | ||
-1,-0.6 -1.6,-0.6s-1.1,0.2 -1.4,0.6L7.8,8.4c-0.4,0.4 -0.6,0.9 -0.6, | ||
1.4 0,0.6 0.2,1.1 0.6,1.4L11,14v5h2v-6.2l-2.2,-2.3zM19,12c-2.8,0 -5, | ||
2.2 -5,5s2.2,5 5,5 5,-2.2 5,-5 -2.2,-5 -5,-5zM19,20.5c-1.9,0 -3.5,-1.6 | ||
-3.5,-3.5s1.6,-3.5 3.5,-3.5 3.5,1.6 3.5,3.5 -1.6,3.5 -3.5,3.5z" /> | ||
</vector> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
|
||
<resources> | ||
<color name="colorPrimary">#f9b840</color> | ||
<color name="colorPrimaryDark">#ffffff</color> | ||
</resources> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<resources> | ||
<style name="AppTheme" parent="Theme.AppCompat.DayNight.DarkActionBar"> | ||
<!-- Customize your theme here. --> | ||
<item name="colorPrimary">@color/colorPrimary</item> | ||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> | ||
</style> | ||
</resources> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -279,11 +279,15 @@ Uri.class, Bitmap.class, new ResourceBitmapDecoder(resourceDrawableDecoder, bitm | |
DirectResourceLoader.inputStreamFactory(context); | ||
ModelLoaderFactory<Integer, AssetFileDescriptor> assetFileDescriptorFactory = | ||
DirectResourceLoader.assetFileDescriptorFactory(context); | ||
ModelLoaderFactory<Integer, Drawable> drawableFactory = | ||
DirectResourceLoader.drawableFactory(context); | ||
registry | ||
.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(int.class, Drawable.class, drawableFactory) | ||
.append(Integer.class, Drawable.class, drawableFactory) | ||
Comment on lines
+282
to
+290
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Validate the integration of the new drawableFactory with the existing registry setup. Ensure that it does not introduce conflicts with other registered loaders, especially in scenarios involving resource and drawable loading.
|
||
.append(Uri.class, InputStream.class, ResourceUriLoader.newStreamFactory(context)) | ||
Comment on lines
279
to
291
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Scalability Issue: The addition of a new drawable factory directly in the RegistryFactory without considering a pluggable or extensible approach for handling various resource types might limit the scalability of the system. As new resource types or special handling cases are introduced, this class may become bloated and harder to maintain, violating the open/closed principle.
|
||
.append( | ||
Uri.class, | ||
|
@@ -292,25 +296,26 @@ Uri.class, Bitmap.class, new ResourceBitmapDecoder(resourceDrawableDecoder, bitm | |
} else { | ||
ResourceLoader.StreamFactory resourceLoaderStreamFactory = | ||
new ResourceLoader.StreamFactory(resources); | ||
ResourceLoader.UriFactory resourceLoaderUriFactory = new ResourceLoader.UriFactory(resources); | ||
ResourceLoader.FileDescriptorFactory resourceLoaderFileDescriptorFactory = | ||
new ResourceLoader.FileDescriptorFactory(resources); | ||
ResourceLoader.AssetFileDescriptorFactory resourceLoaderAssetFileDescriptorFactory = | ||
new ResourceLoader.AssetFileDescriptorFactory(resources); | ||
|
||
registry | ||
.append(int.class, InputStream.class, resourceLoaderStreamFactory) | ||
.append(int.class, ParcelFileDescriptor.class, resourceLoaderFileDescriptorFactory) | ||
.append(Integer.class, InputStream.class, resourceLoaderStreamFactory) | ||
.append(int.class, ParcelFileDescriptor.class, resourceLoaderFileDescriptorFactory) | ||
.append(Integer.class, ParcelFileDescriptor.class, resourceLoaderFileDescriptorFactory) | ||
.append(Integer.class, Uri.class, resourceLoaderUriFactory) | ||
.append(int.class, AssetFileDescriptor.class, resourceLoaderAssetFileDescriptorFactory) | ||
.append( | ||
Integer.class, AssetFileDescriptor.class, resourceLoaderAssetFileDescriptorFactory) | ||
.append(int.class, Uri.class, resourceLoaderUriFactory); | ||
Integer.class, AssetFileDescriptor.class, resourceLoaderAssetFileDescriptorFactory); | ||
} | ||
|
||
// Handles resources from other applications or converting Drawable resource types to Bitmaps. | ||
ResourceLoader.UriFactory resourceLoaderUriFactory = new ResourceLoader.UriFactory(resources); | ||
registry | ||
.append(Integer.class, Uri.class, resourceLoaderUriFactory) | ||
.append(int.class, Uri.class, resourceLoaderUriFactory) | ||
.append(String.class, InputStream.class, new DataUrlLoader.StreamFactory<String>()) | ||
.append(Uri.class, InputStream.class, new DataUrlLoader.StreamFactory<Uri>()) | ||
.append(String.class, InputStream.class, new StringLoader.StreamFactory()) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
import android.content.res.AssetFileDescriptor; | ||
import android.content.res.Resources; | ||
import android.content.res.Resources.Theme; | ||
import android.graphics.drawable.Drawable; | ||
import android.os.Build; | ||
import android.os.Build.VERSION_CODES; | ||
import androidx.annotation.NonNull; | ||
|
@@ -12,9 +13,9 @@ | |
import com.bumptech.glide.load.DataSource; | ||
import com.bumptech.glide.load.Options; | ||
import com.bumptech.glide.load.data.DataFetcher; | ||
import com.bumptech.glide.load.resource.drawable.DrawableDecoderCompat; | ||
import com.bumptech.glide.load.resource.drawable.ResourceDrawableDecoder; | ||
import com.bumptech.glide.signature.ObjectKey; | ||
import java.io.Closeable; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
|
||
|
@@ -26,8 +27,7 @@ | |
* @param <DataT> The type of data this {@code ModelLoader} will produce (e.g. {@link InputStream}, | ||
* {@link AssetFileDescriptor} etc). | ||
*/ | ||
public final class DirectResourceLoader<DataT extends Closeable> | ||
implements ModelLoader<Integer, DataT> { | ||
public final class DirectResourceLoader<DataT> implements ModelLoader<Integer, DataT> { | ||
|
||
private final Context context; | ||
private final ResourceOpener<DataT> resourceOpener; | ||
|
@@ -41,6 +41,10 @@ public static ModelLoaderFactory<Integer, AssetFileDescriptor> assetFileDescript | |
return new AssetFileDescriptorFactory(context); | ||
} | ||
|
||
public static ModelLoaderFactory<Integer, Drawable> drawableFactory(Context context) { | ||
return new DrawableFactory(context); | ||
} | ||
|
||
DirectResourceLoader(Context context, ResourceOpener<DataT> resourceOpener) { | ||
this.context = context.getApplicationContext(); | ||
this.resourceOpener = resourceOpener; | ||
|
@@ -71,6 +75,8 @@ public boolean handles(@NonNull Integer integer) { | |
private interface ResourceOpener<DataT> { | ||
DataT open(Resources resources, int resourceId); | ||
|
||
void close(DataT data) throws IOException; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Security Issue: The method 'void close(DataT data) throws IOException' in 'DirectResourceLoader.java' lacks input validation, which could lead to resource leaks or denial of service if the method is called with invalid or unexpected data. This is particularly relevant because the method is designed to close resources, and improper handling could leave resources open longer than intended or cause exceptions that disrupt normal operation.
|
||
|
||
Class<DataT> getDataClass(); | ||
} | ||
|
||
|
@@ -89,6 +95,11 @@ public AssetFileDescriptor open(Resources resources, int resourceId) { | |
return resources.openRawResourceFd(resourceId); | ||
} | ||
|
||
@Override | ||
public void close(AssetFileDescriptor data) throws IOException { | ||
data.close(); | ||
} | ||
|
||
@Override | ||
public Class<AssetFileDescriptor> getDataClass() { | ||
return AssetFileDescriptor.class; | ||
|
@@ -125,6 +136,11 @@ public InputStream open(Resources resources, int resourceId) { | |
return resources.openRawResource(resourceId); | ||
} | ||
|
||
@Override | ||
public void close(InputStream data) throws IOException { | ||
data.close(); | ||
} | ||
|
||
@Override | ||
public Class<InputStream> getDataClass() { | ||
return InputStream.class; | ||
|
@@ -134,8 +150,45 @@ public Class<InputStream> getDataClass() { | |
public void teardown() {} | ||
} | ||
|
||
private static final class ResourceDataFetcher<DataT extends Closeable> | ||
implements DataFetcher<DataT> { | ||
/** | ||
* Handles vectors, shapes and other resources that cannot be opened with | ||
* Resources.openRawResource. Overlaps in functionality with {@link ResourceDrawableDecoder} and | ||
* {@link com.bumptech.glide.load.resource.bitmap.ResourceBitmapDecoder} but it's more efficient | ||
* for simple resource loads within a single application. | ||
*/ | ||
private static final class DrawableFactory | ||
implements ModelLoaderFactory<Integer, Drawable>, ResourceOpener<Drawable> { | ||
|
||
private final Context context; | ||
|
||
DrawableFactory(Context context) { | ||
this.context = context; | ||
} | ||
|
||
@Override | ||
public Drawable open(Resources resources, int resourceId) { | ||
return DrawableDecoderCompat.getDrawable(context, resourceId, resources.newTheme()); | ||
} | ||
|
||
@Override | ||
public void close(Drawable data) throws IOException {} | ||
|
||
@Override | ||
public Class<Drawable> getDataClass() { | ||
return Drawable.class; | ||
} | ||
|
||
@NonNull | ||
@Override | ||
public ModelLoader<Integer, Drawable> build(@NonNull MultiModelLoaderFactory multiFactory) { | ||
return new DirectResourceLoader<>(context, this); | ||
} | ||
|
||
@Override | ||
public void teardown() {} | ||
Comment on lines
+159
to
+188
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Review the necessity of the empty close method in DrawableFactory. If Drawable resources do not require specific cleanup, document the rationale clearly to avoid confusion.
Comment on lines
150
to
+188
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Consider enhancing the DrawableFactory with additional logging or error handling, especially in the open method, to handle potential issues when loading drawables.
|
||
} | ||
|
||
private static final class ResourceDataFetcher<DataT> implements DataFetcher<DataT> { | ||
|
||
private final Resources resources; | ||
private final ResourceOpener<DataT> resourceOpener; | ||
|
@@ -164,7 +217,7 @@ public void cleanup() { | |
DataT local = data; | ||
if (local != null) { | ||
try { | ||
local.close(); | ||
resourceOpener.close(local); | ||
} catch (IOException e) { | ||
// Ignored. | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Structure Issue: The import 'android.graphics.Canvas' is added but not used in any of the modified code. It's good practice to remove unused imports to keep the codebase clean.
Fix: Remove the unused import to clean up the code.
Code Suggestion: