Support showing all frames of animated gif image in Android 14+ (#4284)

* Support showing all frames of animated gif when sent as image in Android 14+

* Use unique image names and delete existing files that are no longer needed

* Fix lint

* update behavior to keep 2 days of images
This commit is contained in:
Nicolas Mowen 2024-03-23 07:55:56 -06:00 committed by GitHub
parent b23b1838d0
commit ef6327ffb3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -13,6 +13,7 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.Icon
import android.media.AudioManager
import android.media.MediaMetadataRetriever
import android.media.RingtoneManager
@ -38,6 +39,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.content.getSystemService
import androidx.core.text.isDigitsOnly
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
@ -85,6 +87,9 @@ import java.io.File
import java.io.FileOutputStream
import java.net.URL
import java.net.URLDecoder
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
@ -1205,8 +1210,15 @@ class MessagingManager @Inject constructor(
builder
.setLargeIcon(bitmap)
.setStyle(
NotificationCompat.BigPictureStyle()
.bigPicture(bitmap)
NotificationCompat.BigPictureStyle().also { style ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
saveTempAnimatedImage(serverId, url, !UrlUtil.isAbsoluteUrl(dataImage))?.let { filePath ->
style.bigPicture(Icon.createWithContentUri(filePath))
} ?: { style.bigPicture(bitmap) }
} else {
style.bigPicture(bitmap)
}
}
.bigLargeIcon(null as Bitmap?)
)
}
@ -1239,6 +1251,42 @@ class MessagingManager @Inject constructor(
return@withContext image
}
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
private suspend fun saveTempAnimatedImage(serverId: Int, url: URL?, requiresAuth: Boolean = false): Uri? =
withContext(
Dispatchers.IO
) {
if (url == null || url.path.endsWith("gif").not()) {
return@withContext null
}
// delete previous images that are no longer needed
val imageCutoff = LocalDateTime.now().minusDays(2)
context.externalCacheDir?.listFiles()?.filter { file ->
file.absolutePath.endsWith("_animated_notification.gif") &&
imageCutoff.isAfter(LocalDateTime.ofInstant(Instant.ofEpochMilli(file.lastModified()), ZoneId.systemDefault()))
}?.forEach { expired -> expired.delete() }
val file = File(context.externalCacheDir, "${System.currentTimeMillis()}_animated_notification.gif")
try {
val request = Request.Builder().apply {
url(url)
if (requiresAuth) {
addHeader("Authorization", serverManager.authenticationRepository(serverId).buildBearerToken())
}
}.build()
val response = okHttpClient.newCall(request).execute()
val bytes = response.body?.bytes() ?: return@withContext null
file.writeBytes(bytes)
response.close()
} catch (e: Exception) {
Log.e(TAG, "Couldn't download image for notification", e)
}
return@withContext FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
}
private suspend fun handleVideo(
builder: NotificationCompat.Builder,
data: Map<String, String>