Disable mass APNG animation on low-memory devices.

fork-5.53.8
Greyson Parrelli 2021-01-24 16:37:32 -05:00
rodzic acbc17c909
commit 92b586c061
12 zmienionych plików z 125 dodań i 30 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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() {}
}

Wyświetl plik

@ -26,7 +26,11 @@ public class ApngStreamCacheDecoder implements ResourceDecoder<InputStream, APNG
@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

Wyświetl plik

@ -14,6 +14,7 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
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;
@ -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);
}
@ -52,7 +55,7 @@ final class StickerKeyboardPageAdapter extends RecyclerView.Adapter<StickerKeybo
@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
@ -93,7 +96,8 @@ final class StickerKeyboardPageAdapter extends RecyclerView.Adapter<StickerKeybo
public void bind(@NonNull GlideRequests glideRequests,
@Nullable EventListener eventListener,
@NonNull StickerRecord sticker,
@Px int size)
@Px int size,
boolean allowApngAnimation)
{
currentSticker = sticker;
@ -102,6 +106,7 @@ final class StickerKeyboardPageAdapter extends RecyclerView.Adapter<StickerKeybo
itemView.requestLayout();
glideRequests.load(new DecryptableUri(sticker.getUri()))
.set(ApngOptions.ANIMATE, allowApngAnimation)
.transition(DrawableTransitionOptions.withCrossFade())
.into(image);

Wyświetl plik

@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
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;
/**
@ -69,7 +70,7 @@ public final class StickerKeyboardPageFragment extends Fragment implements Stick
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);

Wyświetl plik

@ -19,10 +19,12 @@ import org.thoughtcrime.securesms.components.emoji.MediaKeyboardProvider;
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;
@ -147,7 +149,7 @@ public final class StickerKeyboardProvider implements MediaKeyboardProvider,
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));
@ -238,10 +240,12 @@ public final class StickerKeyboardProvider implements MediaKeyboardProvider,
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
@ -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);
}
}

Wyświetl plik

@ -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);

Wyświetl plik

@ -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<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,
@ -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
@ -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
@ -198,14 +201,15 @@ final class StickerManagementAdapter extends SectionedRecyclerViewAdapter<String
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);
}
}
@ -254,7 +258,8 @@ final class StickerManagementAdapter extends SectionedRecyclerViewAdapter<String
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)));
@ -268,6 +273,7 @@ final class StickerManagementAdapter extends SectionedRecyclerViewAdapter<String
glideRequests.load(new DecryptableUri(stickerPack.getCover().getUri()))
.transition(DrawableTransitionOptions.withCrossFade())
.set(ApngOptions.ANIMATE, allowApngAnimation)
.into(cover);
if (stickerPack.isInstalled()) {

Wyświetl plik

@ -21,10 +21,12 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
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;
@ -140,7 +142,7 @@ public final class StickerPackPreviewActivity extends PassphraseRequiredActivity
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());
@ -192,6 +194,7 @@ public final class StickerPackPreviewActivity extends PassphraseRequiredActivity
: 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);

Wyświetl plik

@ -12,6 +12,7 @@ import androidx.recyclerview.widget.RecyclerView;
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;
@ -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
@ -37,7 +40,7 @@ public final class StickerPackPreviewAdapter extends RecyclerView.Adapter<Sticke
@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
@ -68,12 +71,17 @@ public final class StickerPackPreviewAdapter extends RecyclerView.Adapter<Sticke
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 -> {

Wyświetl plik

@ -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();
}
}

Wyświetl plik

@ -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<String, Object> getMemoryValues() {
return new TreeMap<>(REMOTE_VALUES);