mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-02 08:23:00 +01:00
Add new APNG renderer, just for internal users for now.
This commit is contained in:
committed by
Cody Henthorne
parent
34d87cf6e1
commit
c3f9e5d972
@@ -613,6 +613,7 @@ dependencies {
|
||||
implementation(project(":core:models-jvm"))
|
||||
implementation(project(":feature:camera"))
|
||||
implementation(project(":feature:registration"))
|
||||
implementation(project(":lib:apng"))
|
||||
|
||||
implementation(libs.androidx.fragment.ktx)
|
||||
implementation(libs.androidx.appcompat) {
|
||||
|
||||
@@ -12,4 +12,5 @@ sealed interface LabsSettingsEvents {
|
||||
data class ToggleGroupSuggestionsForMembers(val enabled: Boolean) : LabsSettingsEvents
|
||||
data class ToggleBetterSearch(val enabled: Boolean) : LabsSettingsEvents
|
||||
data class ToggleAutoLowerHand(val enabled: Boolean) : LabsSettingsEvents
|
||||
data class ToggleNewApngRenderer(val enabled: Boolean) : LabsSettingsEvents
|
||||
}
|
||||
|
||||
@@ -142,6 +142,15 @@ private fun LabsSettingsContent(
|
||||
onCheckChanged = { onEvent(LabsSettingsEvents.ToggleAutoLowerHand(it)) }
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.ToggleRow(
|
||||
checked = state.newApngRenderer,
|
||||
text = "New APNG Renderer",
|
||||
label = "Use the new custom APNG renderer instead of the existing third-party library. Requires an app restart to take effect.",
|
||||
onCheckChanged = { onEvent(LabsSettingsEvents.ToggleNewApngRenderer(it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,5 +14,6 @@ data class LabsSettingsState(
|
||||
val incognito: Boolean = false,
|
||||
val groupSuggestionsForMembers: Boolean = false,
|
||||
val betterSearch: Boolean = false,
|
||||
val autoLowerHand: Boolean = false
|
||||
val autoLowerHand: Boolean = false,
|
||||
val newApngRenderer: Boolean = false
|
||||
)
|
||||
|
||||
@@ -41,6 +41,10 @@ class LabsSettingsViewModel : ViewModel() {
|
||||
SignalStore.labs.autoLowerHand = event.enabled
|
||||
_state.value = _state.value.copy(autoLowerHand = event.enabled)
|
||||
}
|
||||
is LabsSettingsEvents.ToggleNewApngRenderer -> {
|
||||
SignalStore.labs.newApngRenderer = event.enabled
|
||||
_state.value = _state.value.copy(newApngRenderer = event.enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +55,8 @@ class LabsSettingsViewModel : ViewModel() {
|
||||
incognito = SignalStore.labs.incognito,
|
||||
groupSuggestionsForMembers = SignalStore.labs.groupSuggestionsForMembers,
|
||||
betterSearch = SignalStore.labs.betterSearch,
|
||||
autoLowerHand = SignalStore.labs.autoLowerHand
|
||||
autoLowerHand = SignalStore.labs.autoLowerHand,
|
||||
newApngRenderer = SignalStore.labs.newApngRenderer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,12 @@ import com.bumptech.glide.load.resource.gif.ByteBufferGifDecoder;
|
||||
import com.bumptech.glide.load.resource.gif.GifDrawable;
|
||||
import com.bumptech.glide.load.resource.gif.StreamGifDecoder;
|
||||
|
||||
import org.signal.apng.ApngDecoder;
|
||||
import org.signal.blurhash.BlurHash;
|
||||
import org.signal.glide.load.resource.apng.decode.APNGDecoder;
|
||||
import org.signal.glide.blurhash.BlurHashModelLoader;
|
||||
import org.signal.glide.blurhash.BlurHashResourceDecoder;
|
||||
import org.signal.glide.common.io.InputStreamFactory;
|
||||
import org.signal.glide.load.resource.apng.decode.APNGDecoder;
|
||||
import org.thoughtcrime.securesms.badges.load.BadgeLoader;
|
||||
import org.thoughtcrime.securesms.badges.load.GiftBadgeModel;
|
||||
import org.thoughtcrime.securesms.badges.models.Badge;
|
||||
@@ -29,9 +30,13 @@ import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoLoader;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.giph.model.ChunkedImageUrl;
|
||||
import org.thoughtcrime.securesms.glide.cache.ApngDrawableTranscoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.ApngFrameDrawableTranscoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.ApngInputStreamFactoryResourceDecoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.ApngInputStreamResourceDecoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.ByteBufferApngDecoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedApngCacheEncoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedApngResourceEncoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedBitmapResourceEncoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedCacheDecoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedCacheEncoder;
|
||||
@@ -46,6 +51,7 @@ import org.signal.glide.decryptableuri.DecryptableUri;
|
||||
import org.signal.glide.decryptableuri.DecryptableUriStreamLoader;
|
||||
import org.thoughtcrime.securesms.mms.RegisterGlideComponents;
|
||||
import org.thoughtcrime.securesms.mms.SignalGlideModule;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.stickers.StickerRemoteUri;
|
||||
import org.thoughtcrime.securesms.stickers.StickerRemoteUriLoader;
|
||||
import org.thoughtcrime.securesms.stories.StoryTextPostModel;
|
||||
@@ -85,16 +91,25 @@ public class SignalGlideComponents implements RegisterGlideComponents {
|
||||
registry.prepend(Bitmap.class, new EncryptedBitmapResourceEncoder(secret));
|
||||
registry.prepend(BitmapDrawable.class, new BitmapDrawableEncoder(glide.getBitmapPool(), encryptedBitmapResourceEncoder));
|
||||
|
||||
ByteBufferApngDecoder byteBufferApngDecoder = new ByteBufferApngDecoder();
|
||||
StreamApngDecoder streamApngDecoder = new StreamApngDecoder(byteBufferApngDecoder);
|
||||
StreamFactoryApngDecoder streamFactoryApngDecoder = new StreamFactoryApngDecoder(byteBufferApngDecoder, glide, registry);
|
||||
|
||||
registry.prepend(InputStream.class, APNGDecoder.class, streamApngDecoder);
|
||||
registry.prepend(InputStreamFactory.class, APNGDecoder.class, streamFactoryApngDecoder);
|
||||
registry.prepend(ByteBuffer.class, APNGDecoder.class, byteBufferApngDecoder);
|
||||
registry.prepend(APNGDecoder.class, new EncryptedApngCacheEncoder(secret));
|
||||
registry.prepend(File.class, APNGDecoder.class, new EncryptedCacheDecoder<>(secret, streamApngDecoder));
|
||||
registry.register(APNGDecoder.class, Drawable.class, new ApngFrameDrawableTranscoder());
|
||||
if (SignalStore.labs().getNewApngRenderer()) {
|
||||
registry.prepend(InputStream.class, ApngDecoder.class, new ApngInputStreamResourceDecoder());
|
||||
registry.prepend(InputStreamFactory.class, ApngDecoder.class, new ApngInputStreamFactoryResourceDecoder());
|
||||
registry.prepend(ApngDecoder.class, new EncryptedApngResourceEncoder(secret));
|
||||
registry.prepend(File.class, ApngDecoder.class, new EncryptedCacheDecoder<>(secret, new ApngInputStreamResourceDecoder()));
|
||||
registry.register(ApngDecoder.class, Drawable.class, new ApngDrawableTranscoder());
|
||||
} else {
|
||||
ByteBufferApngDecoder byteBufferApngDecoder = new ByteBufferApngDecoder();
|
||||
StreamApngDecoder streamApngDecoder = new StreamApngDecoder(byteBufferApngDecoder);
|
||||
StreamFactoryApngDecoder streamFactoryApngDecoder = new StreamFactoryApngDecoder(byteBufferApngDecoder, glide, registry);
|
||||
|
||||
registry.prepend(InputStream.class, APNGDecoder.class, streamApngDecoder);
|
||||
registry.prepend(InputStreamFactory.class, APNGDecoder.class, streamFactoryApngDecoder);
|
||||
registry.prepend(ByteBuffer.class, APNGDecoder.class, byteBufferApngDecoder);
|
||||
registry.prepend(APNGDecoder.class, new EncryptedApngCacheEncoder(secret));
|
||||
registry.prepend(File.class, APNGDecoder.class, new EncryptedCacheDecoder<>(secret, streamApngDecoder));
|
||||
registry.register(APNGDecoder.class, Drawable.class, new ApngFrameDrawableTranscoder());
|
||||
}
|
||||
|
||||
registry.prepend(BlurHash.class, Bitmap.class, new BlurHashResourceDecoder());
|
||||
registry.prepend(StoryTextPostModel.class, Bitmap.class, new StoryTextPostModel.Decoder());
|
||||
|
||||
28
app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngDrawableTranscoder.kt
vendored
Normal file
28
app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngDrawableTranscoder.kt
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package org.thoughtcrime.securesms.glide.cache
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.engine.Resource
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableResource
|
||||
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder
|
||||
import org.signal.apng.ApngDecoder
|
||||
import org.signal.apng.ApngDrawable
|
||||
|
||||
class ApngDrawableTranscoder : ResourceTranscoder<ApngDecoder, Drawable> {
|
||||
override fun transcode(toTranscode: Resource<ApngDecoder>, options: Options): Resource<Drawable> {
|
||||
val decoder = toTranscode.get()
|
||||
val drawable = ApngDrawable(decoder).apply {
|
||||
loopForever = true
|
||||
}
|
||||
|
||||
return object : DrawableResource<Drawable>(drawable) {
|
||||
override fun getResourceClass(): Class<Drawable> = Drawable::class.java
|
||||
|
||||
override fun getSize(): Int = 0
|
||||
|
||||
override fun recycle() {
|
||||
(get() as ApngDrawable).recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.glide.cache
|
||||
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.ResourceDecoder
|
||||
import com.bumptech.glide.load.engine.Resource
|
||||
import org.signal.apng.ApngDecoder
|
||||
import org.signal.core.util.readFully
|
||||
import org.signal.glide.apng.ApngOptions
|
||||
import org.signal.glide.common.io.InputStreamFactory
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
|
||||
class ApngInputStreamFactoryResourceDecoder : ResourceDecoder<InputStreamFactory, ApngDecoder> {
|
||||
|
||||
override fun handles(source: InputStreamFactory, options: Options): Boolean {
|
||||
return if (options.get(ApngOptions.ANIMATE) == true) {
|
||||
ApngDecoder.isApng(source.create())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun decode(source: InputStreamFactory, width: Int, height: Int, options: Options): Resource<ApngDecoder>? {
|
||||
val data: ByteArray = source.create().readFully()
|
||||
val decoder = ApngDecoder(ByteArrayInputStream(data))
|
||||
return ApngResource(decoder, data.size)
|
||||
}
|
||||
}
|
||||
39
app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngInputStreamResourceDecoder.kt
vendored
Normal file
39
app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngInputStreamResourceDecoder.kt
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.glide.cache
|
||||
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.ResourceDecoder
|
||||
import com.bumptech.glide.load.engine.Resource
|
||||
import org.signal.apng.ApngDecoder
|
||||
import org.signal.core.util.readFully
|
||||
import org.signal.core.util.stream.LimitedInputStream
|
||||
import org.signal.glide.apng.ApngOptions
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
class ApngInputStreamResourceDecoder : ResourceDecoder<InputStream, ApngDecoder> {
|
||||
companion object {
|
||||
/** Set to match [com.bumptech.glide.load.data.InputStreamRewinder]'s read limit */
|
||||
private const val READ_LIMIT: Long = 5 * 1024 * 1024
|
||||
}
|
||||
|
||||
override fun handles(source: InputStream, options: Options): Boolean {
|
||||
return if (options.get(ApngOptions.ANIMATE)!!) {
|
||||
ApngDecoder.isApng(LimitedInputStream(source, READ_LIMIT))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun decode(source: InputStream, width: Int, height: Int, options: Options): Resource<ApngDecoder>? {
|
||||
val data: ByteArray = source.readFully()
|
||||
val decoder = ApngDecoder(ByteArrayInputStream(data))
|
||||
return ApngResource(decoder, data.size)
|
||||
}
|
||||
}
|
||||
21
app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngResource.kt
vendored
Normal file
21
app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngResource.kt
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.glide.cache
|
||||
|
||||
import com.bumptech.glide.load.engine.Resource
|
||||
import org.signal.apng.ApngDecoder
|
||||
|
||||
class ApngResource(private val decoder: ApngDecoder, private val size: Int) : Resource<ApngDecoder> {
|
||||
override fun getResourceClass(): Class<ApngDecoder> = ApngDecoder::class.java
|
||||
|
||||
override fun get(): ApngDecoder = decoder
|
||||
|
||||
override fun getSize(): Int = size
|
||||
|
||||
override fun recycle() {
|
||||
decoder.inputStream.close()
|
||||
}
|
||||
}
|
||||
36
app/src/main/java/org/thoughtcrime/securesms/glide/cache/EncryptedApngResourceEncoder.kt
vendored
Normal file
36
app/src/main/java/org/thoughtcrime/securesms/glide/cache/EncryptedApngResourceEncoder.kt
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package org.thoughtcrime.securesms.glide.cache
|
||||
|
||||
import com.bumptech.glide.load.EncodeStrategy
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.ResourceEncoder
|
||||
import com.bumptech.glide.load.engine.Resource
|
||||
import org.signal.apng.ApngDecoder
|
||||
import org.signal.core.util.logging.Log.tag
|
||||
import org.signal.core.util.logging.Log.w
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
internal class EncryptedApngResourceEncoder(private val secret: ByteArray) : EncryptedCoder(), ResourceEncoder<ApngDecoder> {
|
||||
override fun getEncodeStrategy(options: Options): EncodeStrategy {
|
||||
return EncodeStrategy.SOURCE
|
||||
}
|
||||
|
||||
override fun encode(data: Resource<ApngDecoder>, file: File, options: Options): Boolean {
|
||||
try {
|
||||
val input = data.get().inputStream
|
||||
val output = createEncryptedOutputStream(secret, file)
|
||||
|
||||
input.reset()
|
||||
input.copyTo(output)
|
||||
|
||||
return true
|
||||
} catch (e: IOException) {
|
||||
w(TAG, e)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = tag(EncryptedApngResourceEncoder::class.java)
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ class LabsValues internal constructor(store: KeyValueStore) : SignalStoreValues(
|
||||
const val GROUP_SUGGESTIONS_FOR_MEMBERS: String = "labs.group_suggestions_for_members"
|
||||
const val BETTER_SEARCH: String = "labs.better_search"
|
||||
const val AUTO_LOWER_HAND: String = "labs.auto_lower_hand"
|
||||
const val NEW_APNG_RENDERER: String = "labs.new_apng_renderer"
|
||||
}
|
||||
|
||||
public override fun onFirstEverAppLaunch() = Unit
|
||||
@@ -28,6 +29,8 @@ class LabsValues internal constructor(store: KeyValueStore) : SignalStoreValues(
|
||||
|
||||
var autoLowerHand by booleanValue(AUTO_LOWER_HAND, true).falseForExternalUsers()
|
||||
|
||||
var newApngRenderer by booleanValue(NEW_APNG_RENDERER, true).falseForExternalUsers()
|
||||
|
||||
private fun SignalStoreValueDelegate<Boolean>.falseForExternalUsers(): SignalStoreValueDelegate<Boolean> {
|
||||
return this.map { actualValue -> RemoteConfig.internalUser && actualValue }
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.annotation.RequiresApi;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
|
||||
/**
|
||||
* Easy access to various properties of the device, typically to make performance-related decisions.
|
||||
@@ -25,6 +26,10 @@ public final class DeviceProperties {
|
||||
* large numbers of APNGs simultaneously.
|
||||
*/
|
||||
public static boolean shouldAllowApngStickerAnimation(@NonNull Context context) {
|
||||
if (SignalStore.labs().getNewApngRenderer()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MemoryInfo memoryInfo = getMemoryInfo(context);
|
||||
int memoryMb = (int) ByteUnit.BYTES.toMegabytes(memoryInfo.totalMem);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user