Merge pull request #4880 from k9mail/update_glide

Update to Glide 4.11.0
This commit is contained in:
cketti 2020-07-18 00:17:24 +02:00 committed by GitHub
commit 50cbb5cca8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 274 additions and 112 deletions

View file

@ -29,6 +29,9 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${versions.androidxLifecycle}"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:${versions.androidxLifecycle}"
implementation "com.github.bumptech.glide:glide:${versions.glide}"
kapt "com.github.bumptech.glide:compiler:${versions.glide}"
// Required for DependencyInjectionTest to be able to resolve OpenPgpApiManager
testImplementation project(':plugins:openpgp-api-lib:openpgp-api')

View file

@ -18,6 +18,14 @@
-dontwarn okio.**
-dontwarn com.squareup.moshi.**
# Glide
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public class * extends com.bumptech.glide.module.LibraryGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# Project specific rules
-dontnote com.fsck.k9.ui.messageview.**
-dontnote com.fsck.k9.view.**

View file

@ -0,0 +1,8 @@
package com.fsck.k9.glide;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
@GlideModule
public class K9AppGlideModule extends AppGlideModule {
}

View file

@ -24,6 +24,9 @@ dependencies {
implementation "com.jakewharton.timber:timber:${versions.timber}"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.kotlinCoroutines}"
implementation "com.github.bumptech.glide:glide:${versions.glide}"
annotationProcessor "com.github.bumptech.glide:compiler:${versions.glide}"
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
// Required for DependencyInjectionTest to be able to resolve OpenPgpApiManager

View file

@ -17,6 +17,14 @@
-dontwarn okio.**
-dontwarn com.squareup.moshi.**
# Glide
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public class * extends com.bumptech.glide.module.LibraryGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# Project specific rules
-dontnote com.fsck.k9.ui.messageview.**
-dontnote com.fsck.k9.view.**

View file

@ -0,0 +1,8 @@
package com.fsck.k9.glide;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
@GlideModule
public class K9AppGlideModule extends AppGlideModule {
}

View file

@ -34,7 +34,6 @@ dependencies {
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "com.google.android.material:material:${versions.materialComponents}"
implementation "de.cketti.library.changelog:ckchangelog:1.2.1"
implementation "com.github.bumptech.glide:glide:3.6.1"
implementation "com.splitwise:tokenautocomplete:2.0.7"
implementation "de.cketti.safecontentresolver:safe-content-resolver-v21:1.0.0"
implementation "com.xwray:groupie:2.8.0"
@ -53,6 +52,9 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.kotlinCoroutines}"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.kotlinCoroutines}"
implementation "com.github.bumptech.glide:glide:${versions.glide}"
annotationProcessor "com.github.bumptech.glide:compiler:${versions.glide}"
testImplementation project(':mail:testing')
testImplementation project(':app:storage')
testImplementation project(':app:testing')

View file

@ -0,0 +1,52 @@
package com.fsck.k9.contacts
import com.bumptech.glide.load.Key
import com.fsck.k9.mail.Address
import java.security.MessageDigest
/**
* Contains all information necessary for [ContactImageBitmapDecoder] to load the contact picture in the desired format.
*/
class ContactImage(
val contactLetterOnly: Boolean,
val backgroundCacheId: String,
val contactLetterBitmapCreator: ContactLetterBitmapCreator,
val address: Address
) : Key {
private val contactLetterSignature = contactLetterBitmapCreator.signatureOf(address)
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(toString().toByteArray(Key.CHARSET))
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ContactImage
if (contactLetterOnly != other.contactLetterOnly) return false
if (backgroundCacheId != other.backgroundCacheId) return false
if (address != other.address) return false
if (contactLetterSignature != other.contactLetterSignature) return false
return true
}
override fun hashCode(): Int {
var result = contactLetterOnly.hashCode()
result = 31 * result + backgroundCacheId.hashCode()
result = 31 * result + address.hashCode()
result = 31 * result + contactLetterSignature.hashCode()
return result
}
override fun toString(): String {
return "ContactImage(" +
"contactLetterOnly=$contactLetterOnly, " +
"backgroundCacheId='$backgroundCacheId', " +
"address=$address, " +
"contactLetterSignature='$contactLetterSignature'" +
")"
}
}

View file

@ -0,0 +1,46 @@
package com.fsck.k9.contacts
import android.graphics.Bitmap
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 kotlin.math.max
/**
* [ResourceDecoder] implementation that takes a [ContactImage] and fetches the corresponding contact photo using
* [ContactPhotoLoader] or generates a fallback image using [ContactLetterBitmapCreator].
*/
internal class ContactImageBitmapDecoder(
private val contactPhotoLoader: ContactPhotoLoader,
private val bitmapPool: BitmapPool
) : ResourceDecoder<ContactImage, Bitmap> {
override fun decode(contactImage: ContactImage, width: Int, height: Int, options: Options): Resource<Bitmap>? {
val size = max(width, height)
val bitmap = loadContactPhoto(contactImage) ?: createContactLetterBitmap(contactImage, size)
return BitmapResource.obtain(bitmap, bitmapPool)
}
private fun loadContactPhoto(contactImage: ContactImage): Bitmap? {
if (contactImage.contactLetterOnly) return null
return contactPhotoLoader.loadContactPhoto(contactImage.address.address)
}
private fun createContactLetterBitmap(contactImage: ContactImage, size: Int): Bitmap {
val bitmap = bitmapPool.getDirty(size, size, Bitmap.Config.ARGB_8888)
return contactImage.contactLetterBitmapCreator.drawBitmap(bitmap, size, contactImage.address)
}
override fun handles(source: ContactImage, options: Options) = true
}
internal class ContactImageBitmapDecoderFactory(private val contactPhotoLoader: ContactPhotoLoader) {
fun create(bitmapPool: BitmapPool): ContactImageBitmapDecoder {
return ContactImageBitmapDecoder(contactPhotoLoader, bitmapPool)
}
}

View file

@ -0,0 +1,49 @@
package com.fsck.k9.contacts
import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.Options
import com.bumptech.glide.load.ResourceDecoder
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
/**
* [ModelLoader] implementation that does nothing put pass through [ContactImage] to be handled by our custom
* [ResourceDecoder] implementation, [ContactImageBitmapDecoder].
*/
class ContactImageModelLoader : ModelLoader<ContactImage, ContactImage> {
override fun buildLoadData(
contactImage: ContactImage,
width: Int,
height: Int,
options: Options
): ModelLoader.LoadData<ContactImage> {
return ModelLoader.LoadData(contactImage, ContactImageDataFetcher(contactImage))
}
override fun handles(model: ContactImage) = true
}
class ContactImageDataFetcher(private val contactImage: ContactImage) : DataFetcher<ContactImage> {
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in ContactImage>) {
callback.onDataReady(contactImage)
}
override fun getDataClass() = ContactImage::class.java
override fun getDataSource() = DataSource.LOCAL
override fun cleanup() = Unit
override fun cancel() = Unit
}
class ContactImageModelLoaderFactory : ModelLoaderFactory<ContactImage, ContactImage> {
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<ContactImage, ContactImage> {
return ContactImageModelLoader()
}
override fun teardown() = Unit
}

View file

@ -4,8 +4,6 @@ import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import com.bumptech.glide.load.Key
import com.bumptech.glide.signature.StringSignature
import com.fsck.k9.mail.Address
import com.fsck.k9.ui.helper.MaterialColors
@ -57,10 +55,8 @@ class ContactLetterBitmapCreator(
}
}
fun signatureOf(address: Address): Key {
val letter = letterExtractor.extractContactLetter(address)
val backgroundColor = calcUnknownContactColor(address)
return StringSignature(letter + backgroundColor)
fun signatureOf(address: Address): String {
return calcUnknownContactColor(address).toString()
}
companion object {

View file

@ -0,0 +1,21 @@
package com.fsck.k9.contacts
import android.content.ContentResolver
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.fsck.k9.helper.Contacts
import timber.log.Timber
internal class ContactPhotoLoader(private val contentResolver: ContentResolver, private val contacts: Contacts) {
fun loadContactPhoto(emailAddress: String): Bitmap? {
val photoUri = contacts.getPhotoUri(emailAddress) ?: return null
return try {
contentResolver.openInputStream(photoUri).use { inputStream ->
BitmapFactory.decodeStream(inputStream)
}
} catch (e: Exception) {
Timber.e(e, "Couldn't load contact photo: $photoUri")
null
}
}
}

View file

@ -0,0 +1,24 @@
package com.fsck.k9.contacts;
import android.content.Context;
import android.graphics.Bitmap;
import com.bumptech.glide.Glide;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.LibraryGlideModule;
import com.fsck.k9.DI;
import org.jetbrains.annotations.NotNull;
@GlideModule
public class ContactPictureGlideModule extends LibraryGlideModule {
@Override
public void registerComponents(@NotNull Context context, @NotNull Glide glide, Registry registry) {
registry.append(ContactImage.class, ContactImage.class, new ContactImageModelLoaderFactory());
ContactImageBitmapDecoderFactory factory = DI.get(ContactImageBitmapDecoderFactory.class);
ContactImageBitmapDecoder contactImageBitmapDecoder = factory.create(glide.getBitmapPool());
registry.append(ContactImage.class, Bitmap.class, contactImageBitmapDecoder);
}
}

View file

@ -2,32 +2,20 @@ package com.fsck.k9.contacts
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
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.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.resource.bitmap.BitmapResource
import com.bumptech.glide.request.FutureTarget
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 kotlin.math.max
import timber.log.Timber
class ContactPictureLoader(
private val context: Context,
private val contactLetterBitmapCreator: ContactLetterBitmapCreator
) {
private val contactsHelper: Contacts = Contacts.getInstance(context)
private val pictureSizeInPx: Int = PICTURE_SIZE.toDip(context)
private val backgroundCacheId: String = with(contactLetterBitmapCreator.config) {
if (hasDefaultBackgroundColor) defaultBackgroundColor.toString() else "*"
@ -35,15 +23,10 @@ class ContactPictureLoader(
fun setContactPicture(imageView: ImageView, address: Address) {
Glide.with(imageView.context)
.using(AddressModelLoader(backgroundCacheId), Address::class.java)
.from(Address::class.java)
.`as`(Bitmap::class.java)
.decoder(ContactImageBitmapDecoder())
.signature(contactLetterBitmapCreator.signatureOf(address))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.load(address)
.dontAnimate()
.into(imageView)
.load(createContactImage(address, contactLetterOnly = false))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.dontAnimate()
.into(imageView)
}
fun setContactPicture(imageView: ImageView, recipient: Recipient) {
@ -57,24 +40,19 @@ class ContactPictureLoader(
private fun setContactPicture(imageView: ImageView, contactPictureUri: Uri) {
Glide.with(imageView.context)
.load(contactPictureUri)
.error(R.drawable.ic_contact_picture)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.dontAnimate()
.into(imageView)
.load(contactPictureUri)
.error(R.drawable.ic_contact_picture)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.dontAnimate()
.into(imageView)
}
private fun setFallbackPicture(imageView: ImageView, address: Address) {
Glide.with(imageView.context)
.using(AddressModelLoader(backgroundCacheId), Address::class.java)
.from(Address::class.java)
.`as`(Bitmap::class.java)
.decoder(ContactImageBitmapDecoder(contactLetterOnly = true))
.signature(contactLetterBitmapCreator.signatureOf(address))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.load(address)
.dontAnimate()
.into(imageView)
.load(createContactImage(address, contactLetterOnly = true))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.dontAnimate()
.into(imageView)
}
@WorkerThread
@ -91,79 +69,32 @@ class ContactPictureLoader(
private fun getContactPicture(contactPictureUri: Uri): Bitmap? {
return Glide.with(context)
.load(contactPictureUri)
.asBitmap()
.error(R.drawable.ic_contact_picture)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.dontAnimate()
.into(pictureSizeInPx, pictureSizeInPx)
.getOrNull()
.asBitmap()
.load(contactPictureUri)
.error(R.drawable.ic_contact_picture)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.dontAnimate()
.submit(pictureSizeInPx, pictureSizeInPx)
.getOrNull()
}
private fun getFallbackPicture(address: Address): Bitmap? {
return Glide.with(context)
.using(AddressModelLoader(backgroundCacheId), Address::class.java)
.from(Address::class.java)
.`as`(Bitmap::class.java)
.decoder(ContactImageBitmapDecoder(contactLetterOnly = true))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.load(address)
.dontAnimate()
.into(pictureSizeInPx, pictureSizeInPx)
.getOrNull()
.asBitmap()
.load(createContactImage(address, contactLetterOnly = true))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.dontAnimate()
.submit(pictureSizeInPx, pictureSizeInPx)
.getOrNull()
}
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 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"
}
}
private class AddressModelLoader(val backgroundCacheId: String) : ModelLoader<Address, Address> {
override fun getResourceFetcher(address: Address, width: Int, height: Int): DataFetcher<Address> {
return object : DataFetcher<Address> {
override fun getId() = "${address.address}-${address.personal}-$backgroundCacheId"
override fun loadData(priority: Priority?): Address = address
override fun cleanup() = Unit
override fun cancel() = Unit
}
}
private fun createContactImage(address: Address, contactLetterOnly: Boolean): ContactImage {
return ContactImage(
contactLetterOnly = contactLetterOnly,
backgroundCacheId = backgroundCacheId,
contactLetterBitmapCreator = contactLetterBitmapCreator,
address = address
)
}
private fun <T> FutureTarget<T>.getOrNull(): T? {

View file

@ -4,7 +4,9 @@ import org.koin.dsl.module
val contactsModule = module {
single { ContactLetterExtractor() }
factory { ContactLetterBitmapConfig(get(), get()) }
factory { ContactLetterBitmapCreator(get(), get()) }
factory { ContactPictureLoader(get(), get()) }
factory { ContactLetterBitmapConfig(context = get(), themeManager = get()) }
factory { ContactLetterBitmapCreator(letterExtractor = get(), config = get()) }
factory { ContactPhotoLoader(contentResolver = get(), contacts = get()) }
factory { ContactPictureLoader(context = get(), contactLetterBitmapCreator = get()) }
factory { ContactImageBitmapDecoderFactory(contactPhotoLoader = get()) }
}

View file

@ -34,6 +34,7 @@ buildscript {
'mime4j': '0.8.3',
'okhttp': '4.8.0',
'minidns': '0.3.4',
'glide': '4.11.0',
'androidxTestRunner': '1.2.0',
'junit': '4.13',