diff --git a/app/build.gradle b/app/build.gradle index 271257fb62..974288342d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -135,6 +135,7 @@ android { buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode" buildConfigField "String", "DEFAULT_CURRENCIES", "\"EUR,AUD,GBP,CAD,CNY\"" buildConfigField "int[]", "MOBILE_COIN_REGIONS", "new int[]{44}" + buildConfigField "String", "GIPHY_API_KEY", "\"3o6ZsYH6U6Eri53TXy\"" ndk { abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java index b82d7eca1c..c187b0710c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java @@ -15,18 +15,22 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.bumptech.glide.Priority; import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.GlideException; import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.target.Target; import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.components.emoji.parsing.EmojiDrawInfo; import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser; +import org.thoughtcrime.securesms.emoji.EmojiBitmapDecoder; import org.thoughtcrime.securesms.emoji.EmojiSource; import org.thoughtcrime.securesms.mms.GlideApp; +import org.thoughtcrime.securesms.util.DeviceProperties; class EmojiProvider { @@ -89,12 +93,16 @@ class EmojiProvider { return null; } - final EmojiSource source = EmojiSource.getLatest(); - final EmojiDrawable drawable = new EmojiDrawable(source, drawInfo); + final int lowMemoryDecodeScale = DeviceProperties.isLowMemoryDevice(context) ? 2 : 1; + final EmojiSource source = EmojiSource.getLatest(); + final EmojiDrawable drawable = new EmojiDrawable(source, drawInfo, lowMemoryDecodeScale); GlideApp.with(context) .asBitmap() .diskCacheStrategy(DiskCacheStrategy.NONE) .load(drawInfo.getPage().getModel()) + .priority(Priority.HIGH) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .apply(new RequestOptions().set(EmojiBitmapDecoder.OPTION, lowMemoryDecodeScale)) .addListener(new RequestListener() { @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { @@ -130,12 +138,12 @@ class EmojiProvider { return (int) intrinsicHeight; } - EmojiDrawable(@NonNull EmojiSource source, @NonNull EmojiDrawInfo info) { - this.intrinsicWidth = source.getMetrics().getRawWidth() * source.getDecodeScale(); - this.intrinsicHeight = source.getMetrics().getRawHeight() * source.getDecodeScale(); + EmojiDrawable(@NonNull EmojiSource source, @NonNull EmojiDrawInfo info, int lowMemoryDecodeScale) { + this.intrinsicWidth = (source.getMetrics().getRawWidth() * source.getDecodeScale()) / lowMemoryDecodeScale; + this.intrinsicHeight = (source.getMetrics().getRawHeight() * source.getDecodeScale()) / lowMemoryDecodeScale; - final int glyphWidth = (int) (source.getMetrics().getRawWidth() * source.getDecodeScale()); - final int glyphHeight = (int) (source.getMetrics().getRawHeight() * source.getDecodeScale()); + final int glyphWidth = (int) (intrinsicWidth); + final int glyphHeight = (int) (intrinsicHeight); final int index = info.getIndex(); final int emojiPerRow = source.getMetrics().getPerRow(); final int xStart = (index % emojiPerRow) * glyphWidth; diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiBitmapDecoder.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiBitmapDecoder.kt new file mode 100644 index 0000000000..975f694b90 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiBitmapDecoder.kt @@ -0,0 +1,40 @@ +package org.thoughtcrime.securesms.emoji + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import com.bumptech.glide.load.Option +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.ResourceDecoder +import com.bumptech.glide.load.engine.Resource +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool +import com.bumptech.glide.load.resource.bitmap.BitmapResource +import java.io.InputStream + +/** + * Allows fine grain control over how we decode Emoji pages via a scale factor. + * + * This can be set via RequestOptions on a Glide request: + * + * ``` + * .apply(RequestOptions().set(EmojiBitmapDecoder.OPTION, inSampleSize) + * ``` + */ +class EmojiBitmapDecoder(private val bitmapPool: BitmapPool) : ResourceDecoder { + + override fun handles(source: InputStream, options: Options): Boolean { + return options.get(OPTION)?.let { it > 1 } ?: false + } + + override fun decode(source: InputStream, width: Int, height: Int, options: Options): Resource? { + val bitmapOptions = BitmapFactory.Options() + + bitmapOptions.inSampleSize = requireNotNull(options.get(OPTION)) + + return BitmapResource.obtain(BitmapFactory.decodeStream(source, null, bitmapOptions), bitmapPool) + } + + companion object { + @JvmField + val OPTION: Option = Option.memory("emoji_sample_size", 1) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4PagedDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4PagedDataSource.java index cb3af59aad..8aac4f85ef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4PagedDataSource.java +++ b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4PagedDataSource.java @@ -8,6 +8,7 @@ import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; import org.signal.paging.PagedDataSource; +import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.giph.model.GiphyImage; import org.thoughtcrime.securesms.giph.model.GiphyResponse; @@ -27,6 +28,20 @@ import okhttp3.Response; */ final class GiphyMp4PagedDataSource implements PagedDataSource { + private static final Uri BASE_GIPHY_URI = Uri.parse("https://api.giphy.com/v1/gifs/") + .buildUpon() + .appendQueryParameter("api_key", BuildConfig.GIPHY_API_KEY) + .build(); + + private static final Uri TRENDING_URI = BASE_GIPHY_URI.buildUpon() + .appendPath("trending") + .build(); + + private static final Uri SEARCH_URI = BASE_GIPHY_URI.buildUpon() + .appendPath("search") + .build(); + + private static final String TAG = Log.tag(GiphyMp4PagedDataSource.class); private final String searchString; @@ -64,7 +79,7 @@ final class GiphyMp4PagedDataSource implements PagedDataSource { String url; if (TextUtils.isEmpty(searchString)) url = getTrendingUrl(start, length); - else url = getSearchUrl(start, length, Uri.encode(searchString)); + else url = getSearchUrl(start, length, searchString); Request request = new Request.Builder().url(url).build(); @@ -83,10 +98,19 @@ final class GiphyMp4PagedDataSource implements PagedDataSource { } private String getTrendingUrl(int start, int length) { - return "https://api.giphy.com/v1/gifs/trending?api_key=3o6ZsYH6U6Eri53TXy&offset=" + start + "&limit=" + length; + return TRENDING_URI.buildUpon() + .appendQueryParameter("offset", String.valueOf(start)) + .appendQueryParameter("limit", String.valueOf(length)) + .build() + .toString(); } private String getSearchUrl(int start, int length, @NonNull String query) { - return "https://api.giphy.com/v1/gifs/search?api_key=3o6ZsYH6U6Eri53TXy&offset=" + start + "&limit=" + length + "&q=" + Uri.encode(query); + return SEARCH_URI.buildUpon() + .appendQueryParameter("offset", String.valueOf(start)) + .appendQueryParameter("limit", String.valueOf(length)) + .appendQueryParameter("q", query) + .build() + .toString(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java b/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java index 16872a6243..804151c04e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java @@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.blurhash.BlurHashResourceDecoder; import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto; import org.thoughtcrime.securesms.crypto.AttachmentSecret; import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider; +import org.thoughtcrime.securesms.emoji.EmojiBitmapDecoder; import org.thoughtcrime.securesms.giph.model.ChunkedImageUrl; import org.thoughtcrime.securesms.glide.ChunkedImageUrlLoader; import org.thoughtcrime.securesms.glide.ContactPhotoLoader; @@ -82,6 +83,7 @@ public class SignalGlideModule extends AppGlideModule { ApngBufferCacheDecoder apngBufferCacheDecoder = new ApngBufferCacheDecoder(); ApngStreamCacheDecoder apngStreamCacheDecoder = new ApngStreamCacheDecoder(apngBufferCacheDecoder); + registry.prepend(InputStream.class, Bitmap.class, new EmojiBitmapDecoder(glide.getBitmapPool())); registry.prepend(InputStream.class, APNGDecoder.class, apngStreamCacheDecoder); registry.prepend(ByteBuffer.class, APNGDecoder.class, apngBufferCacheDecoder); registry.prepend(APNGDecoder.class, new EncryptedApngCacheEncoder(secret));