diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngBufferCacheDecoder.java b/app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngBufferCacheDecoder.java index 90ad638250..7b72f838e9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngBufferCacheDecoder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngBufferCacheDecoder.java @@ -20,7 +20,11 @@ public class ApngBufferCacheDecoder implements ResourceDecoder ANIMATE = Option.disk(KEY, true, (keyBytes, value, messageDigest) -> { + messageDigest.update(keyBytes); + messageDigest.update(Conversions.intToByteArray(value ? 1 : 0)); + }); + + private ApngOptions() {} +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngStreamCacheDecoder.java b/app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngStreamCacheDecoder.java index 47c6036926..fdce779f78 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngStreamCacheDecoder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngStreamCacheDecoder.java @@ -26,7 +26,11 @@ public class ApngStreamCacheDecoder implements ResourceDecoder stickers; + private final boolean allowApngAnimation; private int stickerSize; - StickerKeyboardPageAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener) { - this.glideRequests = glideRequests; - this.eventListener = eventListener; - this.stickers = new ArrayList<>(); + StickerKeyboardPageAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener, boolean allowApngAnimation) { + this.glideRequests = glideRequests; + this.eventListener = eventListener; + this.allowApngAnimation = allowApngAnimation; + this.stickers = new ArrayList<>(); setHasStableIds(true); } @@ -52,7 +55,7 @@ final class StickerKeyboardPageAdapter extends RecyclerView.Adapter 0 ? 1 : 0; } - presenter.present(this, pagerAdapter, new IconProvider(context, result.getPacks()), null, this, null, startingIndex); + presenter.present(this, pagerAdapter, new IconProvider(context, result.getPacks(), DeviceProperties.shouldAllowApngStickerAnimation(context)), null, this, null, startingIndex); if (isSoloProvider && result.getPacks().isEmpty()) { context.startActivity(StickerManagementActivity.getIntent(context)); @@ -238,10 +240,12 @@ public final class StickerKeyboardProvider implements MediaKeyboardProvider, private final Context context; private final List packs; + private final boolean allowApngAnimation; - private IconProvider(@NonNull Context context, List packs) { - this.context = context; - this.packs = packs; + private IconProvider(@NonNull Context context, List packs, boolean allowApngAnimation) { + this.context = context; + this.packs = packs; + this.allowApngAnimation = allowApngAnimation; } @Override @@ -253,6 +257,7 @@ public final class StickerKeyboardProvider implements MediaKeyboardProvider, Uri uri = packs.get(index - 1).getCover().getUri(); glideRequests.load(new DecryptableStreamUriLoader.DecryptableUri(uri)) + .set(ApngOptions.ANIMATE, allowApngAnimation) .into(imageView); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivity.java b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivity.java index e631ebcb52..3694ec8984 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivity.java @@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.PassphraseRequiredActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.sharing.ShareActivity; +import org.thoughtcrime.securesms.util.DeviceProperties; import org.thoughtcrime.securesms.util.DynamicTheme; /** @@ -93,7 +94,7 @@ public final class StickerManagementActivity extends PassphraseRequiredActivity private void initView() { this.list = findViewById(R.id.sticker_management_list); - this.adapter = new StickerManagementAdapter(GlideApp.with(this), this); + this.adapter = new StickerManagementAdapter(GlideApp.with(this), this, DeviceProperties.shouldAllowApngStickerAnimation(this)); list.setLayoutManager(new LinearLayoutManager(this)); list.setAdapter(adapter); diff --git a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementAdapter.java index dcbce0be70..6773910a80 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementAdapter.java @@ -21,6 +21,7 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.emoji.EmojiTextView; import org.thoughtcrime.securesms.database.model.StickerPackRecord; +import org.thoughtcrime.securesms.glide.cache.ApngOptions; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.util.adapter.SectionedRecyclerViewAdapter; @@ -38,6 +39,7 @@ final class StickerManagementAdapter extends SectionedRecyclerViewAdapter sections = new ArrayList(3) {{ StickerSection yourStickers = new StickerSection(TAG_YOUR_STICKERS, @@ -55,9 +57,10 @@ final class StickerManagementAdapter extends SectionedRecyclerViewAdapter list; + private final boolean allowApngAnimation; - public StickerPackPreviewAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener) { - this.glideRequests = glideRequests; - this.eventListener = eventListener; - this.list = new ArrayList<>(); + public StickerPackPreviewAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener, boolean allowApngAnimation) { + this.glideRequests = glideRequests; + this.eventListener = eventListener; + this.allowApngAnimation = allowApngAnimation; + this.list = new ArrayList<>(); } @Override @@ -37,7 +40,7 @@ public final class StickerPackPreviewAdapter extends RecyclerView.Adapter { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/DeviceProperties.java b/app/src/main/java/org/thoughtcrime/securesms/util/DeviceProperties.java new file mode 100644 index 0000000000..7c34028bd1 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/DeviceProperties.java @@ -0,0 +1,30 @@ +package org.thoughtcrime.securesms.util; + +import android.app.ActivityManager; +import android.content.Context; + +import androidx.annotation.NonNull; + +/** + * Easy access to various properties of the device, typically to make performance-related decisions. + */ +public final class DeviceProperties { + + /** + * Whether or not we believe the device has the performance capabilities to efficiently render + * large numbers of APNGs simultaneously. + */ + public static boolean shouldAllowApngStickerAnimation(@NonNull Context context) { + return !isLowMemoryDevice(context) && getMemoryClass(context) >= FeatureFlags.animatedStickerMinimumMemory(); + } + + public static boolean isLowMemoryDevice(@NonNull Context context) { + ActivityManager activityManager = ServiceUtil.getActivityManager(context); + return activityManager.isLowRamDevice(); + } + + public static int getMemoryClass(@NonNull Context context) { + ActivityManager activityManager = ServiceUtil.getActivityManager(context); + return activityManager.getMemoryClass(); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index a96d1c6fa6..f2e866c2e7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -72,6 +72,7 @@ public final class FeatureFlags { private static final String DEFAULT_MAX_BACKOFF = "android.defaultMaxBackoff"; private static final String OKHTTP_AUTOMATIC_RETRY = "android.okhttpAutomaticRetry"; private static final String SHARE_SELECTION_LIMIT = "android.share.limit"; + private static final String ANIMATED_STICKER_MIN_MEMORY = "android.animatedStickerMinMemory"; /** * We will only store remote values for flags in this set. If you want a flag to be controllable @@ -100,7 +101,8 @@ public final class FeatureFlags { AUTOMATIC_SESSION_INTERVAL, DEFAULT_MAX_BACKOFF, OKHTTP_AUTOMATIC_RETRY, - SHARE_SELECTION_LIMIT + SHARE_SELECTION_LIMIT, + ANIMATED_STICKER_MIN_MEMORY ); @VisibleForTesting @@ -139,7 +141,8 @@ public final class FeatureFlags { AUTOMATIC_SESSION_INTERVAL, DEFAULT_MAX_BACKOFF, OKHTTP_AUTOMATIC_RETRY, - SHARE_SELECTION_LIMIT + SHARE_SELECTION_LIMIT, + ANIMATED_STICKER_MIN_MEMORY ); /** @@ -324,6 +327,11 @@ public final class FeatureFlags { return getBoolean(OKHTTP_AUTOMATIC_RETRY, false); } + /** The minimum amount of memory required for rendering animated stickers in the keyboard and such */ + public static int animatedStickerMinimumMemory() { + return getInteger(ANIMATED_STICKER_MIN_MEMORY, 193); + } + /** Only for rendering debug info. */ public static synchronized @NonNull Map getMemoryValues() { return new TreeMap<>(REMOTE_VALUES);