Fix contact picture flickering in message list

Change the way we try to load contact pictures and generate contact
letter fallback images afterwards, so Glide caches the result.
This commit is contained in:
cketti 2019-10-22 01:55:41 +02:00
parent 7dadab7362
commit e7d8cda043

View file

@ -3,33 +3,26 @@ package com.fsck.k9.contacts
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Bitmap.CompressFormat
import android.graphics.BitmapFactory
import android.net.Uri
import androidx.annotation.WorkerThread
import android.widget.ImageView
import androidx.annotation.WorkerThread
import com.bumptech.glide.Glide
import com.bumptech.glide.Priority
import com.bumptech.glide.load.ResourceDecoder
import com.bumptech.glide.load.data.DataFetcher
import com.bumptech.glide.load.data.StreamLocalUriFetcher
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.Resource
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.load.model.stream.StreamModelLoader
import com.bumptech.glide.load.resource.bitmap.BitmapEncoder
import com.bumptech.glide.load.resource.bitmap.BitmapResource
import com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder
import com.bumptech.glide.load.resource.drawable.GlideDrawable
import com.bumptech.glide.load.resource.file.FileToStreamDecoder
import com.bumptech.glide.load.resource.transcode.BitmapToGlideDrawableTranscoder
import com.bumptech.glide.request.FutureTarget
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.fsck.k9.helper.Contacts
import com.fsck.k9.mail.Address
import com.fsck.k9.ui.R
import com.fsck.k9.view.RecipientSelectView.Recipient
import java.io.InputStream
import timber.log.Timber
import kotlin.math.max
class ContactPictureLoader(
private val context: Context,
@ -44,53 +37,45 @@ class ContactPictureLoader(
fun setContactPicture(imageView: ImageView, address: Address) {
Glide.with(imageView.context)
.using(ContactPictureModelLoader())
.using(AddressModelLoader(backgroundCacheId), Address::class.java)
.from(Address::class.java)
.load(address)
.`as`(Bitmap::class.java)
.decoder(ContactImageBitmapDecoder())
.signature(contactLetterBitmapCreator.signatureOf(address))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(FallbackImageRequestListener(address, imageView))
// for some reason, following 2 lines fix loading issues.
.load(address)
.dontAnimate()
.override(pictureSizeInPx, pictureSizeInPx)
.into(imageView)
}
fun setContactPicture(imageView: ImageView, recipient: Recipient) {
val contactPictureUri = recipient.photoThumbnailUri
if (contactPictureUri != null) {
setContactPicture(imageView, contactPictureUri, recipient.address)
setContactPicture(imageView, contactPictureUri)
} else {
setFallbackPicture(imageView, recipient.address)
}
}
private fun setContactPicture(imageView: ImageView, contactPictureUri: Uri, address: Address) {
private fun setContactPicture(imageView: ImageView, contactPictureUri: Uri) {
Glide.with(imageView.context)
.load(contactPictureUri)
.error(R.drawable.ic_contact_picture)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(FallbackImageRequestListener(address, imageView))
// for some reason, following 2 lines fix loading issues.
.dontAnimate()
.override(pictureSizeInPx, pictureSizeInPx)
.into(imageView)
}
private fun setFallbackPicture(imageView: ImageView, address: Address) {
val context = imageView.context
Glide.with(context)
Glide.with(imageView.context)
.using(AddressModelLoader(backgroundCacheId), Address::class.java)
.from(Address::class.java)
.`as`(Bitmap::class.java)
.transcode(BitmapToGlideDrawableTranscoder(context), GlideDrawable::class.java)
.decoder(ContactLetterBitmapDecoder())
.decoder(ContactImageBitmapDecoder(contactLetterOnly = true))
.signature(contactLetterBitmapCreator.signatureOf(address))
.encoder(BitmapEncoder(CompressFormat.PNG, 0))
.cacheDecoder(FileToStreamDecoder(StreamBitmapDecoder(context)))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.load(address)
// for some reason, following 2 lines fix loading issues.
.dontAnimate()
.override(pictureSizeInPx, pictureSizeInPx)
.into(imageView)
}
@ -110,6 +95,7 @@ class ContactPictureLoader(
return Glide.with(context)
.load(contactPictureUri)
.asBitmap()
.error(R.drawable.ic_contact_picture)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.dontAnimate()
.into(pictureSizeInPx, pictureSizeInPx)
@ -121,9 +107,7 @@ class ContactPictureLoader(
.using(AddressModelLoader(backgroundCacheId), Address::class.java)
.from(Address::class.java)
.`as`(Bitmap::class.java)
.decoder(ContactLetterBitmapDecoder())
.encoder(BitmapEncoder(CompressFormat.PNG, 0))
.cacheDecoder(FileToStreamDecoder(StreamBitmapDecoder(context)))
.decoder(ContactImageBitmapDecoder(contactLetterOnly = true))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.load(address)
.dontAnimate()
@ -131,18 +115,41 @@ class ContactPictureLoader(
.getOrNull()
}
private inner class ContactLetterBitmapDecoder : ResourceDecoder<Address, Bitmap> {
private inner class ContactImageBitmapDecoder(
private val contactLetterOnly: Boolean = false
) : ResourceDecoder<Address, Bitmap> {
override fun decode(address: Address, width: Int, height: Int): Resource<Bitmap> {
val pool = Glide.get(context).bitmapPool
val bitmap: Bitmap =
pool.getDirty(pictureSizeInPx, pictureSizeInPx, Bitmap.Config.ARGB_8888) ?:
Bitmap.createBitmap(pictureSizeInPx, pictureSizeInPx, Bitmap.Config.ARGB_8888)
contactLetterBitmapCreator.drawBitmap(bitmap, pictureSizeInPx, address)
val size = max(width, height)
val bitmap = loadContactPicture(address) ?: createContactLetterBitmap(address, size, pool)
return BitmapResource.obtain(bitmap, pool)
}
private fun loadContactPicture(address: Address): Bitmap? {
if (contactLetterOnly) return null
val photoUri = contactsHelper.getPhotoUri(address.address) ?: return null
return try {
context.contentResolver.openInputStream(photoUri).use { inputStream ->
BitmapFactory.decodeStream(inputStream)
}
} catch (e: Exception) {
Timber.e(e, "Couldn't load contact picture: $photoUri")
null
}
}
private fun createContactLetterBitmap(address: Address, size: Int, pool: BitmapPool): Bitmap {
val bitmap = pool.getDirty(size, size, Bitmap.Config.ARGB_8888)
?: Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
return contactLetterBitmapCreator.drawBitmap(bitmap, size, address)
}
override fun getId(): String {
return "fallback-photo"
}
@ -161,61 +168,6 @@ class ContactPictureLoader(
}
}
private inner class ContactPictureModelLoader : StreamModelLoader<Address> {
override fun getResourceFetcher(address: Address, width: Int, height: Int): DataFetcher<InputStream>? {
return ContactPictureDataFetcher(address)
}
}
private inner class ContactPictureDataFetcher(val address: Address) : DataFetcher<InputStream> {
var streamLocalUriFetcher: StreamLocalUriFetcher? = null
override fun loadData(priority: Priority?): InputStream? {
val photoUri = contactsHelper.getPhotoUri(address.address)
return photoUri?.let {
StreamLocalUriFetcher(context, photoUri).also {
streamLocalUriFetcher = it
}.loadData(priority)
}
}
override fun cancel() = Unit
override fun cleanup() {
streamLocalUriFetcher?.cleanup()
}
override fun getId() = "contact:${address.address}"
}
private inner class FallbackImageRequestListener<T>(
val address: Address,
val imageView: ImageView
) : RequestListener<T, GlideDrawable> {
override fun onException(
e: Exception?,
model: T,
target: Target<GlideDrawable>,
isFirstResource: Boolean
): Boolean {
setFallbackPicture(imageView, address)
return true
}
override fun onResourceReady(
resource: GlideDrawable,
model: T,
target: Target<GlideDrawable>,
isFromMemoryCache: Boolean,
isFirstResource: Boolean
): Boolean {
return false
}
}
private fun <T> FutureTarget<T>.getOrNull(): T? {
return try {
get()