From cb9ab61b6bb716dde35fefde0591117b9b48a573 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Thu, 6 May 2021 16:19:24 -0300 Subject: [PATCH] Fix issue where gifs would load as images. --- .../components/emoji/EmojiProvider.java | 6 +- .../emoji/parsing/EmojiDrawInfo.java | 10 +-- .../securesms/emoji/EmojiBitmapDecoder.kt | 40 --------- .../securesms/emoji/EmojiDownloadListener.kt | 34 -------- .../thoughtcrime/securesms/emoji/EmojiPage.kt | 85 +++++++++++++++++++ .../securesms/emoji/EmojiPageReference.kt | 22 ----- .../securesms/emoji/EmojiSource.kt | 11 ++- .../securesms/mms/SignalGlideModule.java | 4 +- .../securesms/emoji/EmojiSourceTest.kt | 2 +- 9 files changed, 101 insertions(+), 113 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiBitmapDecoder.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiDownloadListener.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiPage.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiPageReference.kt 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 c187b0710..6a8d1ed38 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 @@ -27,7 +27,7 @@ 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.EmojiPage; import org.thoughtcrime.securesms.emoji.EmojiSource; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.util.DeviceProperties; @@ -99,10 +99,10 @@ class EmojiProvider { GlideApp.with(context) .asBitmap() .diskCacheStrategy(DiskCacheStrategy.NONE) - .load(drawInfo.getPage().getModel()) + .load(drawInfo.getPage()) .priority(Priority.HIGH) .diskCacheStrategy(DiskCacheStrategy.NONE) - .apply(new RequestOptions().set(EmojiBitmapDecoder.OPTION, lowMemoryDecodeScale)) + .apply(new RequestOptions().set(EmojiPage.IN_SAMPLE_SIZE, lowMemoryDecodeScale)) .addListener(new RequestListener() { @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiDrawInfo.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiDrawInfo.java index 8e1cfbeab..c3d5114bd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiDrawInfo.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiDrawInfo.java @@ -3,19 +3,19 @@ package org.thoughtcrime.securesms.components.emoji.parsing; import androidx.annotation.NonNull; -import org.thoughtcrime.securesms.emoji.EmojiPageReference; +import org.thoughtcrime.securesms.emoji.EmojiPage; public class EmojiDrawInfo { - private final EmojiPageReference page; - private final int index; + private final EmojiPage page; + private final int index; - public EmojiDrawInfo(final @NonNull EmojiPageReference page, final int index) { + public EmojiDrawInfo(final @NonNull EmojiPage page, final int index) { this.page = page; this.index = index; } - public @NonNull EmojiPageReference getPage() { + public @NonNull EmojiPage getPage() { return page; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiBitmapDecoder.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiBitmapDecoder.kt deleted file mode 100644 index 975f694b9..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiBitmapDecoder.kt +++ /dev/null @@ -1,40 +0,0 @@ -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/emoji/EmojiDownloadListener.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiDownloadListener.kt deleted file mode 100644 index 4f9963cd7..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiDownloadListener.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.thoughtcrime.securesms.emoji - -import android.content.Context -import android.content.Intent -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies -import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob -import org.thoughtcrime.securesms.keyvalue.SignalStore -import org.thoughtcrime.securesms.service.PersistentAlarmManagerListener -import java.util.concurrent.TimeUnit - -private val INTERVAL_WITHOUT_REMOTE_DOWNLOAD = TimeUnit.DAYS.toMillis(1) -private val INTERVAL_WITH_REMOTE_DOWNLOAD = TimeUnit.DAYS.toMillis(7) - -class EmojiDownloadListener : PersistentAlarmManagerListener() { - - override fun getNextScheduledExecutionTime(context: Context): Long = SignalStore.emojiValues().nextScheduledCheck - - override fun onAlarm(context: Context, scheduledTime: Long): Long { - ApplicationDependencies.getJobManager().add(DownloadLatestEmojiDataJob(false)) - - val nextTime: Long = System.currentTimeMillis() + if (EmojiFiles.Version.exists(context)) INTERVAL_WITH_REMOTE_DOWNLOAD else INTERVAL_WITHOUT_REMOTE_DOWNLOAD - - SignalStore.emojiValues().nextScheduledCheck = nextTime - - return nextTime - } - - companion object { - @JvmStatic - fun schedule(context: Context) { - EmojiDownloadListener().onReceive(context, Intent()) - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiPage.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiPage.kt new file mode 100644 index 000000000..fca927cf7 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiPage.kt @@ -0,0 +1,85 @@ +package org.thoughtcrime.securesms.emoji + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import com.bumptech.glide.Priority +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.Key +import com.bumptech.glide.load.Option +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.data.DataFetcher +import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.load.model.ModelLoaderFactory +import com.bumptech.glide.load.model.MultiModelLoaderFactory +import org.thoughtcrime.securesms.mms.PartAuthority +import java.io.InputStream +import java.security.MessageDigest + +typealias EmojiPageFactory = (Uri) -> EmojiPage + +sealed class EmojiPage(private val uri: Uri) : Key { + override fun updateDiskCacheKey(messageDigest: MessageDigest) { + messageDigest.update("EmojiPage".encodeToByteArray()) + messageDigest.update(uri.toString().encodeToByteArray()) + } + + data class Asset(private val uri: Uri) : EmojiPage(uri) + data class Disk(private val uri: Uri) : EmojiPage(uri) + + class Loader(private val context: Context) : ModelLoader { + override fun buildLoadData( + model: EmojiPage, + width: Int, + height: Int, + options: Options + ): ModelLoader.LoadData { + return ModelLoader.LoadData(model, Fetcher(context, model, options.get(IN_SAMPLE_SIZE) ?: 1)) + } + + override fun handles(model: EmojiPage): Boolean = true + + class Factory(private val context: Context) : ModelLoaderFactory { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { + return Loader(context) + } + + override fun teardown() = Unit + } + } + + class Fetcher(private val context: Context, private val model: EmojiPage, private val inSampleSize: Int) : DataFetcher { + override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { + try { + val inputStream: InputStream = when (model) { + is Asset -> context.assets.open(model.uri.toString().replace("file:///android_asset/", "")) + is Disk -> EmojiFiles.openForReading(context, PartAuthority.getEmojiFilename(model.uri)) + } + + val bitmapOptions = BitmapFactory.Options() + bitmapOptions.inSampleSize = inSampleSize + + callback.onDataReady(BitmapFactory.decodeStream(inputStream, null, bitmapOptions)) + } catch (e: Exception) { + callback.onLoadFailed(e) + } + } + + override fun cleanup() = Unit + override fun cancel() = Unit + + override fun getDataClass(): Class { + return Bitmap::class.java + } + + override fun getDataSource(): DataSource { + return DataSource.LOCAL + } + } + + companion object { + @JvmField + val IN_SAMPLE_SIZE: Option = Option.memory("emoji_page_in_sample_size", 1) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiPageReference.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiPageReference.kt deleted file mode 100644 index 00bfe5215..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiPageReference.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.thoughtcrime.securesms.emoji - -import android.net.Uri -import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader - -/** - * Used by Emoji provider to set up a glide request. - */ -class EmojiPageReference { - - val model: Any - - constructor(uri: Uri) { - model = uri - } - - constructor(decryptableUri: DecryptableStreamUriLoader.DecryptableUri) { - model = decryptableUri - } -} - -typealias EmojiPageReferenceFactory = (uri: Uri) -> EmojiPageReference diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt index e4dd45487..c790feed4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiSource.kt @@ -9,7 +9,6 @@ import org.thoughtcrime.securesms.components.emoji.parsing.EmojiDrawInfo import org.thoughtcrime.securesms.components.emoji.parsing.EmojiTree import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore -import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader import org.thoughtcrime.securesms.util.ScreenDensity import java.io.InputStream import java.util.concurrent.CountDownLatch @@ -21,7 +20,7 @@ import java.util.concurrent.atomic.AtomicReference class EmojiSource( val decodeScale: Float, private val emojiData: EmojiData, - private val emojiPageReferenceFactory: EmojiPageReferenceFactory + private val emojiPageFactory: EmojiPageFactory ) : EmojiData by emojiData { val variationMap: Map by lazy { @@ -50,9 +49,9 @@ class EmojiSource( dataPages .filter { it.spriteUri != null } .forEach { page -> - val reference = emojiPageReferenceFactory(page.spriteUri!!) + val emojiPage = emojiPageFactory(page.spriteUri!!) page.emoji.forEachIndexed { idx, emoji -> - tree.add(emoji, EmojiDrawInfo(reference, idx)) + tree.add(emoji, EmojiDrawInfo(emojiPage, idx)) } } @@ -97,7 +96,7 @@ class EmojiSource( val density = ScreenDensity.xhdpiRelativeDensityScaleFactor(version.density) return emojiData?.let { - EmojiSource(density, it) { uri: Uri -> EmojiPageReference(DecryptableStreamUriLoader.DecryptableUri(uri)) } + EmojiSource(density, it) { uri: Uri -> EmojiPage.Disk(uri) } } } @@ -112,7 +111,7 @@ class EmojiSource( displayPages = parsedData.displayPages + PAGE_EMOTICONS, dataPages = parsedData.dataPages + PAGE_EMOTICONS ) - ) { uri: Uri -> EmojiPageReference(uri) } + ) { uri: Uri -> EmojiPage.Asset(uri) } } } } 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 804151c04..f3e717fc0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java @@ -29,7 +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.emoji.EmojiPage; import org.thoughtcrime.securesms.giph.model.ChunkedImageUrl; import org.thoughtcrime.securesms.glide.ChunkedImageUrlLoader; import org.thoughtcrime.securesms.glide.ContactPhotoLoader; @@ -83,7 +83,6 @@ 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)); @@ -92,6 +91,7 @@ public class SignalGlideModule extends AppGlideModule { registry.prepend(BlurHash.class, Bitmap.class, new BlurHashResourceDecoder()); + registry.append(EmojiPage.class, Bitmap.class, new EmojiPage.Loader.Factory(context)); registry.append(ConversationShortcutPhoto.class, Bitmap.class, new ConversationShortcutPhoto.Loader.Factory(context)); registry.append(ContactPhoto.class, InputStream.class, new ContactPhotoLoader.Factory(context)); registry.append(DecryptableUri.class, InputStream.class, new DecryptableStreamUriLoader.Factory(context)); diff --git a/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiSourceTest.kt b/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiSourceTest.kt index bc4c03372..4a247de57 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiSourceTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/emoji/EmojiSourceTest.kt @@ -11,7 +11,7 @@ class EmojiSourceTest { @Test fun `Given a bunch of data pages with max value 100100, when I get the maxEmojiLength, then I expect 6`() { val emojiDataFake = ParsedEmojiData(EmojiMetrics(-1, -1, -1), listOf(), "png", listOf(), dataPages = generatePages(), listOf()) - val testSubject = EmojiSource(0f, emojiDataFake, ::EmojiPageReference) + val testSubject = EmojiSource(0f, emojiDataFake) { uri -> EmojiPage.Disk(uri) } Assert.assertEquals(6, testSubject.maxEmojiLength) }