From 1b391c42f22e967c070a6a105b1e37730ab6ea6e Mon Sep 17 00:00:00 2001 From: Sam Judd Date: Wed, 14 Aug 2019 15:21:24 -0700 Subject: [PATCH] Include day/night mode in resource id cache keys. --- .../com/bumptech/glide/RequestBuilder.java | 18 ++-- .../signature/AndroidResourceSignature.java | 49 ++++++++++ .../AndroidResourceSignatureTest.java | 94 +++++++++++++++++++ 3 files changed, 152 insertions(+), 9 deletions(-) create mode 100644 library/src/main/java/com/bumptech/glide/signature/AndroidResourceSignature.java create mode 100644 library/test/src/test/java/com/bumptech/glide/signature/AndroidResourceSignatureTest.java diff --git a/library/src/main/java/com/bumptech/glide/RequestBuilder.java b/library/src/main/java/com/bumptech/glide/RequestBuilder.java index f97a96b867..b4701d1936 100644 --- a/library/src/main/java/com/bumptech/glide/RequestBuilder.java +++ b/library/src/main/java/com/bumptech/glide/RequestBuilder.java @@ -30,7 +30,7 @@ import com.bumptech.glide.request.target.PreloadTarget; import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.target.ViewTarget; -import com.bumptech.glide.signature.ApplicationVersionSignature; +import com.bumptech.glide.signature.AndroidResourceSignature; import com.bumptech.glide.util.Executors; import com.bumptech.glide.util.Preconditions; import com.bumptech.glide.util.Synthetic; @@ -495,12 +495,12 @@ public RequestBuilder load(@Nullable File file) { * load the image represented by the given {@link Integer} resource id. Defaults to {@link * com.bumptech.glide.load.model.ResourceLoader} to load resource id models. * - *

By default this method adds a version code based signature to the cache key used to cache - * this resource in Glide. This signature is sufficient to guarantee that end users will see the - * most up to date versions of your Drawables, but during development if you do not increment your - * version code before each install and you replace a Drawable with different data without - * changing the Drawable name, you may see inconsistent cached data. To get around this, consider - * using {@link com.bumptech.glide.load.engine.DiskCacheStrategy#NONE} via {@link + *

By default this method adds a version code and night mode based signature to the cache key + * used to cache this resource in Glide. This signature is sufficient to guarantee that end users + * will see the most up to date versions of your Drawables, but during development if you do not + * increment your version code before each install and you replace a Drawable with different data + * without changing the Drawable name, you may see inconsistent cached data. To get around this, + * consider using {@link com.bumptech.glide.load.engine.DiskCacheStrategy#NONE} via {@link * RequestOptions#diskCacheStrategy(com.bumptech.glide.load.engine.DiskCacheStrategy)} during * development, and re-enabling the default {@link * com.bumptech.glide.load.engine.DiskCacheStrategy#RESOURCE} for release builds. @@ -519,13 +519,13 @@ public RequestBuilder load(@Nullable File file) { * caution for non-{@link Bitmap} {@link Drawable}s. * * @see #load(Integer) - * @see com.bumptech.glide.signature.ApplicationVersionSignature + * @see com.bumptech.glide.signature.AndroidResourceSignature */ @NonNull @CheckResult @Override public RequestBuilder load(@RawRes @DrawableRes @Nullable Integer resourceId) { - return loadGeneric(resourceId).apply(signatureOf(ApplicationVersionSignature.obtain(context))); + return loadGeneric(resourceId).apply(signatureOf(AndroidResourceSignature.obtain(context))); } /** diff --git a/library/src/main/java/com/bumptech/glide/signature/AndroidResourceSignature.java b/library/src/main/java/com/bumptech/glide/signature/AndroidResourceSignature.java new file mode 100644 index 0000000000..98696ba0a9 --- /dev/null +++ b/library/src/main/java/com/bumptech/glide/signature/AndroidResourceSignature.java @@ -0,0 +1,49 @@ +package com.bumptech.glide.signature; + +import android.content.Context; +import android.content.res.Configuration; +import androidx.annotation.NonNull; +import com.bumptech.glide.load.Key; +import com.bumptech.glide.util.Util; +import java.nio.ByteBuffer; +import java.security.MessageDigest; + +public final class AndroidResourceSignature implements Key { + + private final int nightMode; + private final Key applicationVersion; + + @NonNull + public static Key obtain(@NonNull Context context) { + Key signature = ApplicationVersionSignature.obtain(context); + int nightMode = + context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + return new AndroidResourceSignature(nightMode, signature); + } + + private AndroidResourceSignature(int nightMode, Key applicationVersion) { + this.nightMode = nightMode; + this.applicationVersion = applicationVersion; + } + + @Override + public boolean equals(Object o) { + if (o instanceof AndroidResourceSignature) { + AndroidResourceSignature that = (AndroidResourceSignature) o; + return nightMode == that.nightMode && applicationVersion.equals(that.applicationVersion); + } + return false; + } + + @Override + public int hashCode() { + return Util.hashCode(applicationVersion, nightMode); + } + + @Override + public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { + applicationVersion.updateDiskCacheKey(messageDigest); + byte[] nightModeData = ByteBuffer.allocate(4).putInt(nightMode).array(); + messageDigest.update(nightModeData); + } +} diff --git a/library/test/src/test/java/com/bumptech/glide/signature/AndroidResourceSignatureTest.java b/library/test/src/test/java/com/bumptech/glide/signature/AndroidResourceSignatureTest.java new file mode 100644 index 0000000000..36923b46cb --- /dev/null +++ b/library/test/src/test/java/com/bumptech/glide/signature/AndroidResourceSignatureTest.java @@ -0,0 +1,94 @@ +package com.bumptech.glide.signature; + +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; +import androidx.test.core.app.ApplicationProvider; +import com.bumptech.glide.load.Key; +import com.bumptech.glide.tests.KeyTester; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = 28) +public class AndroidResourceSignatureTest { + @Rule public final KeyTester keyTester = new KeyTester(); + private Context context; + + @Before + public void setUp() { + context = ApplicationProvider.getApplicationContext(); + } + + @Test + public void testCanGetKeyForSignature() { + Key key = AndroidResourceSignature.obtain(context); + assertNotNull(key); + } + + @Test + public void testKeyForSignatureIsTheSameAcrossCallsInTheSamePackage() { + keyTester + .addEquivalenceGroup( + AndroidResourceSignature.obtain(context), AndroidResourceSignature.obtain(context)) + .addEquivalenceGroup(new ObjectKey("test")) + .addRegressionTest( + ApplicationVersionSignature.obtain(context), + "5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9") + .test(); + } + + @Test + public void testKeyForSignatureDiffersByNightMode() { + RuntimeEnvironment.setQualifiers("notnight"); + keyTester + .addEquivalenceGroup( + AndroidResourceSignature.obtain(context), AndroidResourceSignature.obtain(context)) + .addRegressionTest( + AndroidResourceSignature.obtain(context), + "265d958bdae1bea56e45cc31f4db672c22893b66fef85617bbc78742bd912207"); + RuntimeEnvironment.setQualifiers("night"); + keyTester + .addEquivalenceGroup( + AndroidResourceSignature.obtain(context), AndroidResourceSignature.obtain(context)) + .addRegressionTest( + AndroidResourceSignature.obtain(context), + "96c9b8d5bb071ccd67df50cd9a0059640ebd02db78d08f07611ec145ce44a638"); + + keyTester.test(); + } + + @Test + public void testUnresolvablePackageInfo() throws NameNotFoundException { + Context context = mock(Context.class, Answers.RETURNS_DEEP_STUBS); + String packageName = "my.package"; + when(context.getPackageName()).thenReturn(packageName); + when(context.getPackageManager().getPackageInfo(packageName, 0)) + .thenThrow(new NameNotFoundException("test")); + + Key key = AndroidResourceSignature.obtain(context); + + assertNotNull(key); + } + + @Test + public void testMissingPackageInfo() throws NameNotFoundException { + Context context = mock(Context.class, Answers.RETURNS_DEEP_STUBS); + String packageName = "my.package"; + when(context.getPackageName()).thenReturn(packageName); + when(context.getPackageManager().getPackageInfo(packageName, 0)).thenReturn(null); + + Key key = AndroidResourceSignature.obtain(context); + + assertNotNull(key); + } +}