Skip to content

Commit

Permalink
Disable mass APNG animation on low-memory devices.
Browse files Browse the repository at this point in the history
  • Loading branch information
greyson-signal committed Jan 24, 2021
1 parent acbc17c commit 92b586c
Show file tree
Hide file tree
Showing 12 changed files with 125 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ public class ApngBufferCacheDecoder implements ResourceDecoder<ByteBuffer, APNGD

@Override
public boolean handles(@NonNull ByteBuffer source, @NonNull Options options) {
return APNGParser.isAPNG(new ByteBufferReader(source));
if (options.get(ApngOptions.ANIMATE)) {
return APNGParser.isAPNG(new ByteBufferReader(source));
} else {
return false;
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.thoughtcrime.securesms.glide.cache;

import com.bumptech.glide.load.Option;

import org.signal.core.util.Conversions;

/**
* Holds options that can be used to alter how APNGs are decoded in Glide.
*/
public final class ApngOptions {

private static final String KEY = "org.signal.skip_apng";

public static Option<Boolean> ANIMATE = Option.disk(KEY, true, (keyBytes, value, messageDigest) -> {
messageDigest.update(keyBytes);
messageDigest.update(Conversions.intToByteArray(value ? 1 : 0));
});

private ApngOptions() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ public ApngStreamCacheDecoder(ResourceDecoder<ByteBuffer, APNGDecoder> byteBuffe

@Override
public boolean handles(@NonNull InputStream source, @NonNull Options options) {
return APNGParser.isAPNG(new StreamReader(source));
if (options.get(ApngOptions.ANIMATE)) {
return APNGParser.isAPNG(new StreamReader(source));
} else {
return false;
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.glide.cache.ApngOptions;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.mms.GlideRequests;

Expand All @@ -29,13 +30,15 @@ final class StickerKeyboardPageAdapter extends RecyclerView.Adapter<StickerKeybo
private final GlideRequests glideRequests;
private final EventListener eventListener;
private final List<StickerRecord> 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);
}
Expand All @@ -52,7 +55,7 @@ public long getItemId(int position) {

@Override
public void onBindViewHolder(@NonNull StickerKeyboardPageViewHolder viewHolder, int i) {
viewHolder.bind(glideRequests, eventListener, stickers.get(i), stickerSize);
viewHolder.bind(glideRequests, eventListener, stickers.get(i), stickerSize, allowApngAnimation);
}

@Override
Expand Down Expand Up @@ -93,7 +96,8 @@ public StickerKeyboardPageViewHolder(@NonNull View itemView) {
public void bind(@NonNull GlideRequests glideRequests,
@Nullable EventListener eventListener,
@NonNull StickerRecord sticker,
@Px int size)
@Px int size,
boolean allowApngAnimation)
{
currentSticker = sticker;

Expand All @@ -102,6 +106,7 @@ public void bind(@NonNull GlideRequests glideRequests,
itemView.requestLayout();

glideRequests.load(new DecryptableUri(sticker.getUri()))
.set(ApngOptions.ANIMATE, allowApngAnimation)
.transition(DrawableTransitionOptions.withCrossFade())
.into(image);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.stickers.StickerKeyboardPageAdapter.StickerKeyboardPageViewHolder;
import org.thoughtcrime.securesms.util.DeviceProperties;
import org.whispersystems.libsignal.util.Pair;

/**
Expand Down Expand Up @@ -69,7 +70,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
GlideRequests glideRequests = GlideApp.with(this);

this.list = view.findViewById(R.id.sticker_keyboard_list);
this.adapter = new StickerKeyboardPageAdapter(glideRequests, this);
this.adapter = new StickerKeyboardPageAdapter(glideRequests, this, DeviceProperties.shouldAllowApngStickerAnimation(requireContext()));
this.layoutManager = new GridLayoutManager(requireContext(), 2);
this.listTouchListener = new StickerRolloverTouchListener(requireContext(), glideRequests, eventListener, this);
this.packId = getArguments().getString(KEY_PACK_ID);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.model.StickerPackRecord;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.glide.cache.ApngOptions;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.stickers.StickerKeyboardPageFragment.EventListener;
import org.thoughtcrime.securesms.stickers.StickerKeyboardRepository.PackListResult;
import org.thoughtcrime.securesms.util.DeviceProperties;
import org.thoughtcrime.securesms.util.Throttler;

import java.util.ArrayList;
Expand Down Expand Up @@ -147,7 +149,7 @@ private void present(@NonNull Presenter presenter, @NonNull PackListResult resul
startingIndex = !result.hasRecents() && result.getPacks().size() > 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));
Expand Down Expand Up @@ -238,10 +240,12 @@ private static class IconProvider implements TabIconProvider {

private final Context context;
private final List<StickerPackRecord> packs;
private final boolean allowApngAnimation;

private IconProvider(@NonNull Context context, List<StickerPackRecord> packs) {
this.context = context;
this.packs = packs;
private IconProvider(@NonNull Context context, List<StickerPackRecord> packs, boolean allowApngAnimation) {
this.context = context;
this.packs = packs;
this.allowApngAnimation = allowApngAnimation;
}

@Override
Expand All @@ -253,6 +257,7 @@ public void loadCategoryTabIcon(@NonNull GlideRequests glideRequests, @NonNull I
Uri uri = packs.get(index - 1).getCover().getUri();

glideRequests.load(new DecryptableStreamUriLoader.DecryptableUri(uri))
.set(ApngOptions.ANIMATE, allowApngAnimation)
.into(imageView);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
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;

/**
Expand Down Expand Up @@ -93,7 +94,7 @@ public void onStickerPackShareClicked(@NonNull String packId, @NonNull String pa

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
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;
Expand All @@ -38,6 +39,7 @@ final class StickerManagementAdapter extends SectionedRecyclerViewAdapter<String

private final GlideRequests glideRequests;
private final EventListener eventListener;
private final boolean allowApngAnimation;

private final List<StickerSection> sections = new ArrayList<StickerSection>(3) {{
StickerSection yourStickers = new StickerSection(TAG_YOUR_STICKERS,
Expand All @@ -55,9 +57,10 @@ final class StickerManagementAdapter extends SectionedRecyclerViewAdapter<String
add(messageStickers);
}};

StickerManagementAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener) {
this.glideRequests = glideRequests;
this.eventListener = eventListener;
StickerManagementAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener, boolean allowApngAnimation) {
this.glideRequests = glideRequests;
this.eventListener = eventListener;
this.allowApngAnimation = allowApngAnimation;
}

@Override
Expand All @@ -82,7 +85,7 @@ final class StickerManagementAdapter extends SectionedRecyclerViewAdapter<String

@Override
public void bindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull StickerSection section, int localPosition) {
section.bindViewHolder(viewHolder, localPosition, glideRequests, eventListener);
section.bindViewHolder(viewHolder, localPosition, glideRequests, eventListener, allowApngAnimation);
}

@Override
Expand Down Expand Up @@ -198,14 +201,15 @@ public long getItemId(@NonNull StableIdGenerator<String> idGenerator, int global
void bindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder,
int localPosition,
@NonNull GlideRequests glideRequests,
@NonNull EventListener eventListener)
@NonNull EventListener eventListener,
boolean allowApngAnimation)
{
if (localPosition == 0) {
((HeaderViewHolder) viewHolder).bind(titleResId);
} else if (records.isEmpty()) {
((EmptyViewHolder) viewHolder).bind(emptyResId);
} else {
((StickerViewHolder) viewHolder).bind(glideRequests, eventListener, records.get(localPosition - 1), localPosition == records.size());
((StickerViewHolder) viewHolder).bind(glideRequests, eventListener, records.get(localPosition - 1), localPosition == records.size(), allowApngAnimation);
}
}

Expand Down Expand Up @@ -254,7 +258,8 @@ static class StickerViewHolder extends RecyclerView.ViewHolder {
void bind(@NonNull GlideRequests glideRequests,
@NonNull EventListener eventListener,
@NonNull StickerPackRecord stickerPack,
boolean lastInList)
boolean lastInList,
boolean allowApngAnimation)
{
title.setText(stickerPack.getTitle().or(itemView.getResources().getString(R.string.StickerManagementAdapter_untitled)));
author.setText(stickerPack.getAuthor().or(itemView.getResources().getString(R.string.StickerManagementAdapter_unknown)));
Expand All @@ -268,6 +273,7 @@ void bind(@NonNull GlideRequests glideRequests,

glideRequests.load(new DecryptableUri(stickerPack.getCover().getUri()))
.transition(DrawableTransitionOptions.withCrossFade())
.set(ApngOptions.ANIMATE, allowApngAnimation)
.into(cover);

if (stickerPack.isInstalled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.glide.cache.ApngOptions;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.sharing.ShareActivity;
import org.thoughtcrime.securesms.stickers.StickerManifest.Sticker;
import org.thoughtcrime.securesms.util.DeviceProperties;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.whispersystems.libsignal.util.Pair;
Expand Down Expand Up @@ -140,7 +142,7 @@ private void initView() {
this.shareButton = findViewById(R.id.sticker_install_share_button);
this.shareButtonImage = findViewById(R.id.sticker_install_share_button_image);

this.adapter = new StickerPackPreviewAdapter(GlideApp.with(this), this);
this.adapter = new StickerPackPreviewAdapter(GlideApp.with(this), this, DeviceProperties.shouldAllowApngStickerAnimation(this));
this.layoutManager = new GridLayoutManager(this, 2);
this.touchListener = new StickerRolloverTouchListener(this, GlideApp.with(this), this, this);
onScreenWidthChanged(getScreenWidth());
Expand Down Expand Up @@ -192,6 +194,7 @@ private void presentManifest(@NonNull StickerManifest manifest) {
: new StickerRemoteUri(cover.getPackId(), cover.getPackKey(), cover.getId());
GlideApp.with(this).load(model)
.transition(DrawableTransitionOptions.withCrossFade())
.set(ApngOptions.ANIMATE, DeviceProperties.shouldAllowApngStickerAnimation(this))
.into(coverImage);
} else {
coverImage.setImageDrawable(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;

import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.glide.cache.ApngOptions;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
import org.thoughtcrime.securesms.mms.GlideRequests;

Expand All @@ -23,11 +24,13 @@ public final class StickerPackPreviewAdapter extends RecyclerView.Adapter<Sticke
private final GlideRequests glideRequests;
private final EventListener eventListener;
private final List<StickerManifest.Sticker> 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
Expand All @@ -37,7 +40,7 @@ public StickerPackPreviewAdapter(@NonNull GlideRequests glideRequests, @NonNull

@Override
public void onBindViewHolder(@NonNull StickerViewHolder stickerViewHolder, int i) {
stickerViewHolder.bind(glideRequests, list.get(i), eventListener);
stickerViewHolder.bind(glideRequests, list.get(i), eventListener, allowApngAnimation);
}

@Override
Expand Down Expand Up @@ -68,12 +71,17 @@ private StickerViewHolder(@NonNull View itemView) {
this.image = itemView.findViewById(R.id.sticker_install_item_image);
}

void bind(@NonNull GlideRequests glideRequests, @NonNull StickerManifest.Sticker sticker, @NonNull EventListener eventListener) {
void bind(@NonNull GlideRequests glideRequests,
@NonNull StickerManifest.Sticker sticker,
@NonNull EventListener eventListener,
boolean allowApngAnimation)
{
currentEmoji = sticker.getEmoji();
currentGlideModel = sticker.getUri().isPresent() ? new DecryptableStreamUriLoader.DecryptableUri(sticker.getUri().get())
: new StickerRemoteUri(sticker.getPackId(), sticker.getPackKey(), sticker.getId());
glideRequests.load(currentGlideModel)
.transition(DrawableTransitionOptions.withCrossFade())
.set(ApngOptions.ANIMATE, allowApngAnimation)
.into(image);

image.setOnLongClickListener(v -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}

0 comments on commit 92b586c

Please sign in to comment.